From 5c17e0283e875a9535e657ee1c0daeb13d88ec57 Mon Sep 17 00:00:00 2001 From: jsklan Date: Thu, 9 Apr 2026 19:12:27 -0400 Subject: [PATCH 01/29] chore(seed): add allOf composition test fixtures Add two test-definition fixtures exercising allOf edge cases: - allof: default settings (extends mode) - allof-inline: inline-all-of-schemas enabled, shares the same spec Covers array items narrowing, primitive constraint narrowing via $ref, required propagation, metadata inheritance, and multi-parent overlap. --- .../fern/apis/allof-inline/generators.yml | 6 + .../fern/apis/allof/generators.yml | 4 + test-definitions/fern/apis/allof/openapi.yml | 285 ++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 test-definitions/fern/apis/allof-inline/generators.yml create mode 100644 test-definitions/fern/apis/allof/generators.yml create mode 100644 test-definitions/fern/apis/allof/openapi.yml diff --git a/test-definitions/fern/apis/allof-inline/generators.yml b/test-definitions/fern/apis/allof-inline/generators.yml new file mode 100644 index 000000000000..a39ff2fc742f --- /dev/null +++ b/test-definitions/fern/apis/allof-inline/generators.yml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json +api: + specs: + - openapi: ../allof/openapi.yml + settings: + inline-all-of-schemas: true diff --git a/test-definitions/fern/apis/allof/generators.yml b/test-definitions/fern/apis/allof/generators.yml new file mode 100644 index 000000000000..8762d29b8370 --- /dev/null +++ b/test-definitions/fern/apis/allof/generators.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json +api: + specs: + - openapi: ./openapi.yml diff --git a/test-definitions/fern/apis/allof/openapi.yml b/test-definitions/fern/apis/allof/openapi.yml new file mode 100644 index 000000000000..e1f054848c18 --- /dev/null +++ b/test-definitions/fern/apis/allof/openapi.yml @@ -0,0 +1,285 @@ +openapi: 3.0.3 +info: + title: allOf Composition + version: 1.0.0 + description: > + Exercises allOf schema composition patterns, covering array items narrowing, + primitive constraint narrowing, required propagation, metadata inheritance, + and multi-parent property merging. + +servers: + - url: https://api.example.com + +paths: + /rule-types: + get: + operationId: searchRuleTypes + summary: Search rule types with paginated results + parameters: + - name: query + in: query + schema: + type: string + responses: + "200": + description: Paginated list of rule types + content: + application/json: + schema: + $ref: "#/components/schemas/RuleTypeSearchResponse" + + /rules: + post: + operationId: createRule + summary: Create a rule with constrained execution context + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/RuleCreateRequest" + responses: + "200": + description: Created rule + content: + application/json: + schema: + $ref: "#/components/schemas/RuleResponse" + + /users: + get: + operationId: listUsers + summary: List users with paginated results + responses: + "200": + description: Paginated list of users + content: + application/json: + schema: + $ref: "#/components/schemas/UserSearchResponse" + + /entities: + get: + operationId: getEntity + summary: Get an entity that combines multiple parents + responses: + "200": + description: An entity with properties from multiple parents + content: + application/json: + schema: + $ref: "#/components/schemas/CombinedEntity" + +components: + schemas: + # ----------------------------------------------------------------------- + # Base schemas + # ----------------------------------------------------------------------- + PaginatedResult: + type: object + required: + - paging + - results + properties: + paging: + $ref: "#/components/schemas/PagingCursors" + results: + type: array + maxItems: 100 + description: Current page of results from the requested resource. + items: {} + + PagingCursors: + type: object + required: + - next + properties: + next: + type: string + description: Cursor for the next page of results. + previous: + type: string + description: Cursor for the previous page of results. + + RuleExecutionContext: + type: string + description: Execution environment for a rule. + enum: + - prod + - staging + - dev + + AuditInfo: + type: object + description: Common audit metadata. + required: + - createdBy + - createdDateTime + properties: + createdBy: + type: string + description: The user who created this resource. + readOnly: true + createdDateTime: + type: string + format: date-time + description: When this resource was created. + readOnly: true + modifiedBy: + type: string + description: The user who last modified this resource. + readOnly: true + modifiedDateTime: + type: string + format: date-time + description: When this resource was last modified. + readOnly: true + + # ----------------------------------------------------------------------- + # Case 1: Array items narrowing without redeclaring type: array + # + # The child specifies only `items` to narrow the element type. Per the + # spec, the merged result should be type: array with items: RuleType. + # The parent's `required: [results]` must also propagate. + # ----------------------------------------------------------------------- + RuleType: + type: object + required: + - id + - name + properties: + id: + type: string + name: + type: string + description: + type: string + + RuleTypeSearchResponse: + allOf: + - $ref: "#/components/schemas/PaginatedResult" + - properties: + results: + items: + $ref: "#/components/schemas/RuleType" + + # ----------------------------------------------------------------------- + # Case 2: Array items narrowing WITH explicit type: array + # + # Same as Case 1, but the child redeclares type: array alongside items. + # This should produce identical output to Case 1. + # ----------------------------------------------------------------------- + User: + type: object + required: + - id + - email + properties: + id: + type: string + email: + type: string + format: email + + UserSearchResponse: + allOf: + - $ref: "#/components/schemas/PaginatedResult" + - type: object + properties: + results: + type: array + items: + $ref: "#/components/schemas/User" + + # ----------------------------------------------------------------------- + # Case 3: Property-level allOf narrowing a primitive type + # + # allOf on a single property combines a $ref to an enum with inline + # constraints. The result should be a string with both the enum values + # and the pattern constraint — not an empty object. + # ----------------------------------------------------------------------- + RuleCreateRequest: + type: object + required: + - name + - executionContext + properties: + name: + type: string + executionContext: + allOf: + - $ref: "#/components/schemas/RuleExecutionContext" + - type: string + pattern: "^(?!prod$)[a-z0-9_-]+$" + description: > + Execution context for the rule, excluding the prod environment. + + # ----------------------------------------------------------------------- + # Case 4: allOf with metadata inheritance (description, readOnly) + # + # Child schema should inherit readOnly and description from parent + # properties even when it only narrows part of the property definition. + # ----------------------------------------------------------------------- + RuleResponse: + allOf: + - $ref: "#/components/schemas/AuditInfo" + - type: object + required: + - id + - name + - status + properties: + id: + type: string + name: + type: string + status: + type: string + enum: + - active + - inactive + - draft + executionContext: + $ref: "#/components/schemas/RuleExecutionContext" + + # ----------------------------------------------------------------------- + # Case 5: allOf composing multiple parents with overlapping properties + # + # Two parents define the same property (`name`) with different + # descriptions. The composed schema also adds its own properties. + # ----------------------------------------------------------------------- + Identifiable: + type: object + required: + - id + properties: + id: + type: string + description: Unique identifier. + name: + type: string + description: Display name from Identifiable. + + Describable: + type: object + properties: + name: + type: string + description: Display name from Describable. + summary: + type: string + description: A short summary. + + CombinedEntity: + allOf: + - $ref: "#/components/schemas/Identifiable" + - $ref: "#/components/schemas/Describable" + - type: object + required: + - status + properties: + status: + type: string + enum: + - active + - archived From 222276227fdba49ddbc964b7fc99bf4e974b4ec8 Mon Sep 17 00:00:00 2001 From: jsklan Date: Thu, 9 Apr 2026 20:52:04 -0400 Subject: [PATCH 02/29] chore(internal): add allOf debug pipeline script and IR snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add debug-allof-pipeline.ts for running allof/allof-inline fixtures through the full CLI pipeline (openapi-ir → write-definition → ir). Include generated IR snapshots confirming remaining allOf bugs. --- .../test-definitions/allof-inline.json | 2057 +++++ .../__test__/test-definitions/allof.json | 2059 +++++ .../test-definitions/allof-inline.json | 6743 ++++++++++++++++ .../ir/__test__/test-definitions/allof.json | 6758 +++++++++++++++++ scripts/debug-allof-pipeline.ts | 80 + 5 files changed, 17697 insertions(+) create mode 100644 packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json create mode 100644 packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json create mode 100644 packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json create mode 100644 packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json create mode 100644 scripts/debug-allof-pipeline.ts diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json new file mode 100644 index 000000000000..e57b3935c3d8 --- /dev/null +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json @@ -0,0 +1,2057 @@ +{ + "version": "1.0.0", + "types": { + "type_:PaginatedResult": { + "type": "object", + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "list", + "value": { + "type": "unknown" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:PagingCursors": { + "type": "object", + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleExecutionContext": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" + } + } + }, + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } + }, + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ] + }, + "type_:AuditInfo": { + "type": "object", + "declaration": { + "name": { + "originalName": "AuditInfo", + "camelCase": { + "unsafeName": "auditInfo", + "safeName": "auditInfo" + }, + "snakeCase": { + "unsafeName": "audit_info", + "safeName": "audit_info" + }, + "screamingSnakeCase": { + "unsafeName": "AUDIT_INFO", + "safeName": "AUDIT_INFO" + }, + "pascalCase": { + "unsafeName": "AuditInfo", + "safeName": "AuditInfo" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleType": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleType", + "camelCase": { + "unsafeName": "ruleType", + "safeName": "ruleType" + }, + "snakeCase": { + "unsafeName": "rule_type", + "safeName": "rule_type" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE", + "safeName": "RULE_TYPE" + }, + "pascalCase": { + "unsafeName": "RuleType", + "safeName": "RuleType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleTypeSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleTypeSearchResponse", + "camelCase": { + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:RuleType" + } + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:User": { + "type": "object", + "declaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "email", + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:UserSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "UserSearchResponse", + "camelCase": { + "unsafeName": "userSearchResponse", + "safeName": "userSearchResponse" + }, + "snakeCase": { + "unsafeName": "user_search_response", + "safeName": "user_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "USER_SEARCH_RESPONSE", + "safeName": "USER_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "UserSearchResponse", + "safeName": "UserSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:User" + } + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleResponseStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleResponseStatus", + "camelCase": { + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + }, + { + "wireValue": "draft", + "name": { + "originalName": "draft", + "camelCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "snakeCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "screamingSnakeCase": { + "unsafeName": "DRAFT", + "safeName": "DRAFT" + }, + "pascalCase": { + "unsafeName": "Draft", + "safeName": "Draft" + } + } + } + ] + }, + "type_:RuleResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleResponse", + "camelCase": { + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" + }, + "snakeCase": { + "unsafeName": "rule_response", + "safeName": "rule_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleResponseStatus" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:RuleExecutionContext" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Identifiable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Identifiable", + "camelCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "snakeCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "screamingSnakeCase": { + "unsafeName": "IDENTIFIABLE", + "safeName": "IDENTIFIABLE" + }, + "pascalCase": { + "unsafeName": "Identifiable", + "safeName": "Identifiable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Describable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Describable", + "camelCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "snakeCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIBABLE", + "safeName": "DESCRIBABLE" + }, + "pascalCase": { + "unsafeName": "Describable", + "safeName": "Describable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:CombinedEntityStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "CombinedEntityStatus", + "camelCase": { + "unsafeName": "combinedEntityStatus", + "safeName": "combinedEntityStatus" + }, + "snakeCase": { + "unsafeName": "combined_entity_status", + "safeName": "combined_entity_status" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY_STATUS", + "safeName": "COMBINED_ENTITY_STATUS" + }, + "pascalCase": { + "unsafeName": "CombinedEntityStatus", + "safeName": "CombinedEntityStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "archived", + "name": { + "originalName": "archived", + "camelCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "snakeCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "screamingSnakeCase": { + "unsafeName": "ARCHIVED", + "safeName": "ARCHIVED" + }, + "pascalCase": { + "unsafeName": "Archived", + "safeName": "Archived" + } + } + } + ] + }, + "type_:CombinedEntity": { + "type": "object", + "declaration": { + "name": { + "originalName": "CombinedEntity", + "camelCase": { + "unsafeName": "combinedEntity", + "safeName": "combinedEntity" + }, + "snakeCase": { + "unsafeName": "combined_entity", + "safeName": "combined_entity" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY", + "safeName": "COMBINED_ENTITY" + }, + "pascalCase": { + "unsafeName": "CombinedEntity", + "safeName": "CombinedEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:CombinedEntityStatus" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + } + }, + "headers": [], + "endpoints": { + "endpoint_.searchRuleTypes": { + "auth": null, + "declaration": { + "name": { + "originalName": "searchRuleTypes", + "camelCase": { + "unsafeName": "searchRuleTypes", + "safeName": "searchRuleTypes" + }, + "snakeCase": { + "unsafeName": "search_rule_types", + "safeName": "search_rule_types" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES", + "safeName": "SEARCH_RULE_TYPES" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypes", + "safeName": "SearchRuleTypes" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/rule-types" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "SearchRuleTypesRequest", + "camelCase": { + "unsafeName": "searchRuleTypesRequest", + "safeName": "searchRuleTypesRequest" + }, + "snakeCase": { + "unsafeName": "search_rule_types_request", + "safeName": "search_rule_types_request" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES_REQUEST", + "safeName": "SEARCH_RULE_TYPES_REQUEST" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypesRequest", + "safeName": "SearchRuleTypesRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [ + { + "name": { + "wireValue": "query", + "name": { + "originalName": "query", + "camelCase": { + "unsafeName": "query", + "safeName": "query" + }, + "snakeCase": { + "unsafeName": "query", + "safeName": "query" + }, + "screamingSnakeCase": { + "unsafeName": "QUERY", + "safeName": "QUERY" + }, + "pascalCase": { + "unsafeName": "Query", + "safeName": "Query" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "headers": [], + "body": null, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.createRule": { + "auth": null, + "declaration": { + "name": { + "originalName": "createRule", + "camelCase": { + "unsafeName": "createRule", + "safeName": "createRule" + }, + "snakeCase": { + "unsafeName": "create_rule", + "safeName": "create_rule" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RULE", + "safeName": "CREATE_RULE" + }, + "pascalCase": { + "unsafeName": "CreateRule", + "safeName": "CreateRule" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/rules" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "RuleCreateRequest", + "camelCase": { + "unsafeName": "ruleCreateRequest", + "safeName": "ruleCreateRequest" + }, + "snakeCase": { + "unsafeName": "rule_create_request", + "safeName": "rule_create_request" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_CREATE_REQUEST", + "safeName": "RULE_CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "RuleCreateRequest", + "safeName": "RuleCreateRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [], + "headers": [], + "body": { + "type": "properties", + "value": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleExecutionContext" + }, + "propertyAccess": null, + "variable": null + } + ] + }, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.listUsers": { + "auth": null, + "declaration": { + "name": { + "originalName": "listUsers", + "camelCase": { + "unsafeName": "listUsers", + "safeName": "listUsers" + }, + "snakeCase": { + "unsafeName": "list_users", + "safeName": "list_users" + }, + "screamingSnakeCase": { + "unsafeName": "LIST_USERS", + "safeName": "LIST_USERS" + }, + "pascalCase": { + "unsafeName": "ListUsers", + "safeName": "ListUsers" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/users" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.getEntity": { + "auth": null, + "declaration": { + "name": { + "originalName": "getEntity", + "camelCase": { + "unsafeName": "getEntity", + "safeName": "getEntity" + }, + "snakeCase": { + "unsafeName": "get_entity", + "safeName": "get_entity" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ENTITY", + "safeName": "GET_ENTITY" + }, + "pascalCase": { + "unsafeName": "GetEntity", + "safeName": "GetEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/entities" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + } + }, + "pathParameters": [], + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": { + "originalName": "Default", + "camelCase": { + "unsafeName": "default", + "safeName": "default" + }, + "snakeCase": { + "unsafeName": "default", + "safeName": "default" + }, + "screamingSnakeCase": { + "unsafeName": "DEFAULT", + "safeName": "DEFAULT" + }, + "pascalCase": { + "unsafeName": "Default", + "safeName": "Default" + } + }, + "url": "https://api.example.com", + "docs": null + } + ] + } + }, + "variables": null, + "generatorConfig": null +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json new file mode 100644 index 000000000000..02f579045a52 --- /dev/null +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json @@ -0,0 +1,2059 @@ +{ + "version": "1.0.0", + "types": { + "type_:PaginatedResult": { + "type": "object", + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "list", + "value": { + "type": "unknown" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:PagingCursors": { + "type": "object", + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleExecutionContext": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" + } + } + }, + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } + }, + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ] + }, + "type_:AuditInfo": { + "type": "object", + "declaration": { + "name": { + "originalName": "AuditInfo", + "camelCase": { + "unsafeName": "auditInfo", + "safeName": "auditInfo" + }, + "snakeCase": { + "unsafeName": "audit_info", + "safeName": "audit_info" + }, + "screamingSnakeCase": { + "unsafeName": "AUDIT_INFO", + "safeName": "AUDIT_INFO" + }, + "pascalCase": { + "unsafeName": "AuditInfo", + "safeName": "AuditInfo" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleType": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleType", + "camelCase": { + "unsafeName": "ruleType", + "safeName": "ruleType" + }, + "snakeCase": { + "unsafeName": "rule_type", + "safeName": "rule_type" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE", + "safeName": "RULE_TYPE" + }, + "pascalCase": { + "unsafeName": "RuleType", + "safeName": "RuleType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleTypeSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleTypeSearchResponse", + "camelCase": { + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:RuleType" + } + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:User": { + "type": "object", + "declaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "email", + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:UserSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "UserSearchResponse", + "camelCase": { + "unsafeName": "userSearchResponse", + "safeName": "userSearchResponse" + }, + "snakeCase": { + "unsafeName": "user_search_response", + "safeName": "user_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "USER_SEARCH_RESPONSE", + "safeName": "USER_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "UserSearchResponse", + "safeName": "UserSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:User" + } + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleResponseStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleResponseStatus", + "camelCase": { + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + }, + { + "wireValue": "draft", + "name": { + "originalName": "draft", + "camelCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "snakeCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "screamingSnakeCase": { + "unsafeName": "DRAFT", + "safeName": "DRAFT" + }, + "pascalCase": { + "unsafeName": "Draft", + "safeName": "Draft" + } + } + } + ] + }, + "type_:RuleResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleResponse", + "camelCase": { + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" + }, + "snakeCase": { + "unsafeName": "rule_response", + "safeName": "rule_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleResponseStatus" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:RuleExecutionContext" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": [ + "type_:AuditInfo" + ], + "additionalProperties": false + }, + "type_:Identifiable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Identifiable", + "camelCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "snakeCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "screamingSnakeCase": { + "unsafeName": "IDENTIFIABLE", + "safeName": "IDENTIFIABLE" + }, + "pascalCase": { + "unsafeName": "Identifiable", + "safeName": "Identifiable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Describable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Describable", + "camelCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "snakeCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIBABLE", + "safeName": "DESCRIBABLE" + }, + "pascalCase": { + "unsafeName": "Describable", + "safeName": "Describable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:CombinedEntityStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "CombinedEntityStatus", + "camelCase": { + "unsafeName": "combinedEntityStatus", + "safeName": "combinedEntityStatus" + }, + "snakeCase": { + "unsafeName": "combined_entity_status", + "safeName": "combined_entity_status" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY_STATUS", + "safeName": "COMBINED_ENTITY_STATUS" + }, + "pascalCase": { + "unsafeName": "CombinedEntityStatus", + "safeName": "CombinedEntityStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "archived", + "name": { + "originalName": "archived", + "camelCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "snakeCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "screamingSnakeCase": { + "unsafeName": "ARCHIVED", + "safeName": "ARCHIVED" + }, + "pascalCase": { + "unsafeName": "Archived", + "safeName": "Archived" + } + } + } + ] + }, + "type_:CombinedEntity": { + "type": "object", + "declaration": { + "name": { + "originalName": "CombinedEntity", + "camelCase": { + "unsafeName": "combinedEntity", + "safeName": "combinedEntity" + }, + "snakeCase": { + "unsafeName": "combined_entity", + "safeName": "combined_entity" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY", + "safeName": "COMBINED_ENTITY" + }, + "pascalCase": { + "unsafeName": "CombinedEntity", + "safeName": "CombinedEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:CombinedEntityStatus" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + } + }, + "headers": [], + "endpoints": { + "endpoint_.searchRuleTypes": { + "auth": null, + "declaration": { + "name": { + "originalName": "searchRuleTypes", + "camelCase": { + "unsafeName": "searchRuleTypes", + "safeName": "searchRuleTypes" + }, + "snakeCase": { + "unsafeName": "search_rule_types", + "safeName": "search_rule_types" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES", + "safeName": "SEARCH_RULE_TYPES" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypes", + "safeName": "SearchRuleTypes" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/rule-types" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "SearchRuleTypesRequest", + "camelCase": { + "unsafeName": "searchRuleTypesRequest", + "safeName": "searchRuleTypesRequest" + }, + "snakeCase": { + "unsafeName": "search_rule_types_request", + "safeName": "search_rule_types_request" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES_REQUEST", + "safeName": "SEARCH_RULE_TYPES_REQUEST" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypesRequest", + "safeName": "SearchRuleTypesRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [ + { + "name": { + "wireValue": "query", + "name": { + "originalName": "query", + "camelCase": { + "unsafeName": "query", + "safeName": "query" + }, + "snakeCase": { + "unsafeName": "query", + "safeName": "query" + }, + "screamingSnakeCase": { + "unsafeName": "QUERY", + "safeName": "QUERY" + }, + "pascalCase": { + "unsafeName": "Query", + "safeName": "Query" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "headers": [], + "body": null, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.createRule": { + "auth": null, + "declaration": { + "name": { + "originalName": "createRule", + "camelCase": { + "unsafeName": "createRule", + "safeName": "createRule" + }, + "snakeCase": { + "unsafeName": "create_rule", + "safeName": "create_rule" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RULE", + "safeName": "CREATE_RULE" + }, + "pascalCase": { + "unsafeName": "CreateRule", + "safeName": "CreateRule" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/rules" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "RuleCreateRequest", + "camelCase": { + "unsafeName": "ruleCreateRequest", + "safeName": "ruleCreateRequest" + }, + "snakeCase": { + "unsafeName": "rule_create_request", + "safeName": "rule_create_request" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_CREATE_REQUEST", + "safeName": "RULE_CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "RuleCreateRequest", + "safeName": "RuleCreateRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [], + "headers": [], + "body": { + "type": "properties", + "value": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleExecutionContext" + }, + "propertyAccess": null, + "variable": null + } + ] + }, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.listUsers": { + "auth": null, + "declaration": { + "name": { + "originalName": "listUsers", + "camelCase": { + "unsafeName": "listUsers", + "safeName": "listUsers" + }, + "snakeCase": { + "unsafeName": "list_users", + "safeName": "list_users" + }, + "screamingSnakeCase": { + "unsafeName": "LIST_USERS", + "safeName": "LIST_USERS" + }, + "pascalCase": { + "unsafeName": "ListUsers", + "safeName": "ListUsers" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/users" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.getEntity": { + "auth": null, + "declaration": { + "name": { + "originalName": "getEntity", + "camelCase": { + "unsafeName": "getEntity", + "safeName": "getEntity" + }, + "snakeCase": { + "unsafeName": "get_entity", + "safeName": "get_entity" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ENTITY", + "safeName": "GET_ENTITY" + }, + "pascalCase": { + "unsafeName": "GetEntity", + "safeName": "GetEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/entities" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + } + }, + "pathParameters": [], + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": { + "originalName": "Default", + "camelCase": { + "unsafeName": "default", + "safeName": "default" + }, + "snakeCase": { + "unsafeName": "default", + "safeName": "default" + }, + "screamingSnakeCase": { + "unsafeName": "DEFAULT", + "safeName": "DEFAULT" + }, + "pascalCase": { + "unsafeName": "Default", + "safeName": "Default" + } + }, + "url": "https://api.example.com", + "docs": null + } + ] + } + }, + "variables": null, + "generatorConfig": null +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json new file mode 100644 index 000000000000..2acecdd505c6 --- /dev/null +++ b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json @@ -0,0 +1,6743 @@ +{ + "selfHosted": false, + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": "api", + "apiDisplayName": "allOf Composition", + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_:PaginatedResult": { + "inline": null, + "name": { + "name": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PaginatedResult" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "paging", + "valueType": { + "_type": "named", + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "results", + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "unknown" + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Current page of results from the requested resource." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:PagingCursors" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:PagingCursors": { + "inline": null, + "name": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "next", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Cursor for the next page of results." + }, + { + "name": "previous", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Cursor for the previous page of results." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleExecutionContext": { + "inline": null, + "name": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + }, + "shape": { + "_type": "enum", + "default": null, + "values": [ + { + "name": "prod", + "availability": null, + "docs": null + }, + { + "name": "staging", + "availability": null, + "docs": null + }, + { + "name": "dev", + "availability": null, + "docs": null + } + ], + "forwardCompatible": null + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": "Execution environment for a rule." + }, + "type_:AuditInfo": { + "inline": null, + "name": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "createdBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who created this resource." + }, + { + "name": "createdDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was created." + }, + { + "name": "modifiedBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who last modified this resource." + }, + { + "name": "modifiedDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was last modified." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": "Common audit metadata." + }, + "type_:RuleType": { + "inline": null, + "name": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "description", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleTypeSearchResponse": { + "inline": null, + "name": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "paging", + "valueType": { + "_type": "named", + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "results", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Current page of results from the requested resource." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:PagingCursors", + "type_:RuleType" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:User": { + "inline": null, + "name": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "email", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": { + "format": "email", + "pattern": null, + "minLength": null, + "maxLength": null + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:UserSearchResponse": { + "inline": null, + "name": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "paging", + "valueType": { + "_type": "named", + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "results", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Current page of results from the requested resource." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:PagingCursors", + "type_:User" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleResponseStatus": { + "inline": true, + "name": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus" + }, + "shape": { + "_type": "enum", + "default": null, + "values": [ + { + "name": "active", + "availability": null, + "docs": null + }, + { + "name": "inactive", + "availability": null, + "docs": null + }, + { + "name": "draft", + "availability": null, + "docs": null + } + ], + "forwardCompatible": null + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleResponse": { + "inline": null, + "name": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "createdBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who created this resource." + }, + { + "name": "createdDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was created." + }, + { + "name": "modifiedBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who last modified this resource." + }, + { + "name": "modifiedDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was last modified." + }, + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "status", + "valueType": { + "_type": "named", + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "executionContext", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:RuleResponseStatus", + "type_:RuleExecutionContext" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:Identifiable": { + "inline": null, + "name": { + "name": "Identifiable", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Identifiable" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Unique identifier." + }, + { + "name": "name", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Display name from Identifiable." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:Describable": { + "inline": null, + "name": { + "name": "Describable", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Describable" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "name", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Display name from Describable." + }, + { + "name": "summary", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "A short summary." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:CombinedEntityStatus": { + "inline": true, + "name": { + "name": "CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntityStatus" + }, + "shape": { + "_type": "enum", + "default": null, + "values": [ + { + "name": "active", + "availability": null, + "docs": null + }, + { + "name": "archived", + "availability": null, + "docs": null + } + ], + "forwardCompatible": null + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:CombinedEntity": { + "inline": null, + "name": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Unique identifier." + }, + { + "name": "name", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Display name from Describable." + }, + { + "name": "summary", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "A short summary." + }, + { + "name": "status", + "valueType": { + "_type": "named", + "name": "CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntityStatus", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:CombinedEntityStatus" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "displayName": null, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {}, + "proto": null + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_.searchRuleTypes", + "name": "searchRuleTypes", + "displayName": "Search rule types with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/rule-types", + "parts": [] + }, + "fullPath": { + "head": "rule-types", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [ + { + "name": "query", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "allowMultiple": false, + "clientDefault": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "explode": null, + "availability": null, + "docs": null + } + ], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "SearchRuleTypesRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of rule types", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of rule types" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "b6434d4c", + "name": null, + "url": "/rule-types", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "previous", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "results", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "description", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "b2c39ee3", + "url": "/rule-types", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "propertyAccess": null + }, + { + "name": "previous", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "propertyAccess": null + }, + { + "name": "results", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + }, + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.createRule", + "name": "createRule", + "displayName": "Create a rule with constrained execution context", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/rules", + "parts": [] + }, + "fullPath": { + "head": "rules", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "inlinedRequestBody", + "name": "RuleCreateRequest", + "extends": [], + "properties": [ + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + }, + { + "name": "executionContext", + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [], + "docs": null, + "v2Examples": null, + "contentType": "application/json" + }, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "RuleCreateRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse", + "default": null, + "inline": null + }, + "docs": "Created rule", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Created rule" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "c1cf878e", + "name": null, + "url": "/rules", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": null + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "createdBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "status", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponseStatus", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "active" + } + }, + "jsonExample": "active" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z", + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "e09bf9ea", + "url": "/rules", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + } + }, + { + "name": "executionContext", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + } + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "createdBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "status", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "active" + }, + "typeName": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus" + } + }, + "jsonExample": "active" + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + } + }, + "jsonExample": { + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z", + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.listUsers", + "name": "listUsers", + "displayName": "List users with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/users", + "parts": [] + }, + "fullPath": { + "head": "users", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of users", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of users" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "55942cbc", + "name": null, + "url": "/users", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "UserSearchResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "previous", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "UserSearchResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "results", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "User", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "User", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "email", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "originalTypeDeclaration": { + "typeId": "type_:User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "User", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "id": "id", + "email": "email" + } + } + ], + "itemType": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + } + ] + }, + "originalTypeDeclaration": { + "typeId": "type_:UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "UserSearchResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "email": "email" + } + ] + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "e7c56b0b", + "url": "/users", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "originalTypeDeclaration": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "propertyAccess": null + }, + { + "name": "previous", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "propertyAccess": null + }, + { + "name": "results", + "originalTypeDeclaration": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "email", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + } + }, + "jsonExample": { + "id": "id", + "email": "email" + } + }, + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "email", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + } + }, + "jsonExample": { + "id": "id", + "email": "email" + } + } + ], + "itemType": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ] + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ] + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.getEntity", + "name": "getEntity", + "displayName": "Get an entity that combines multiple parents", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/entities", + "parts": [] + }, + "fullPath": { + "head": "entities", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity", + "default": null, + "inline": null + }, + "docs": "An entity with properties from multiple parents", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "An entity with properties from multiple parents" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "b2b07150", + "name": null, + "url": "/entities", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "summary", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "summary" + } + } + }, + "jsonExample": "summary" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "summary" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "status", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntityStatus", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "active" + } + }, + "jsonExample": "active" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "summary": "summary", + "status": "active" + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "54665e53", + "url": "/entities", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "summary", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "summary" + } + } + }, + "jsonExample": "summary" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "summary" + }, + "propertyAccess": null + }, + { + "name": "status", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "active" + }, + "typeName": { + "name": "CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntityStatus" + } + }, + "jsonExample": "active" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "summary": "summary", + "status": "active" + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + } + ], + "audiences": null + } + }, + "constants": { + "errorInstanceIdKey": "errorInstanceId" + }, + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": "Default", + "url": "https://api.example.com", + "audiences": null, + "defaultUrl": null, + "urlTemplate": null, + "urlVariables": null, + "docs": null + } + ] + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_": [ + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:RuleType", + "type_:RuleTypeSearchResponse", + "type_:User", + "type_:UserSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse", + "type_:CombinedEntityStatus", + "type_:CombinedEntity" + ] + }, + "sharedTypes": [ + "type_:PaginatedResult", + "type_:AuditInfo", + "type_:Identifiable", + "type_:Describable" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "publishConfig": null, + "dynamic": { + "version": "1.0.0", + "types": { + "type_:PaginatedResult": { + "type": "object", + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "list", + "value": { + "type": "unknown" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:PagingCursors": { + "type": "object", + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleExecutionContext": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" + } + } + }, + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } + }, + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ] + }, + "type_:AuditInfo": { + "type": "object", + "declaration": { + "name": { + "originalName": "AuditInfo", + "camelCase": { + "unsafeName": "auditInfo", + "safeName": "auditInfo" + }, + "snakeCase": { + "unsafeName": "audit_info", + "safeName": "audit_info" + }, + "screamingSnakeCase": { + "unsafeName": "AUDIT_INFO", + "safeName": "AUDIT_INFO" + }, + "pascalCase": { + "unsafeName": "AuditInfo", + "safeName": "AuditInfo" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleType": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleType", + "camelCase": { + "unsafeName": "ruleType", + "safeName": "ruleType" + }, + "snakeCase": { + "unsafeName": "rule_type", + "safeName": "rule_type" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE", + "safeName": "RULE_TYPE" + }, + "pascalCase": { + "unsafeName": "RuleType", + "safeName": "RuleType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleTypeSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleTypeSearchResponse", + "camelCase": { + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:RuleType" + } + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:User": { + "type": "object", + "declaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "email", + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:UserSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "UserSearchResponse", + "camelCase": { + "unsafeName": "userSearchResponse", + "safeName": "userSearchResponse" + }, + "snakeCase": { + "unsafeName": "user_search_response", + "safeName": "user_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "USER_SEARCH_RESPONSE", + "safeName": "USER_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "UserSearchResponse", + "safeName": "UserSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:User" + } + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleResponseStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleResponseStatus", + "camelCase": { + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + }, + { + "wireValue": "draft", + "name": { + "originalName": "draft", + "camelCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "snakeCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "screamingSnakeCase": { + "unsafeName": "DRAFT", + "safeName": "DRAFT" + }, + "pascalCase": { + "unsafeName": "Draft", + "safeName": "Draft" + } + } + } + ] + }, + "type_:RuleResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleResponse", + "camelCase": { + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" + }, + "snakeCase": { + "unsafeName": "rule_response", + "safeName": "rule_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleResponseStatus" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:RuleExecutionContext" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Identifiable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Identifiable", + "camelCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "snakeCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "screamingSnakeCase": { + "unsafeName": "IDENTIFIABLE", + "safeName": "IDENTIFIABLE" + }, + "pascalCase": { + "unsafeName": "Identifiable", + "safeName": "Identifiable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Describable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Describable", + "camelCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "snakeCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIBABLE", + "safeName": "DESCRIBABLE" + }, + "pascalCase": { + "unsafeName": "Describable", + "safeName": "Describable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:CombinedEntityStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "CombinedEntityStatus", + "camelCase": { + "unsafeName": "combinedEntityStatus", + "safeName": "combinedEntityStatus" + }, + "snakeCase": { + "unsafeName": "combined_entity_status", + "safeName": "combined_entity_status" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY_STATUS", + "safeName": "COMBINED_ENTITY_STATUS" + }, + "pascalCase": { + "unsafeName": "CombinedEntityStatus", + "safeName": "CombinedEntityStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "archived", + "name": { + "originalName": "archived", + "camelCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "snakeCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "screamingSnakeCase": { + "unsafeName": "ARCHIVED", + "safeName": "ARCHIVED" + }, + "pascalCase": { + "unsafeName": "Archived", + "safeName": "Archived" + } + } + } + ] + }, + "type_:CombinedEntity": { + "type": "object", + "declaration": { + "name": { + "originalName": "CombinedEntity", + "camelCase": { + "unsafeName": "combinedEntity", + "safeName": "combinedEntity" + }, + "snakeCase": { + "unsafeName": "combined_entity", + "safeName": "combined_entity" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY", + "safeName": "COMBINED_ENTITY" + }, + "pascalCase": { + "unsafeName": "CombinedEntity", + "safeName": "CombinedEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:CombinedEntityStatus" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + } + }, + "headers": [], + "endpoints": { + "endpoint_.searchRuleTypes": { + "auth": null, + "declaration": { + "name": { + "originalName": "searchRuleTypes", + "camelCase": { + "unsafeName": "searchRuleTypes", + "safeName": "searchRuleTypes" + }, + "snakeCase": { + "unsafeName": "search_rule_types", + "safeName": "search_rule_types" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES", + "safeName": "SEARCH_RULE_TYPES" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypes", + "safeName": "SearchRuleTypes" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/rule-types" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "SearchRuleTypesRequest", + "camelCase": { + "unsafeName": "searchRuleTypesRequest", + "safeName": "searchRuleTypesRequest" + }, + "snakeCase": { + "unsafeName": "search_rule_types_request", + "safeName": "search_rule_types_request" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES_REQUEST", + "safeName": "SEARCH_RULE_TYPES_REQUEST" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypesRequest", + "safeName": "SearchRuleTypesRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [ + { + "name": { + "wireValue": "query", + "name": { + "originalName": "query", + "camelCase": { + "unsafeName": "query", + "safeName": "query" + }, + "snakeCase": { + "unsafeName": "query", + "safeName": "query" + }, + "screamingSnakeCase": { + "unsafeName": "QUERY", + "safeName": "QUERY" + }, + "pascalCase": { + "unsafeName": "Query", + "safeName": "Query" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "headers": [], + "body": null, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.createRule": { + "auth": null, + "declaration": { + "name": { + "originalName": "createRule", + "camelCase": { + "unsafeName": "createRule", + "safeName": "createRule" + }, + "snakeCase": { + "unsafeName": "create_rule", + "safeName": "create_rule" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RULE", + "safeName": "CREATE_RULE" + }, + "pascalCase": { + "unsafeName": "CreateRule", + "safeName": "CreateRule" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/rules" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "RuleCreateRequest", + "camelCase": { + "unsafeName": "ruleCreateRequest", + "safeName": "ruleCreateRequest" + }, + "snakeCase": { + "unsafeName": "rule_create_request", + "safeName": "rule_create_request" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_CREATE_REQUEST", + "safeName": "RULE_CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "RuleCreateRequest", + "safeName": "RuleCreateRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [], + "headers": [], + "body": { + "type": "properties", + "value": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleExecutionContext" + }, + "propertyAccess": null, + "variable": null + } + ] + }, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.listUsers": { + "auth": null, + "declaration": { + "name": { + "originalName": "listUsers", + "camelCase": { + "unsafeName": "listUsers", + "safeName": "listUsers" + }, + "snakeCase": { + "unsafeName": "list_users", + "safeName": "list_users" + }, + "screamingSnakeCase": { + "unsafeName": "LIST_USERS", + "safeName": "LIST_USERS" + }, + "pascalCase": { + "unsafeName": "ListUsers", + "safeName": "ListUsers" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/users" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.getEntity": { + "auth": null, + "declaration": { + "name": { + "originalName": "getEntity", + "camelCase": { + "unsafeName": "getEntity", + "safeName": "getEntity" + }, + "snakeCase": { + "unsafeName": "get_entity", + "safeName": "get_entity" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ENTITY", + "safeName": "GET_ENTITY" + }, + "pascalCase": { + "unsafeName": "GetEntity", + "safeName": "GetEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/entities" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + } + }, + "pathParameters": [], + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": { + "originalName": "Default", + "camelCase": { + "unsafeName": "default", + "safeName": "default" + }, + "snakeCase": { + "unsafeName": "default", + "safeName": "default" + }, + "screamingSnakeCase": { + "unsafeName": "DEFAULT", + "safeName": "DEFAULT" + }, + "pascalCase": { + "unsafeName": "Default", + "safeName": "Default" + } + }, + "url": "https://api.example.com", + "docs": null + } + ] + } + }, + "variables": null, + "generatorConfig": null + }, + "audiences": null, + "generationMetadata": null, + "apiPlayground": true, + "casingsConfig": { + "generationLanguage": null, + "keywords": null, + "smartCasing": true + }, + "subpackages": {}, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": "service_", + "types": [ + "type_:PaginatedResult", + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:AuditInfo", + "type_:RuleType", + "type_:RuleTypeSearchResponse", + "type_:User", + "type_:UserSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse", + "type_:Identifiable", + "type_:Describable", + "type_:CombinedEntityStatus", + "type_:CombinedEntity" + ], + "errors": [], + "subpackages": [], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json new file mode 100644 index 000000000000..5b073307626a --- /dev/null +++ b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json @@ -0,0 +1,6758 @@ +{ + "selfHosted": false, + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": "api", + "apiDisplayName": "allOf Composition", + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_:PaginatedResult": { + "inline": null, + "name": { + "name": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PaginatedResult" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "paging", + "valueType": { + "_type": "named", + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "results", + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "unknown" + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Current page of results from the requested resource." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:PagingCursors" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:PagingCursors": { + "inline": null, + "name": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "next", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Cursor for the next page of results." + }, + { + "name": "previous", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Cursor for the previous page of results." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleExecutionContext": { + "inline": null, + "name": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + }, + "shape": { + "_type": "enum", + "default": null, + "values": [ + { + "name": "prod", + "availability": null, + "docs": null + }, + { + "name": "staging", + "availability": null, + "docs": null + }, + { + "name": "dev", + "availability": null, + "docs": null + } + ], + "forwardCompatible": null + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": "Execution environment for a rule." + }, + "type_:AuditInfo": { + "inline": null, + "name": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "createdBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who created this resource." + }, + { + "name": "createdDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was created." + }, + { + "name": "modifiedBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who last modified this resource." + }, + { + "name": "modifiedDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was last modified." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": "Common audit metadata." + }, + "type_:RuleType": { + "inline": null, + "name": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "description", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleTypeSearchResponse": { + "inline": null, + "name": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "results", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Current page of results from the requested resource." + }, + { + "name": "paging", + "valueType": { + "_type": "named", + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:RuleType", + "type_:PagingCursors" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:User": { + "inline": null, + "name": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "email", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": { + "format": "email", + "pattern": null, + "minLength": null, + "maxLength": null + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:UserSearchResponse": { + "inline": null, + "name": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "results", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Current page of results from the requested resource." + }, + { + "name": "paging", + "valueType": { + "_type": "named", + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:User", + "type_:PagingCursors" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleResponseStatus": { + "inline": true, + "name": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus" + }, + "shape": { + "_type": "enum", + "default": null, + "values": [ + { + "name": "active", + "availability": null, + "docs": null + }, + { + "name": "inactive", + "availability": null, + "docs": null + }, + { + "name": "draft", + "availability": null, + "docs": null + } + ], + "forwardCompatible": null + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:RuleResponse": { + "inline": null, + "name": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "shape": { + "_type": "object", + "extends": [ + { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + } + ], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "status", + "valueType": { + "_type": "named", + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "executionContext", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [ + { + "name": "createdBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who created this resource." + }, + { + "name": "createdDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was created." + }, + { + "name": "modifiedBy", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "The user who last modified this resource." + }, + { + "name": "modifiedDateTime", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "propertyAccess": "READ_ONLY", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "When this resource was last modified." + } + ] + }, + "referencedTypes": [ + "type_:AuditInfo", + "type_:RuleResponseStatus", + "type_:RuleExecutionContext" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:Identifiable": { + "inline": null, + "name": { + "name": "Identifiable", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Identifiable" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Unique identifier." + }, + { + "name": "name", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Display name from Identifiable." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:Describable": { + "inline": null, + "name": { + "name": "Describable", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Describable" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "name", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Display name from Describable." + }, + { + "name": "summary", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "A short summary." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:CombinedEntityStatus": { + "inline": true, + "name": { + "name": "CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntityStatus" + }, + "shape": { + "_type": "enum", + "default": null, + "values": [ + { + "name": "active", + "availability": null, + "docs": null + }, + { + "name": "archived", + "availability": null, + "docs": null + } + ], + "forwardCompatible": null + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:CombinedEntity": { + "inline": null, + "name": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "status", + "valueType": { + "_type": "named", + "name": "CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntityStatus", + "default": null, + "inline": null + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Unique identifier." + }, + { + "name": "name", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Display name from Identifiable." + }, + { + "name": "summary", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "A short summary." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:CombinedEntityStatus" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "displayName": null, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {}, + "proto": null + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_.searchRuleTypes", + "name": "searchRuleTypes", + "displayName": "Search rule types with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/rule-types", + "parts": [] + }, + "fullPath": { + "head": "rule-types", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [ + { + "name": "query", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "allowMultiple": false, + "clientDefault": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "explode": null, + "availability": null, + "docs": null + } + ], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "SearchRuleTypesRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of rule types", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of rule types" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "b6434d4c", + "name": null, + "url": "/rule-types", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "previous", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "results", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "description", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "fa817ef7", + "url": "/rule-types", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "results", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + }, + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "propertyAccess": null + }, + { + "name": "paging", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "propertyAccess": null + }, + { + "name": "previous", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + } + }, + "jsonExample": { + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ], + "paging": { + "next": "next", + "previous": "previous" + } + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.createRule", + "name": "createRule", + "displayName": "Create a rule with constrained execution context", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/rules", + "parts": [] + }, + "fullPath": { + "head": "rules", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "inlinedRequestBody", + "name": "RuleCreateRequest", + "extends": [], + "properties": [ + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + }, + { + "name": "executionContext", + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [], + "docs": null, + "v2Examples": null, + "contentType": "application/json" + }, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "RuleCreateRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse", + "default": null, + "inline": null + }, + "docs": "Created rule", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Created rule" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "c1cf878e", + "name": null, + "url": "/rules", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": null + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "createdBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "status", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponseStatus", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "active" + } + }, + "jsonExample": "active" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z", + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "4c8a497e", + "url": "/rules", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + } + }, + { + "name": "executionContext", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + } + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "status", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "active" + }, + "typeName": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus" + } + }, + "jsonExample": "active" + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "propertyAccess": null + }, + { + "name": "createdBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod", + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z" + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.listUsers", + "name": "listUsers", + "displayName": "List users with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/users", + "parts": [] + }, + "fullPath": { + "head": "users", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of users", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of users" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "55942cbc", + "name": null, + "url": "/users", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "UserSearchResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "previous", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "UserSearchResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "results", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "User", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "User", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "email", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "originalTypeDeclaration": { + "typeId": "type_:User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "User", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "id": "id", + "email": "email" + } + } + ], + "itemType": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + } + ] + }, + "originalTypeDeclaration": { + "typeId": "type_:UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "UserSearchResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "email": "email" + } + ] + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "53509ec7", + "url": "/users", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "results", + "originalTypeDeclaration": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "email", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + } + }, + "jsonExample": { + "id": "id", + "email": "email" + } + }, + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "email", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + } + }, + "jsonExample": { + "id": "id", + "email": "email" + } + } + ], + "itemType": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ] + }, + "propertyAccess": null + }, + { + "name": "paging", + "originalTypeDeclaration": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "propertyAccess": null + }, + { + "name": "previous", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse" + } + }, + "jsonExample": { + "results": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ], + "paging": { + "next": "next", + "previous": "previous" + } + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.getEntity", + "name": "getEntity", + "displayName": "Get an entity that combines multiple parents", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/entities", + "parts": [] + }, + "fullPath": { + "head": "entities", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity", + "default": null, + "inline": null + }, + "docs": "An entity with properties from multiple parents", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "An entity with properties from multiple parents" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "b2b07150", + "name": null, + "url": "/entities", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "name", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "summary", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "summary" + } + } + }, + "jsonExample": "summary" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "summary" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "status", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntityStatus", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "active" + } + }, + "jsonExample": "active" + }, + "originalTypeDeclaration": { + "typeId": "type_:CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "CombinedEntity", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "name": "name", + "summary": "summary", + "id": "id", + "status": "active" + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "dc0a034f", + "url": "/entities", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "status", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "active" + }, + "typeName": { + "name": "CombinedEntityStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntityStatus" + } + }, + "jsonExample": "active" + }, + "propertyAccess": null + }, + { + "name": "id", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "summary", + "originalTypeDeclaration": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "summary" + } + } + }, + "jsonExample": "summary" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "summary" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "CombinedEntity", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:CombinedEntity" + } + }, + "jsonExample": { + "status": "active", + "id": "id", + "name": "name", + "summary": "summary" + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + } + ], + "audiences": null + } + }, + "constants": { + "errorInstanceIdKey": "errorInstanceId" + }, + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": "Default", + "url": "https://api.example.com", + "audiences": null, + "defaultUrl": null, + "urlTemplate": null, + "urlVariables": null, + "docs": null + } + ] + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_": [ + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:AuditInfo", + "type_:RuleType", + "type_:RuleTypeSearchResponse", + "type_:User", + "type_:UserSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse", + "type_:CombinedEntityStatus", + "type_:CombinedEntity" + ] + }, + "sharedTypes": [ + "type_:PaginatedResult", + "type_:Identifiable", + "type_:Describable" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "publishConfig": null, + "dynamic": { + "version": "1.0.0", + "types": { + "type_:PaginatedResult": { + "type": "object", + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "list", + "value": { + "type": "unknown" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:PagingCursors": { + "type": "object", + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleExecutionContext": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" + } + } + }, + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } + }, + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ] + }, + "type_:AuditInfo": { + "type": "object", + "declaration": { + "name": { + "originalName": "AuditInfo", + "camelCase": { + "unsafeName": "auditInfo", + "safeName": "auditInfo" + }, + "snakeCase": { + "unsafeName": "audit_info", + "safeName": "audit_info" + }, + "screamingSnakeCase": { + "unsafeName": "AUDIT_INFO", + "safeName": "AUDIT_INFO" + }, + "pascalCase": { + "unsafeName": "AuditInfo", + "safeName": "AuditInfo" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleType": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleType", + "camelCase": { + "unsafeName": "ruleType", + "safeName": "ruleType" + }, + "snakeCase": { + "unsafeName": "rule_type", + "safeName": "rule_type" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE", + "safeName": "RULE_TYPE" + }, + "pascalCase": { + "unsafeName": "RuleType", + "safeName": "RuleType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleTypeSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleTypeSearchResponse", + "camelCase": { + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:RuleType" + } + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:User": { + "type": "object", + "declaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "email", + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:UserSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "UserSearchResponse", + "camelCase": { + "unsafeName": "userSearchResponse", + "safeName": "userSearchResponse" + }, + "snakeCase": { + "unsafeName": "user_search_response", + "safeName": "user_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "USER_SEARCH_RESPONSE", + "safeName": "USER_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "UserSearchResponse", + "safeName": "UserSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:User" + } + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleResponseStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleResponseStatus", + "camelCase": { + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + }, + { + "wireValue": "draft", + "name": { + "originalName": "draft", + "camelCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "snakeCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "screamingSnakeCase": { + "unsafeName": "DRAFT", + "safeName": "DRAFT" + }, + "pascalCase": { + "unsafeName": "Draft", + "safeName": "Draft" + } + } + } + ] + }, + "type_:RuleResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleResponse", + "camelCase": { + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" + }, + "snakeCase": { + "unsafeName": "rule_response", + "safeName": "rule_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" + }, + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleResponseStatus" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:RuleExecutionContext" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": [ + "type_:AuditInfo" + ], + "additionalProperties": false + }, + "type_:Identifiable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Identifiable", + "camelCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "snakeCase": { + "unsafeName": "identifiable", + "safeName": "identifiable" + }, + "screamingSnakeCase": { + "unsafeName": "IDENTIFIABLE", + "safeName": "IDENTIFIABLE" + }, + "pascalCase": { + "unsafeName": "Identifiable", + "safeName": "Identifiable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Describable": { + "type": "object", + "declaration": { + "name": { + "originalName": "Describable", + "camelCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "snakeCase": { + "unsafeName": "describable", + "safeName": "describable" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIBABLE", + "safeName": "DESCRIBABLE" + }, + "pascalCase": { + "unsafeName": "Describable", + "safeName": "Describable" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:CombinedEntityStatus": { + "type": "enum", + "declaration": { + "name": { + "originalName": "CombinedEntityStatus", + "camelCase": { + "unsafeName": "combinedEntityStatus", + "safeName": "combinedEntityStatus" + }, + "snakeCase": { + "unsafeName": "combined_entity_status", + "safeName": "combined_entity_status" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY_STATUS", + "safeName": "COMBINED_ENTITY_STATUS" + }, + "pascalCase": { + "unsafeName": "CombinedEntityStatus", + "safeName": "CombinedEntityStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "archived", + "name": { + "originalName": "archived", + "camelCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "snakeCase": { + "unsafeName": "archived", + "safeName": "archived" + }, + "screamingSnakeCase": { + "unsafeName": "ARCHIVED", + "safeName": "ARCHIVED" + }, + "pascalCase": { + "unsafeName": "Archived", + "safeName": "Archived" + } + } + } + ] + }, + "type_:CombinedEntity": { + "type": "object", + "declaration": { + "name": { + "originalName": "CombinedEntity", + "camelCase": { + "unsafeName": "combinedEntity", + "safeName": "combinedEntity" + }, + "snakeCase": { + "unsafeName": "combined_entity", + "safeName": "combined_entity" + }, + "screamingSnakeCase": { + "unsafeName": "COMBINED_ENTITY", + "safeName": "COMBINED_ENTITY" + }, + "pascalCase": { + "unsafeName": "CombinedEntity", + "safeName": "CombinedEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:CombinedEntityStatus" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "summary", + "name": { + "originalName": "summary", + "camelCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "snakeCase": { + "unsafeName": "summary", + "safeName": "summary" + }, + "screamingSnakeCase": { + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" + }, + "pascalCase": { + "unsafeName": "Summary", + "safeName": "Summary" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + } + }, + "headers": [], + "endpoints": { + "endpoint_.searchRuleTypes": { + "auth": null, + "declaration": { + "name": { + "originalName": "searchRuleTypes", + "camelCase": { + "unsafeName": "searchRuleTypes", + "safeName": "searchRuleTypes" + }, + "snakeCase": { + "unsafeName": "search_rule_types", + "safeName": "search_rule_types" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES", + "safeName": "SEARCH_RULE_TYPES" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypes", + "safeName": "SearchRuleTypes" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/rule-types" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "SearchRuleTypesRequest", + "camelCase": { + "unsafeName": "searchRuleTypesRequest", + "safeName": "searchRuleTypesRequest" + }, + "snakeCase": { + "unsafeName": "search_rule_types_request", + "safeName": "search_rule_types_request" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES_REQUEST", + "safeName": "SEARCH_RULE_TYPES_REQUEST" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypesRequest", + "safeName": "SearchRuleTypesRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [ + { + "name": { + "wireValue": "query", + "name": { + "originalName": "query", + "camelCase": { + "unsafeName": "query", + "safeName": "query" + }, + "snakeCase": { + "unsafeName": "query", + "safeName": "query" + }, + "screamingSnakeCase": { + "unsafeName": "QUERY", + "safeName": "QUERY" + }, + "pascalCase": { + "unsafeName": "Query", + "safeName": "Query" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "headers": [], + "body": null, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.createRule": { + "auth": null, + "declaration": { + "name": { + "originalName": "createRule", + "camelCase": { + "unsafeName": "createRule", + "safeName": "createRule" + }, + "snakeCase": { + "unsafeName": "create_rule", + "safeName": "create_rule" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RULE", + "safeName": "CREATE_RULE" + }, + "pascalCase": { + "unsafeName": "CreateRule", + "safeName": "CreateRule" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/rules" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "RuleCreateRequest", + "camelCase": { + "unsafeName": "ruleCreateRequest", + "safeName": "ruleCreateRequest" + }, + "snakeCase": { + "unsafeName": "rule_create_request", + "safeName": "rule_create_request" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_CREATE_REQUEST", + "safeName": "RULE_CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "RuleCreateRequest", + "safeName": "RuleCreateRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [], + "queryParameters": [], + "headers": [], + "body": { + "type": "properties", + "value": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:RuleExecutionContext" + }, + "propertyAccess": null, + "variable": null + } + ] + }, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.listUsers": { + "auth": null, + "declaration": { + "name": { + "originalName": "listUsers", + "camelCase": { + "unsafeName": "listUsers", + "safeName": "listUsers" + }, + "snakeCase": { + "unsafeName": "list_users", + "safeName": "list_users" + }, + "screamingSnakeCase": { + "unsafeName": "LIST_USERS", + "safeName": "LIST_USERS" + }, + "pascalCase": { + "unsafeName": "ListUsers", + "safeName": "ListUsers" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/users" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.getEntity": { + "auth": null, + "declaration": { + "name": { + "originalName": "getEntity", + "camelCase": { + "unsafeName": "getEntity", + "safeName": "getEntity" + }, + "snakeCase": { + "unsafeName": "get_entity", + "safeName": "get_entity" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ENTITY", + "safeName": "GET_ENTITY" + }, + "pascalCase": { + "unsafeName": "GetEntity", + "safeName": "GetEntity" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/entities" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null + } + }, + "pathParameters": [], + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": { + "originalName": "Default", + "camelCase": { + "unsafeName": "default", + "safeName": "default" + }, + "snakeCase": { + "unsafeName": "default", + "safeName": "default" + }, + "screamingSnakeCase": { + "unsafeName": "DEFAULT", + "safeName": "DEFAULT" + }, + "pascalCase": { + "unsafeName": "Default", + "safeName": "Default" + } + }, + "url": "https://api.example.com", + "docs": null + } + ] + } + }, + "variables": null, + "generatorConfig": null + }, + "audiences": null, + "generationMetadata": null, + "apiPlayground": true, + "casingsConfig": { + "generationLanguage": null, + "keywords": null, + "smartCasing": true + }, + "subpackages": {}, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": "service_", + "types": [ + "type_:PaginatedResult", + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:AuditInfo", + "type_:RuleType", + "type_:RuleTypeSearchResponse", + "type_:User", + "type_:UserSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse", + "type_:Identifiable", + "type_:Describable", + "type_:CombinedEntityStatus", + "type_:CombinedEntity" + ], + "errors": [], + "subpackages": [], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/scripts/debug-allof-pipeline.ts b/scripts/debug-allof-pipeline.ts new file mode 100644 index 000000000000..b55bbb51c017 --- /dev/null +++ b/scripts/debug-allof-pipeline.ts @@ -0,0 +1,80 @@ +import { execSync } from "child_process"; +import { cpSync, existsSync, mkdirSync } from "fs"; +import { join, resolve } from "path"; + +const ROOT = resolve(__dirname, ".."); +const TEST_DEFS = join(ROOT, "test-definitions"); +const CLI = join(ROOT, "packages/cli/cli/dist/prod/cli.cjs"); +const RESULTS_BASE = join(ROOT, ".local/results"); + +const APIS = ["allof", "allof-inline"]; + +function fern(args: string): void { + const cmd = `FERN_NO_VERSION_REDIRECTION=true node --enable-source-maps ${CLI} ${args}`; + execSync(cmd, { cwd: TEST_DEFS, stdio: "inherit" }); +} + +function main(): void { + if (!existsSync(CLI)) { + process.stderr.write(`CLI not built. Run: pnpm fern:build\n`); + process.exit(1); + } + + const allResults: Array<{ api: string; name: string; ok: boolean }> = []; + + for (const api of APIS) { + const results = join(RESULTS_BASE, api); + mkdirSync(results, { recursive: true }); + + const steps: Array<{ name: string; run: () => void }> = [ + { + name: "openapi-ir", + run: () => fern(`openapi-ir ${results}/openapi-ir.json --api ${api}`) + }, + { + name: "write-definition", + run: () => { + fern(`write-definition --api ${api}`); + const defSrc = join(TEST_DEFS, `fern/apis/${api}/.definition`); + const defDst = join(results, ".definition"); + if (existsSync(defSrc)) { + cpSync(defSrc, defDst, { recursive: true }); + } else { + process.stderr.write(`Warning: ${defSrc} not found after write-definition\n`); + } + } + }, + { + name: "ir", + run: () => fern(`ir ${results}/ir.json --api ${api}`) + } + ]; + + process.stdout.write(`\n=== ${api} ===\n`); + + for (const step of steps) { + process.stdout.write(`\n--- ${step.name} ---\n`); + try { + step.run(); + process.stdout.write(`OK: ${step.name}\n`); + allResults.push({ api, name: step.name, ok: true }); + } catch { + process.stderr.write(`FAIL: ${step.name}\n`); + allResults.push({ api, name: step.name, ok: false }); + } + } + } + + process.stdout.write(`\n--- summary ---\n`); + for (const r of allResults) { + process.stdout.write(` ${r.ok ? "OK" : "FAIL"}: ${r.api}/${r.name}\n`); + } + process.stdout.write(`Results in ${RESULTS_BASE}/\n`); + + const failed = allResults.filter((r) => !r.ok); + if (failed.length > 0) { + process.exit(1); + } +} + +main(); From 30e1997cc3d7de997656e35e0bb1d5e76e729555 Mon Sep 17 00:00:00 2001 From: jsklan Date: Thu, 9 Apr 2026 21:09:41 -0400 Subject: [PATCH 03/29] test(cli): add assertion-based allOf tests reproducing SPS Commerce bugs Add V3 importer test fixture and assertion tests for the allOf edge cases reported in Pylon #18189 / FER-9158. Tests validate: - Case A: array items narrowing preserves parent required status - Case B: property-level allOf with $ref + inline primitive - Case C: required field preservation through allOf composition 7 of 8 assertions currently fail, confirming the bugs. The task is complete when all 8 pass. --- .../baseline-sdks/allof-spscommerce.json | 1847 ++++++++++++++++ .../v3-sdks/allof-spscommerce.json | 1899 +++++++++++++++++ .../src/__test__/allof-spscommerce.test.ts | 201 ++ .../allof-spscommerce/fern/fern.config.json | 4 + .../allof-spscommerce/fern/generators.yml | 4 + .../fixtures/allof-spscommerce/openapi.yml | 180 ++ 6 files changed, 4135 insertions(+) create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/fern.config.json create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/generators.yml create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/openapi.yml diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json new file mode 100644 index 000000000000..d1e60003cac1 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json @@ -0,0 +1,1847 @@ +{ + "selfHosted": false, + "apiName": "api", + "apiDisplayName": "allOf SPS Commerce Reproduction", + "auth": { + "requirement": "ALL", + "schemes": [] + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_:PagingCursors": { + "name": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PagingCursors" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "next", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "previous", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:PaginatedResult": { + "name": { + "name": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PaginatedResult" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "paging", + "valueType": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PagingCursors", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "docs": "Current page of results from the requested resource.", + "name": "results", + "valueType": { + "container": { + "list": { + "type": "unknown" + }, + "type": "list" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleExecutionContext": { + "docs": "The execution environment for a rule.", + "name": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleExecutionContext" + }, + "shape": { + "values": [ + { + "name": "prod" + }, + { + "name": "staging" + }, + { + "name": "dev" + } + ], + "type": "enum" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleTypeResponseStatus": { + "inline": true, + "name": { + "name": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponseStatus" + }, + "shape": { + "values": [ + { + "name": "active" + }, + { + "name": "inactive" + } + ], + "type": "enum" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleTypeResponse": { + "name": { + "name": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponse" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "description", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "status", + "valueType": { + "name": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponseStatus", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleTypeSearchResponse": { + "name": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeSearchResponse" + }, + "shape": { + "extends": [], + "properties": [ + { + "docs": "Current page of results from the requested resource.", + "name": "results", + "valueType": { + "container": { + "optional": { + "container": { + "list": { + "name": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponse", + "type": "named" + }, + "type": "list" + }, + "type": "container" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "paging", + "valueType": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PagingCursors", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleResponseStatus": { + "inline": true, + "name": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponseStatus" + }, + "shape": { + "values": [ + { + "name": "active" + }, + { + "name": "inactive" + }, + { + "name": "draft" + } + ], + "type": "enum" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleResponse": { + "name": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponse" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "status", + "valueType": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponseStatus", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "executionContext", + "valueType": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleExecutionContext", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + } + }, + "errors": {}, + "services": { + "service_": { + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {} + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_.searchRuleTypes", + "name": "searchRuleTypes", + "displayName": "Search for rule types", + "auth": false, + "idempotent": false, + "method": "GET", + "path": { + "head": "/v1/preview/1/rule-types", + "parts": [] + }, + "fullPath": { + "head": "v1/preview/1/rule-types", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [ + { + "name": "query", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "allowMultiple": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "headers": [], + "sdkRequest": { + "shape": { + "wrapperName": "SearchRuleTypesRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false, + "type": "wrapper" + }, + "requestParameterName": "request" + }, + "response": { + "body": { + "value": { + "docs": "Paginated list of rule types", + "responseBodyType": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeSearchResponse", + "type": "named" + }, + "type": "response" + }, + "type": "json" + }, + "statusCode": 200, + "docs": "Paginated list of rule types" + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [], + "responseHeaders": [] + }, + { + "id": "endpoint_.createRule", + "name": "createRule", + "displayName": "Create a rule", + "auth": false, + "idempotent": false, + "method": "POST", + "path": { + "head": "/v1/preview/1/rules", + "parts": [] + }, + "fullPath": { + "head": "v1/preview/1/rules", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "name": "RuleCreateRequest", + "extends": [], + "contentType": "application/json", + "properties": [ + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "executionContext", + "valueType": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleExecutionContext", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "inlinedRequestBody" + }, + "sdkRequest": { + "shape": { + "wrapperName": "RuleCreateRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false, + "type": "wrapper" + }, + "requestParameterName": "request" + }, + "response": { + "body": { + "value": { + "docs": "Created rule", + "responseBodyType": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponse", + "type": "named" + }, + "type": "response" + }, + "type": "json" + }, + "statusCode": 200, + "docs": "Created rule" + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [], + "responseHeaders": [] + } + ] + } + }, + "constants": { + "errorInstanceIdKey": "errorInstanceId" + }, + "environments": { + "defaultEnvironment": "Default", + "environments": { + "environments": [ + { + "id": "Default", + "name": "Default", + "url": "https://api.spscommerce.com" + } + ], + "type": "singleBaseUrl" + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_": [ + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:RuleTypeResponseStatus", + "type_:RuleTypeResponse", + "type_:RuleTypeSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse" + ] + }, + "sharedTypes": [ + "type_:PaginatedResult" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "dynamic": { + "version": "1.0.0", + "types": { + "type_:PagingCursors": { + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:PaginatedResult": { + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "value": "type_:PagingCursors", + "type": "named" + } + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "value": { + "type": "unknown" + }, + "type": "list" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:RuleExecutionContext": { + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" + } + } + }, + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } + }, + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ], + "type": "enum" + }, + "type_:RuleTypeResponseStatus": { + "declaration": { + "name": { + "originalName": "RuleTypeResponseStatus", + "camelCase": { + "unsafeName": "ruleTypeResponseStatus", + "safeName": "ruleTypeResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_type_response_status", + "safeName": "rule_type_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_RESPONSE_STATUS", + "safeName": "RULE_TYPE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleTypeResponseStatus", + "safeName": "RuleTypeResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + } + ], + "type": "enum" + }, + "type_:RuleTypeResponse": { + "declaration": { + "name": { + "originalName": "RuleTypeResponse", + "camelCase": { + "unsafeName": "ruleTypeResponse", + "safeName": "ruleTypeResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_response", + "safeName": "rule_type_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_RESPONSE", + "safeName": "RULE_TYPE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeResponse", + "safeName": "RuleTypeResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } + } + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "value": "type_:RuleTypeResponseStatus", + "type": "named" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:RuleTypeSearchResponse": { + "declaration": { + "name": { + "originalName": "RuleTypeSearchResponse", + "camelCase": { + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "value": { + "value": { + "value": "type_:RuleTypeResponse", + "type": "named" + }, + "type": "list" + }, + "type": "optional" + } + }, + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "value": "type_:PagingCursors", + "type": "named" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:RuleResponseStatus": { + "declaration": { + "name": { + "originalName": "RuleResponseStatus", + "camelCase": { + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + }, + { + "wireValue": "draft", + "name": { + "originalName": "draft", + "camelCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "snakeCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "screamingSnakeCase": { + "unsafeName": "DRAFT", + "safeName": "DRAFT" + }, + "pascalCase": { + "unsafeName": "Draft", + "safeName": "Draft" + } + } + } + ], + "type": "enum" + }, + "type_:RuleResponse": { + "declaration": { + "name": { + "originalName": "RuleResponse", + "camelCase": { + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" + }, + "snakeCase": { + "unsafeName": "rule_response", + "safeName": "rule_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "value": "type_:RuleResponseStatus", + "type": "named" + } + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "value": "type_:RuleExecutionContext", + "type": "named" + } + } + ], + "additionalProperties": false, + "type": "object" + } + }, + "headers": [], + "endpoints": { + "endpoint_.searchRuleTypes": { + "declaration": { + "name": { + "originalName": "searchRuleTypes", + "camelCase": { + "unsafeName": "searchRuleTypes", + "safeName": "searchRuleTypes" + }, + "snakeCase": { + "unsafeName": "search_rule_types", + "safeName": "search_rule_types" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES", + "safeName": "SEARCH_RULE_TYPES" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypes", + "safeName": "SearchRuleTypes" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "location": { + "method": "GET", + "path": "/v1/preview/1/rule-types" + }, + "request": { + "declaration": { + "name": { + "originalName": "SearchRuleTypesRequest", + "camelCase": { + "unsafeName": "searchRuleTypesRequest", + "safeName": "searchRuleTypesRequest" + }, + "snakeCase": { + "unsafeName": "search_rule_types_request", + "safeName": "search_rule_types_request" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES_REQUEST", + "safeName": "SEARCH_RULE_TYPES_REQUEST" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypesRequest", + "safeName": "SearchRuleTypesRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "pathParameters": [], + "queryParameters": [ + { + "name": { + "wireValue": "query", + "name": { + "originalName": "query", + "camelCase": { + "unsafeName": "query", + "safeName": "query" + }, + "snakeCase": { + "unsafeName": "query", + "safeName": "query" + }, + "screamingSnakeCase": { + "unsafeName": "QUERY", + "safeName": "QUERY" + }, + "pascalCase": { + "unsafeName": "Query", + "safeName": "Query" + } + } + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + } + ], + "headers": [], + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + }, + "type": "inlined" + }, + "response": { + "type": "json" + }, + "examples": [] + }, + "endpoint_.createRule": { + "declaration": { + "name": { + "originalName": "createRule", + "camelCase": { + "unsafeName": "createRule", + "safeName": "createRule" + }, + "snakeCase": { + "unsafeName": "create_rule", + "safeName": "create_rule" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RULE", + "safeName": "CREATE_RULE" + }, + "pascalCase": { + "unsafeName": "CreateRule", + "safeName": "CreateRule" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "location": { + "method": "POST", + "path": "/v1/preview/1/rules" + }, + "request": { + "declaration": { + "name": { + "originalName": "RuleCreateRequest", + "camelCase": { + "unsafeName": "ruleCreateRequest", + "safeName": "ruleCreateRequest" + }, + "snakeCase": { + "unsafeName": "rule_create_request", + "safeName": "rule_create_request" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_CREATE_REQUEST", + "safeName": "RULE_CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "RuleCreateRequest", + "safeName": "RuleCreateRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "pathParameters": [], + "queryParameters": [], + "headers": [], + "body": { + "value": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "value": "type_:RuleExecutionContext", + "type": "named" + } + } + ], + "type": "properties" + }, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + }, + "type": "inlined" + }, + "response": { + "type": "json" + }, + "examples": [] + } + }, + "pathParameters": [], + "environments": { + "defaultEnvironment": "Default", + "environments": { + "environments": [ + { + "id": "Default", + "name": { + "originalName": "Default", + "camelCase": { + "unsafeName": "default", + "safeName": "default" + }, + "snakeCase": { + "unsafeName": "default", + "safeName": "default" + }, + "screamingSnakeCase": { + "unsafeName": "DEFAULT", + "safeName": "DEFAULT" + }, + "pascalCase": { + "unsafeName": "Default", + "safeName": "Default" + } + }, + "url": "https://api.spscommerce.com" + } + ], + "type": "singleBaseUrl" + } + } + }, + "apiPlayground": true, + "casingsConfig": { + "smartCasing": true + }, + "subpackages": {}, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "service": "service_", + "types": [ + "type_:PagingCursors", + "type_:PaginatedResult", + "type_:RuleExecutionContext", + "type_:RuleTypeResponseStatus", + "type_:RuleTypeResponse", + "type_:RuleTypeSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse" + ], + "errors": [], + "subpackages": [], + "hasEndpointsInTree": true + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version" + } + } +} \ No newline at end of file diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json new file mode 100644 index 000000000000..7d2d77bd85e1 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json @@ -0,0 +1,1899 @@ +{ + "auth": { + "requirement": "ALL", + "schemes": [] + }, + "selfHosted": false, + "types": { + "PagingCursors": { + "name": { + "typeId": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PagingCursors" + }, + "shape": { + "properties": [ + { + "name": "next", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "PagingCursorsNext_example_autogenerated": "string" + } + } + }, + { + "name": "previous", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "PagingCursorsPrevious_example_autogenerated": "string" + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "PagingCursors_example_autogenerated": { + "next": "string" + } + } + } + }, + "PaginatedResult": { + "name": { + "typeId": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PaginatedResult" + }, + "shape": { + "properties": [ + { + "name": "paging", + "valueType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PagingCursors", + "typeId": "PagingCursors", + "inline": false, + "displayName": "PagingCursors", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "PaginatedResultPaging_example_autogenerated": { + "next": "string", + "previous": "string" + } + } + } + }, + { + "name": "results", + "valueType": { + "container": { + "list": { + "type": "unknown" + }, + "type": "list" + }, + "type": "container" + }, + "docs": "Current page of results from the requested resource.", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "PaginatedResultResults_example_autogenerated": [ + null + ] + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "PaginatedResult_example_autogenerated": { + "paging": { + "next": "string" + }, + "results": [ + null + ] + } + } + } + }, + "RuleExecutionContext": { + "name": { + "typeId": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleExecutionContext" + }, + "shape": { + "values": [ + { + "name": "prod" + }, + { + "name": "staging" + }, + { + "name": "dev" + } + ], + "type": "enum" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "docs": "The execution environment for a rule.", + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleExecutionContext_example_autogenerated": "prod" + } + } + }, + "RuleTypeResponseStatus": { + "name": { + "typeId": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponseStatus" + }, + "shape": { + "values": [ + { + "name": "active" + }, + { + "name": "inactive" + } + ], + "type": "enum" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeResponseStatus_example_autogenerated": "active" + } + } + }, + "RuleTypeResponse": { + "name": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "shape": { + "properties": [ + { + "name": "id", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeResponseId_example_autogenerated": "string" + } + } + }, + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeResponseName_example_autogenerated": "string" + } + } + }, + { + "name": "description", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeResponseDescription_example_autogenerated": "string" + } + } + }, + { + "name": "status", + "valueType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponseStatus", + "typeId": "RuleTypeResponseStatus", + "inline": false, + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeResponseStatus_example_autogenerated": "active" + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeResponse_example_autogenerated": { + "id": "string", + "name": "string", + "status": "active" + } + } + } + }, + "RuleTypeSearchResponse": { + "name": { + "typeId": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeSearchResponse" + }, + "shape": { + "properties": [ + { + "name": "results", + "valueType": { + "container": { + "optional": { + "container": { + "list": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse", + "typeId": "RuleTypeResponse", + "inline": false, + "displayName": "RuleTypeResponse", + "type": "named" + }, + "type": "list" + }, + "type": "container" + }, + "type": "optional" + }, + "type": "container" + }, + "docs": "Current page of results from the requested resource.", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeSearchResponseResults_example_autogenerated": [ + { + "id": "string", + "name": "string", + "status": "active" + } + ] + } + } + } + ], + "extends": [ + { + "typeId": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PaginatedResult", + "displayName": "PaginatedResult" + } + ], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeSearchResponse_example_autogenerated": { + "paging": { + "next": "string" + }, + "results": [ + null + ] + } + } + } + }, + "RuleCreateRequestExecutionContext": { + "name": { + "typeId": "RuleCreateRequestExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequestExecutionContext" + }, + "shape": { + "properties": [], + "extends": [ + { + "typeId": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleExecutionContext", + "displayName": "RuleExecutionContext" + } + ], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleCreateRequestExecutionContext_example_autogenerated": "prod" + } + } + }, + "RuleCreateRequest": { + "name": { + "typeId": "RuleCreateRequest", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequest" + }, + "shape": { + "properties": [ + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleCreateRequestName_example_autogenerated": "string" + } + } + }, + { + "name": "executionContext", + "valueType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequestExecutionContext", + "typeId": "RuleCreateRequestExecutionContext", + "inline": false, + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleCreateRequestExecutionContext_example_autogenerated": "prod" + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleCreateRequest_example_autogenerated": { + "name": "string", + "executionContext": "prod" + } + } + } + }, + "RuleResponseStatus": { + "name": { + "typeId": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponseStatus" + }, + "shape": { + "values": [ + { + "name": "active" + }, + { + "name": "inactive" + }, + { + "name": "draft" + } + ], + "type": "enum" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleResponseStatus_example_autogenerated": "active" + } + } + }, + "RuleResponse": { + "name": { + "typeId": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse" + }, + "shape": { + "properties": [ + { + "name": "id", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleResponseId_example_autogenerated": "string" + } + } + }, + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleResponseName_example_autogenerated": "string" + } + } + }, + { + "name": "status", + "valueType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponseStatus", + "typeId": "RuleResponseStatus", + "inline": false, + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleResponseStatus_example_autogenerated": "active" + } + } + }, + { + "name": "executionContext", + "valueType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleExecutionContext", + "typeId": "RuleExecutionContext", + "inline": false, + "displayName": "RuleExecutionContext", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleResponseExecutionContext_example_autogenerated": "prod" + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleResponse_example_autogenerated": { + "id": "string", + "name": "string", + "status": "active", + "executionContext": "prod" + } + } + } + } + }, + "services": { + "service_": { + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "endpoints": [ + { + "displayName": "Search for rule types", + "method": "GET", + "baseUrl": "https://api.spscommerce.com", + "path": { + "head": "/v1/preview/1/rule-types", + "parts": [] + }, + "pathParameters": [], + "queryParameters": [ + { + "name": "query", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "allowMultiple": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "query_example": "query" + } + } + } + ], + "headers": [], + "responseHeaders": [], + "errors": [], + "auth": false, + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "id": "4ef66806", + "url": "/v1/preview/1/rule-types", + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "response": { + "value": { + "value": { + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + }, + { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + } + ] + }, + "shape": { + "shape": { + "properties": [ + { + "name": "paging", + "originalTypeDeclaration": { + "typeId": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PaginatedResult" + }, + "value": { + "jsonExample": { + "next": "next", + "previous": "previous" + }, + "shape": { + "shape": { + "properties": [ + { + "name": "next", + "originalTypeDeclaration": { + "typeId": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PagingCursors" + }, + "value": { + "jsonExample": "next", + "shape": { + "primitive": { + "string": { + "original": "next" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "previous", + "originalTypeDeclaration": { + "typeId": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PagingCursors" + }, + "value": { + "jsonExample": "previous", + "shape": { + "container": { + "optional": { + "jsonExample": "previous", + "shape": { + "primitive": { + "string": { + "original": "previous" + }, + "type": "string" + }, + "type": "primitive" + } + }, + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PagingCursors" + }, + "type": "named" + } + } + }, + { + "name": "results", + "originalTypeDeclaration": { + "typeId": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PaginatedResult" + }, + "value": { + "jsonExample": [ + { + "key": "value" + }, + { + "key": "value" + } + ], + "shape": { + "container": { + "list": [ + { + "jsonExample": { + "key": "value" + }, + "shape": { + "unknown": { + "key": "value" + }, + "type": "unknown" + } + }, + { + "jsonExample": { + "key": "value" + }, + "shape": { + "unknown": { + "key": "value" + }, + "type": "unknown" + } + } + ], + "itemType": { + "type": "unknown" + }, + "type": "list" + }, + "type": "container" + } + } + }, + { + "name": "results", + "originalTypeDeclaration": { + "typeId": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeSearchResponse" + }, + "value": { + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + }, + { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + } + ], + "shape": { + "container": { + "optional": { + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + }, + { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + } + ], + "shape": { + "container": { + "list": [ + { + "jsonExample": { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + }, + "shape": { + "shape": { + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "id", + "shape": { + "primitive": { + "string": { + "original": "id" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "name", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "name", + "shape": { + "primitive": { + "string": { + "original": "name" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "description", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "description", + "shape": { + "container": { + "optional": { + "jsonExample": "description", + "shape": { + "primitive": { + "string": { + "original": "description" + }, + "type": "string" + }, + "type": "primitive" + } + }, + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + }, + { + "name": "status", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "active", + "shape": { + "shape": { + "value": "active", + "type": "enum" + }, + "typeName": { + "typeId": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponseStatus" + }, + "type": "named" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "type": "named" + } + }, + { + "jsonExample": { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + }, + "shape": { + "shape": { + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "id", + "shape": { + "primitive": { + "string": { + "original": "id" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "name", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "name", + "shape": { + "primitive": { + "string": { + "original": "name" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "description", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "description", + "shape": { + "container": { + "optional": { + "jsonExample": "description", + "shape": { + "primitive": { + "string": { + "original": "description" + }, + "type": "string" + }, + "type": "primitive" + } + }, + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + } + } + }, + { + "name": "status", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "active", + "shape": { + "shape": { + "value": "active", + "type": "enum" + }, + "typeName": { + "typeId": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponseStatus" + }, + "type": "named" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "type": "named" + } + } + ], + "itemType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse", + "typeId": "RuleTypeResponse", + "inline": false, + "displayName": "RuleTypeResponse", + "type": "named" + }, + "type": "list" + }, + "type": "container" + } + }, + "valueType": { + "container": { + "list": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse", + "typeId": "RuleTypeResponse", + "inline": false, + "displayName": "RuleTypeResponse", + "type": "named" + }, + "type": "list" + }, + "type": "container" + }, + "type": "optional" + }, + "type": "container" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeSearchResponse" + }, + "type": "named" + } + }, + "type": "body" + }, + "type": "ok" + } + } + } + ], + "idempotent": false, + "fullPath": { + "head": "/v1/preview/1/rule-types", + "parts": [] + }, + "allPathParameters": [], + "source": { + "type": "openapi" + }, + "audiences": [], + "id": "endpoint_.searchRuleTypes", + "name": "searchRuleTypes", + "v2RequestBodies": {}, + "response": { + "statusCode": 200, + "body": { + "value": { + "responseBodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeSearchResponse", + "typeId": "RuleTypeSearchResponse", + "inline": false, + "displayName": "RuleTypeSearchResponse", + "type": "named" + }, + "docs": "Paginated list of rule types", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "searchRuleTypesExample": { + "paging": { + "next": "string", + "previous": "string" + }, + "results": null + } + } + }, + "type": "response" + }, + "type": "json" + }, + "docs": "Paginated list of rule types" + }, + "v2Examples": { + "autogeneratedExamples": { + "base_searchRuleTypesExample_200": { + "displayName": "searchRuleTypesExample", + "request": { + "endpoint": { + "method": "GET", + "path": "/v1/preview/1/rule-types" + }, + "environment": "https://api.spscommerce.com", + "pathParameters": {}, + "queryParameters": {}, + "headers": {} + }, + "response": { + "statusCode": 200, + "body": { + "value": { + "paging": { + "next": "string", + "previous": "string" + }, + "results": null + }, + "type": "json" + } + } + } + }, + "userSpecifiedExamples": {} + }, + "v2Responses": { + "responses": [ + { + "statusCode": 200, + "body": { + "value": { + "responseBodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeSearchResponse", + "typeId": "RuleTypeSearchResponse", + "inline": false, + "displayName": "RuleTypeSearchResponse", + "type": "named" + }, + "docs": "Paginated list of rule types", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "searchRuleTypesExample": { + "paging": { + "next": "string", + "previous": "string" + }, + "results": null + } + } + }, + "type": "response" + }, + "type": "json" + }, + "docs": "Paginated list of rule types" + } + ] + } + }, + { + "displayName": "Create a rule", + "method": "POST", + "baseUrl": "https://api.spscommerce.com", + "path": { + "head": "/v1/preview/1/rules", + "parts": [] + }, + "pathParameters": [], + "queryParameters": [], + "headers": [], + "responseHeaders": [], + "errors": [], + "auth": false, + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "id": "f33597bf", + "url": "/v1/preview/1/rules", + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "jsonExample": { + "name": "name", + "executionContext": {} + }, + "shape": { + "shape": { + "properties": [ + { + "name": "name", + "originalTypeDeclaration": { + "typeId": "RuleCreateRequest", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequest" + }, + "value": { + "jsonExample": "name", + "shape": { + "primitive": { + "string": { + "original": "name" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "executionContext", + "originalTypeDeclaration": { + "typeId": "RuleCreateRequest", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequest" + }, + "value": { + "jsonExample": {}, + "shape": { + "shape": { + "properties": [], + "type": "object" + }, + "typeName": { + "typeId": "RuleCreateRequestExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequestExecutionContext" + }, + "type": "named" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "RuleCreateRequest", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequest" + }, + "type": "named" + }, + "type": "reference" + }, + "response": { + "value": { + "value": { + "jsonExample": { + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + }, + "shape": { + "shape": { + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "typeId": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse" + }, + "value": { + "jsonExample": "id", + "shape": { + "primitive": { + "string": { + "original": "id" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "name", + "originalTypeDeclaration": { + "typeId": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse" + }, + "value": { + "jsonExample": "name", + "shape": { + "primitive": { + "string": { + "original": "name" + }, + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "status", + "originalTypeDeclaration": { + "typeId": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse" + }, + "value": { + "jsonExample": "active", + "shape": { + "shape": { + "value": "active", + "type": "enum" + }, + "typeName": { + "typeId": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponseStatus" + }, + "type": "named" + } + } + }, + { + "name": "executionContext", + "originalTypeDeclaration": { + "typeId": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse" + }, + "value": { + "jsonExample": "prod", + "shape": { + "shape": { + "value": "prod", + "type": "enum" + }, + "typeName": { + "typeId": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleExecutionContext" + }, + "type": "named" + } + } + } + ], + "type": "object" + }, + "typeName": { + "typeId": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse" + }, + "type": "named" + } + }, + "type": "body" + }, + "type": "ok" + } + } + } + ], + "idempotent": false, + "fullPath": { + "head": "/v1/preview/1/rules", + "parts": [] + }, + "allPathParameters": [], + "source": { + "type": "openapi" + }, + "audiences": [], + "id": "endpoint_.createRule", + "name": "createRule", + "requestBody": { + "contentType": "application/json", + "requestBodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequest", + "typeId": "RuleCreateRequest", + "inline": false, + "displayName": "RuleCreateRequest", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "createRuleExample": { + "name": "string", + "executionContext": "prod" + } + } + }, + "type": "reference" + }, + "v2RequestBodies": { + "requestBodies": [ + { + "contentType": "application/json", + "requestBodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleCreateRequest", + "typeId": "RuleCreateRequest", + "inline": false, + "displayName": "RuleCreateRequest", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "createRuleExample": { + "name": "string", + "executionContext": "prod" + } + } + }, + "type": "reference" + } + ] + }, + "response": { + "statusCode": 200, + "body": { + "value": { + "responseBodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse", + "typeId": "RuleResponse", + "inline": false, + "displayName": "RuleResponse", + "type": "named" + }, + "docs": "Created rule", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "createRuleExample": { + "id": "string", + "name": "string", + "status": "active", + "executionContext": "prod" + } + } + }, + "type": "response" + }, + "type": "json" + }, + "docs": "Created rule" + }, + "v2Examples": { + "autogeneratedExamples": { + "createRuleExample_200": { + "displayName": "createRuleExample", + "request": { + "endpoint": { + "method": "POST", + "path": "/v1/preview/1/rules" + }, + "environment": "https://api.spscommerce.com", + "pathParameters": {}, + "queryParameters": {}, + "headers": {}, + "requestBody": { + "name": "string", + "executionContext": "prod" + } + }, + "response": { + "statusCode": 200, + "body": { + "value": { + "id": "string", + "name": "string", + "status": "active", + "executionContext": "prod" + }, + "type": "json" + } + } + } + }, + "userSpecifiedExamples": {} + }, + "v2Responses": { + "responses": [ + { + "statusCode": 200, + "body": { + "value": { + "responseBodyType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleResponse", + "typeId": "RuleResponse", + "inline": false, + "displayName": "RuleResponse", + "type": "named" + }, + "docs": "Created rule", + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "createRuleExample": { + "id": "string", + "name": "string", + "status": "active", + "executionContext": "prod" + } + } + }, + "type": "response" + }, + "type": "json" + }, + "docs": "Created rule" + } + ] + } + } + ] + } + }, + "errors": {}, + "webhookGroups": {}, + "headers": [], + "idempotencyHeaders": [], + "apiDisplayName": "allOf SPS Commerce Reproduction", + "pathParameters": [], + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "variables": [], + "serviceTypeReferenceInfo": { + "sharedTypes": [], + "typesReferencedOnlyByService": {} + }, + "environments": { + "defaultEnvironment": "https://api.spscommerce.com", + "environments": { + "environments": [ + { + "id": "https://api.spscommerce.com", + "name": "https://api.spscommerce.com", + "url": "https://api.spscommerce.com" + } + ], + "type": "singleBaseUrl" + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "service": "service_", + "types": [ + "PagingCursors", + "PaginatedResult", + "RuleExecutionContext", + "RuleTypeResponse", + "RuleTypeSearchResponse", + "RuleCreateRequest", + "RuleResponse" + ], + "errors": [], + "subpackages": [], + "hasEndpointsInTree": false + }, + "subpackages": {}, + "sdkConfig": { + "hasFileDownloadEndpoints": false, + "hasPaginatedEndpoints": false, + "hasStreamingEndpoints": false, + "isAuthMandatory": true, + "platformHeaders": { + "language": "", + "sdkName": "", + "sdkVersion": "" + } + }, + "apiName": "allOf SPS Commerce Reproduction", + "constants": { + "errorInstanceIdKey": "errorInstanceId" + } +} \ No newline at end of file diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts new file mode 100644 index 000000000000..5735c3c9c2a8 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts @@ -0,0 +1,201 @@ +/** + * Assertion-based tests for allOf composition edge cases reported by SPS Commerce. + * These tests validate the IR output against expected behavior per the OpenAPI spec, + * rather than snapshot-matching. They should FAIL until the allOf bugs are fixed. + * + * Uses the V3 importer (OSSWorkspace) which is the code path for docs customers. + * + * Pylon #18189 / Linear FER-9158 + */ + +import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; +import { createMockTaskContext } from "@fern-api/task-context"; +import { loadAPIWorkspace } from "@fern-api/workspace-loader"; + +const FIXTURE_DIR = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures/allof-spscommerce/fern")); + +interface IRProperty { + name: string; + valueType: { + _type: string; + container?: { + _type: string; + optional?: unknown; + list?: unknown; + }; + name?: string; + typeId?: string; + }; +} + +interface IRTypeShape { + _type: string; + properties?: IRProperty[]; + extends?: Array<{ name: string; typeId: string }>; +} + +interface IRType { + name: { name: string; typeId: string }; + shape: IRTypeShape; +} + +interface IR { + types: Record; +} + +function findType(ir: IR, name: string): IRType | undefined { + return Object.values(ir.types).find((t) => t.name.name === name); +} + +function findProperty(type: IRType, propName: string): IRProperty | undefined { + if (type.shape._type !== "object") { + return undefined; + } + return type.shape.properties?.find((p) => p.name === propName); +} + +function getOuterType(prop: IRProperty): string { + return prop.valueType._type; +} + +function getContainerType(prop: IRProperty): string | undefined { + if (prop.valueType._type === "container") { + return prop.valueType.container?._type; + } + return undefined; +} + +describe("allOf SPS Commerce edge cases", () => { + let ir: IR; + + beforeAll(async () => { + const context = createMockTaskContext(); + const workspace = await loadAPIWorkspace({ + absolutePathToWorkspace: FIXTURE_DIR, + context, + cliVersion: "0.0.0", + workspaceName: "allof-spscommerce" + }); + if (!workspace.didSucceed) { + throw new Error(`Failed to load fixture: ${JSON.stringify(workspace.failures)}`); + } + if (!(workspace.workspace instanceof OSSWorkspace)) { + throw new Error("Expected OSSWorkspace (V3 importer) but got a different workspace type"); + } + const intermediateRepresentation = await workspace.workspace.getIntermediateRepresentation({ + context, + audiences: { type: "all" }, + enableUniqueErrorsPerEndpoint: false, + generateV1Examples: true, + logWarnings: false + }); + ir = intermediateRepresentation as unknown as IR; + }, 30_000); + + describe("Case A: array items narrowing via allOf", () => { + it("RuleTypeSearchResponse should exist as a type", () => { + const type = findType(ir, "RuleTypeSearchResponse"); + expect(type).toBeDefined(); + }); + + it("results property should be a list, not optional", () => { + const type = findType(ir, "RuleTypeSearchResponse")!; + const results = findProperty(type, "results"); + expect(results).toBeDefined(); + + // results is required in the parent PaginatedResult, so it should NOT + // be wrapped in optional/nullable after allOf composition + const containerType = getContainerType(results!); + expect(containerType).not.toBe("optional"); + expect(containerType).not.toBe("nullable"); + expect(containerType).toBe("list"); + }); + + it("results items should be typed as RuleTypeResponse, not unknown", () => { + const type = findType(ir, "RuleTypeSearchResponse")!; + const results = findProperty(type, "results"); + expect(results).toBeDefined(); + + // Drill into the list's element type — it should be named:RuleTypeResponse + const valueType = results!.valueType; + let listType: Record | undefined; + if (valueType._type === "container" && valueType.container?._type === "list") { + listType = valueType.container.list as Record; + } else if (valueType._type === "container" && valueType.container?._type === "optional") { + // If wrapped in optional (the bug), drill through it + const inner = valueType.container.optional as Record; + if (inner?._type === "container" && (inner.container as Record)?._type === "list") { + listType = (inner.container as Record).list as Record; + } + } + expect(listType).toBeDefined(); + expect(listType?._type).toBe("named"); + expect(listType?.name).toBe("RuleTypeResponse"); + }); + + it("paging property should remain required (not optional)", () => { + const type = findType(ir, "RuleTypeSearchResponse")!; + const paging = findProperty(type, "paging"); + expect(paging).toBeDefined(); + + const containerType = getContainerType(paging!); + expect(containerType).not.toBe("optional"); + }); + }); + + describe("Case B: property-level allOf with $ref + inline primitive", () => { + it("RuleCreateRequest should exist as a type", () => { + const type = findType(ir, "RuleCreateRequest"); + expect(type).toBeDefined(); + expect(type!.shape._type).toBe("object"); + }); + + it("executionContext property should exist", () => { + const type = findType(ir, "RuleCreateRequest")!; + const prop = findProperty(type, "executionContext"); + expect(prop).toBeDefined(); + }); + + it("executionContext should reference RuleExecutionContext, not be unknown or empty", () => { + const type = findType(ir, "RuleCreateRequest")!; + const prop = findProperty(type, "executionContext"); + expect(prop).toBeDefined(); + + // The property should reference the RuleExecutionContext enum. + // It should NOT be: unknown, an empty object, or a named reference + // to a synthetic type with zero properties. + const outerType = getOuterType(prop!); + expect(outerType).not.toBe("unknown"); + + // If it's a named reference, check it points to RuleExecutionContext + // (not a synthetic empty object type) + if (outerType === "named") { + expect(prop!.valueType.name).toBe("RuleExecutionContext"); + } else if (outerType === "container") { + // Could be optional since the enum + // doesn't have a default — but the inner type must still reference it + const container = prop!.valueType.container!; + if (container._type === "optional") { + const inner = container.optional as Record; + expect(inner?._type).toBe("named"); + expect(inner?.name).toBe("RuleExecutionContext"); + } + } + }); + }); + + describe("Case C: required field preservation through allOf", () => { + it("RuleResponse should exist with all required properties as non-optional", () => { + const type = findType(ir, "RuleResponse"); + expect(type).toBeDefined(); + + for (const requiredProp of ["id", "name", "status", "executionContext"]) { + const prop = findProperty(type!, requiredProp); + expect(prop).toBeDefined(); + const containerType = getContainerType(prop!); + expect(containerType).not.toBe("optional"); + } + }); + }); +}); diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/fern.config.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/fern.config.json new file mode 100644 index 000000000000..ecb7133e2645 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "fern", + "version": "*" +} diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/generators.yml b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/generators.yml new file mode 100644 index 000000000000..5b01f1e0833d --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/generators.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json +api: + specs: + - openapi: ../openapi.yml diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/openapi.yml b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/openapi.yml new file mode 100644 index 000000000000..22ff87249f0d --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/openapi.yml @@ -0,0 +1,180 @@ +openapi: 3.0.3 +info: + title: allOf SPS Commerce Reproduction + version: 1.0.0 + description: > + Reproduces the allOf edge cases reported by SPS Commerce (Pylon #18189). + These patterns are taken directly from the customer's Slack messages and + their simplified allof-troubleshooting.oas.yml test file. + +servers: + - url: https://api.spscommerce.com + +paths: + /v1/preview/1/rule-types: + get: + operationId: searchRuleTypes + summary: Search for rule types + parameters: + - name: query + in: query + schema: + type: string + responses: + "200": + description: Paginated list of rule types + content: + application/json: + schema: + $ref: "#/components/schemas/RuleTypeSearchResponse" + + /v1/preview/1/rules: + post: + operationId: createRule + summary: Create a rule + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/RuleCreateRequest" + responses: + "200": + description: Created rule + content: + application/json: + schema: + $ref: "#/components/schemas/RuleResponse" + +components: + schemas: + # ----------------------------------------------------------------------- + # Shared base schemas (from SPS Commerce's actual spec) + # ----------------------------------------------------------------------- + PagingCursors: + type: object + required: + - next + properties: + next: + type: string + previous: + type: string + + PaginatedResult: + type: object + required: + - paging + - results + properties: + paging: + $ref: "#/components/schemas/PagingCursors" + results: + type: array + maxItems: 100 + description: Current page of results from the requested resource. + items: {} + + RuleExecutionContext: + type: string + description: The execution environment for a rule. + enum: + - prod + - staging + - dev + + # ----------------------------------------------------------------------- + # Case A: Array items narrowing WITHOUT redeclaring type: array + # + # From the customer's original report (Slack reply #5). The child schema + # narrows `results.items` from {} to RuleTypeResponse. The parent declares + # `required: [paging, results]`, so `results` should remain required in + # the composed type. + # + # Expected: results is a required list of RuleTypeResponse + # Bug: results renders as optional (required not propagated from parent) + # ----------------------------------------------------------------------- + RuleTypeResponse: + type: object + required: + - id + - name + - status + properties: + id: + type: string + name: + type: string + description: + type: string + status: + type: string + enum: + - active + - inactive + + RuleTypeSearchResponse: + allOf: + - $ref: "#/components/schemas/PaginatedResult" + - properties: + results: + items: + $ref: "#/components/schemas/RuleTypeResponse" + + # ----------------------------------------------------------------------- + # Case B: Property-level allOf combining $ref with inline constraints + # + # From Evan's report (Slack reply #10). The property `executionContext` + # uses allOf to combine a $ref to an enum with an inline string + pattern + # constraint. This means the value must be one of the enum values AND + # match the pattern (i.e., any value except "prod"). + # + # Expected: executionContext references RuleExecutionContext (the enum) + # Bug: renders as an empty object — the non-object allOf element is + # skipped entirely ("Skipping non-object allOf element") + # ----------------------------------------------------------------------- + RuleCreateRequest: + type: object + required: + - name + - executionContext + properties: + name: + type: string + executionContext: + allOf: + - $ref: "#/components/schemas/RuleExecutionContext" + - type: string + pattern: "^(?!prod$)[a-z0-9_-]+$" + description: > + Execution context for the rule, excluding the prod environment. + + # ----------------------------------------------------------------------- + # Case C: allOf extending a parent with additional required properties + # + # The response type composes PaginatedResult (for paging) with additional + # rule-specific fields. All properties from the parent should retain their + # required/optional status. + # + # Expected: id, name, status are required; executionContext is required + # ----------------------------------------------------------------------- + RuleResponse: + type: object + required: + - id + - name + - status + - executionContext + properties: + id: + type: string + name: + type: string + status: + type: string + enum: + - active + - inactive + - draft + executionContext: + $ref: "#/components/schemas/RuleExecutionContext" From 6425f88221e4f8d400d0b6acaeebd5b99fd899a8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:11:24 +0000 Subject: [PATCH 04/29] fix(cli): resolve allOf composition bugs in V3 OpenAPI importer - Enhance shouldMergeAllOf path to resolve $ref elements instead of bailing out - Add cycle detection via Set to prevent infinite loops on circular refs - Handle property-level allOf with $ref to non-object types (e.g. enums) by referencing the original type instead of creating synthetic copies - Update test infrastructure to serialize IR discriminants for assertions - Update snapshots for allof-spscommerce fixture Co-Authored-By: bot_apk --- .../src/converters/schema/SchemaConverter.ts | 36 +- .../schema/SchemaOrReferenceConverter.ts | 61 +- .../v3-sdks/allof-spscommerce.json | 696 ++++++++---------- .../src/__test__/allof-spscommerce.test.ts | 11 +- 4 files changed, 376 insertions(+), 428 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index 524a7cebc773..522c423bb058 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -197,17 +197,36 @@ export class SchemaConverter extends AbstractConverter = {}; + const resolvedRefs = new Set(); + let hasCycle = false; for (const allOfSchema of this.schema.allOf ?? []) { + let schemaToMerge: OpenAPIV3_1.SchemaObject; + if (this.context.isReferenceObject(allOfSchema)) { - return undefined; + const refPath = allOfSchema.$ref; + if (resolvedRefs.has(refPath)) { + hasCycle = true; + break; + } + resolvedRefs.add(refPath); + + const resolved = this.context.resolveMaybeReference({ + schemaOrReference: allOfSchema, + breadcrumbs: this.breadcrumbs + }); + if (resolved == null) { + return undefined; + } + schemaToMerge = resolved; + } else { + schemaToMerge = allOfSchema; } // Handle bare oneOf/anyOf elements used for mutual exclusion patterns // (e.g., oneOf with variants containing `not: {}` properties). // Flatten variant properties into the merged schema as optional properties. - const variants = - (allOfSchema as OpenAPIV3_1.SchemaObject).oneOf ?? (allOfSchema as OpenAPIV3_1.SchemaObject).anyOf; - if (variants != null && allOfSchema.type == null && allOfSchema.properties == null) { + const variants = schemaToMerge.oneOf ?? schemaToMerge.anyOf; + if (variants != null && schemaToMerge.type == null && schemaToMerge.properties == null) { const flattenedProperties: Record = {}; for (const variantSchemaOrRef of variants) { const variantSchema = this.context.isReferenceObject(variantSchemaOrRef) @@ -243,8 +262,8 @@ export class SchemaConverter extends AbstractConverter { - if (srcValue === allOfSchema) { + mergedSchema = mergeWith(mergedSchema, schemaToMerge, (objValue, srcValue) => { + if (srcValue === schemaToMerge) { return objValue; } if (Array.isArray(objValue) && Array.isArray(srcValue)) { @@ -254,6 +273,11 @@ export class SchemaConverter extends AbstractConverter this.context.isReferenceObject(s)); + const inlineElements = this.schemaOrReference.allOf.filter( + (s) => !this.context.isReferenceObject(s) + ) as OpenAPIV3_1.SchemaObject[]; + + if ( + refElements.length === 1 && + inlineElements.every((s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf) + ) { + const resolved = this.context.resolveMaybeReference({ + schemaOrReference: refElements[0]!, breadcrumbs: this.breadcrumbs }); - if (response.ok) { - return { - type: this.wrapTypeReference(response.reference), - inlinedTypes: {} - }; + if (resolved != null && resolved.type !== "object" && !resolved.properties) { + const response = this.context.convertReferenceToTypeReference({ + reference: refElements[0]! as OpenAPIV3_1.ReferenceObject, + breadcrumbs: this.breadcrumbs + }); + if (response.ok) { + return { + type: this.wrapTypeReference(response.reference), + inlinedTypes: {} + }; + } } } + return undefined; } diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json index 7d2d77bd85e1..13a75af96e77 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json @@ -349,28 +349,45 @@ }, "shape": { "properties": [ + { + "name": "paging", + "valueType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PagingCursors", + "typeId": "PagingCursors", + "inline": false, + "displayName": "PagingCursors", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "RuleTypeSearchResponsePaging_example_autogenerated": { + "next": "string", + "previous": "string" + } + } + } + }, { "name": "results", "valueType": { "container": { - "optional": { - "container": { - "list": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse", - "typeId": "RuleTypeResponse", - "inline": false, - "displayName": "RuleTypeResponse", - "type": "named" - }, - "type": "list" + "list": { + "fernFilepath": { + "allParts": [], + "packagePath": [] }, - "type": "container" + "name": "RuleTypeResponse", + "typeId": "RuleTypeResponse", + "inline": false, + "displayName": "RuleTypeResponse", + "type": "named" }, - "type": "optional" + "type": "list" }, "type": "container" }, @@ -389,17 +406,7 @@ } } ], - "extends": [ - { - "typeId": "PaginatedResult", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PaginatedResult", - "displayName": "PaginatedResult" - } - ], + "extends": [], "extendedProperties": [], "extraProperties": false, "type": "object" @@ -407,7 +414,7 @@ "autogeneratedExamples": [], "userProvidedExamples": [], "referencedTypes": {}, - "inline": false, + "inline": true, "v2Examples": { "userSpecifiedExamples": {}, "autogeneratedExamples": { @@ -416,49 +423,16 @@ "next": "string" }, "results": [ - null + { + "id": "string", + "name": "string", + "status": "active" + } ] } } } }, - "RuleCreateRequestExecutionContext": { - "name": { - "typeId": "RuleCreateRequestExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleCreateRequestExecutionContext" - }, - "shape": { - "properties": [], - "extends": [ - { - "typeId": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleExecutionContext", - "displayName": "RuleExecutionContext" - } - ], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleCreateRequestExecutionContext_example_autogenerated": "prod" - } - } - }, "RuleCreateRequest": { "name": { "typeId": "RuleCreateRequest", @@ -496,9 +470,10 @@ "allParts": [], "packagePath": [] }, - "name": "RuleCreateRequestExecutionContext", - "typeId": "RuleCreateRequestExecutionContext", + "name": "RuleExecutionContext", + "typeId": "RuleExecutionContext", "inline": false, + "displayName": "RuleExecutionContext", "type": "named" }, "v2Examples": { @@ -733,7 +708,7 @@ "autogeneratedExamples": [ { "example": { - "id": "4ef66806", + "id": "d622963", "url": "/v1/preview/1/rule-types", "endpointHeaders": [], "endpointPathParameters": [], @@ -770,12 +745,12 @@ { "name": "paging", "originalTypeDeclaration": { - "typeId": "PaginatedResult", + "typeId": "RuleTypeSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "PaginatedResult" + "name": "RuleTypeSearchResponse" }, "value": { "jsonExample": { @@ -865,60 +840,6 @@ } } }, - { - "name": "results", - "originalTypeDeclaration": { - "typeId": "PaginatedResult", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PaginatedResult" - }, - "value": { - "jsonExample": [ - { - "key": "value" - }, - { - "key": "value" - } - ], - "shape": { - "container": { - "list": [ - { - "jsonExample": { - "key": "value" - }, - "shape": { - "unknown": { - "key": "value" - }, - "type": "unknown" - } - }, - { - "jsonExample": { - "key": "value" - }, - "shape": { - "unknown": { - "key": "value" - }, - "type": "unknown" - } - } - ], - "itemType": { - "type": "unknown" - }, - "type": "list" - }, - "type": "container" - } - } - }, { "name": "results", "originalTypeDeclaration": { @@ -946,155 +867,108 @@ ], "shape": { "container": { - "optional": { - "jsonExample": [ - { + "list": [ + { + "jsonExample": { "id": "id", "name": "name", "description": "description", "status": "active" }, - { - "id": "id", - "name": "name", - "description": "description", - "status": "active" - } - ], - "shape": { - "container": { - "list": [ - { - "jsonExample": { - "id": "id", - "name": "name", - "description": "description", - "status": "active" + "shape": { + "shape": { + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "id", + "shape": { + "primitive": { + "string": { + "original": "id" + }, + "type": "string" + }, + "type": "primitive" + } + } }, - "shape": { - "shape": { - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" + { + "name": "name", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "name", + "shape": { + "primitive": { + "string": { + "original": "name" }, - "value": { - "jsonExample": "id", + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "description", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "description", + "shape": { + "container": { + "optional": { + "jsonExample": "description", "shape": { "primitive": { "string": { - "original": "id" + "original": "description" }, "type": "string" }, "type": "primitive" } - } - }, - { - "name": "name", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" }, - "value": { - "jsonExample": "name", - "shape": { - "primitive": { - "string": { - "original": "name" - }, + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "description", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] + } }, - "name": "RuleTypeResponse" + "type": "primitive" }, - "value": { - "jsonExample": "description", - "shape": { - "container": { - "optional": { - "jsonExample": "description", - "shape": { - "primitive": { - "string": { - "original": "description" - }, - "type": "string" - }, - "type": "primitive" - } - }, - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - } - } + "type": "optional" }, - { - "name": "status", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "active", - "shape": { - "shape": { - "value": "active", - "type": "enum" - }, - "typeName": { - "typeId": "RuleTypeResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponseStatus" - }, - "type": "named" - } - } - } - ], - "type": "object" - }, - "typeName": { + "type": "container" + } + } + }, + { + "name": "status", + "originalTypeDeclaration": { "typeId": "RuleTypeResponse", "fernFilepath": { "allParts": [], @@ -1102,140 +976,140 @@ }, "name": "RuleTypeResponse" }, - "type": "named" + "value": { + "jsonExample": "active", + "shape": { + "shape": { + "value": "active", + "type": "enum" + }, + "typeName": { + "typeId": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponseStatus" + }, + "type": "named" + } + } } + ], + "type": "object" + }, + "typeName": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] }, - { - "jsonExample": { - "id": "id", - "name": "name", - "description": "description", - "status": "active" + "name": "RuleTypeResponse" + }, + "type": "named" + } + }, + { + "jsonExample": { + "id": "id", + "name": "name", + "description": "description", + "status": "active" + }, + "shape": { + "shape": { + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "id", + "shape": { + "primitive": { + "string": { + "original": "id" + }, + "type": "string" + }, + "type": "primitive" + } + } }, - "shape": { - "shape": { - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" + { + "name": "name", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "name", + "shape": { + "primitive": { + "string": { + "original": "name" }, - "value": { - "jsonExample": "id", + "type": "string" + }, + "type": "primitive" + } + } + }, + { + "name": "description", + "originalTypeDeclaration": { + "typeId": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponse" + }, + "value": { + "jsonExample": "description", + "shape": { + "container": { + "optional": { + "jsonExample": "description", "shape": { "primitive": { "string": { - "original": "id" + "original": "description" }, "type": "string" }, "type": "primitive" } - } - }, - { - "name": "name", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" }, - "value": { - "jsonExample": "name", - "shape": { - "primitive": { - "string": { - "original": "name" - }, + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "description", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] + } }, - "name": "RuleTypeResponse" + "type": "primitive" }, - "value": { - "jsonExample": "description", - "shape": { - "container": { - "optional": { - "jsonExample": "description", - "shape": { - "primitive": { - "string": { - "original": "description" - }, - "type": "string" - }, - "type": "primitive" - } - }, - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - } - } + "type": "optional" }, - { - "name": "status", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "active", - "shape": { - "shape": { - "value": "active", - "type": "enum" - }, - "typeName": { - "typeId": "RuleTypeResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponseStatus" - }, - "type": "named" - } - } - } - ], - "type": "object" - }, - "typeName": { + "type": "container" + } + } + }, + { + "name": "status", + "originalTypeDeclaration": { "typeId": "RuleTypeResponse", "fernFilepath": { "allParts": [], @@ -1243,44 +1117,52 @@ }, "name": "RuleTypeResponse" }, - "type": "named" + "value": { + "jsonExample": "active", + "shape": { + "shape": { + "value": "active", + "type": "enum" + }, + "typeName": { + "typeId": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "RuleTypeResponseStatus" + }, + "type": "named" + } + } } - } - ], - "itemType": { + ], + "type": "object" + }, + "typeName": { + "typeId": "RuleTypeResponse", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "RuleTypeResponse", - "typeId": "RuleTypeResponse", - "inline": false, - "displayName": "RuleTypeResponse", - "type": "named" - }, - "type": "list" - }, - "type": "container" - } - }, - "valueType": { - "container": { - "list": { - "fernFilepath": { - "allParts": [], - "packagePath": [] + "name": "RuleTypeResponse" }, - "name": "RuleTypeResponse", - "typeId": "RuleTypeResponse", - "inline": false, - "displayName": "RuleTypeResponse", "type": "named" - }, - "type": "list" + } + } + ], + "itemType": { + "fernFilepath": { + "allParts": [], + "packagePath": [] }, - "type": "container" + "name": "RuleTypeResponse", + "typeId": "RuleTypeResponse", + "inline": false, + "displayName": "RuleTypeResponse", + "type": "named" }, - "type": "optional" + "type": "list" }, "type": "container" } @@ -1442,7 +1324,7 @@ "autogeneratedExamples": [ { "example": { - "id": "f33597bf", + "id": "32b0112f", "url": "/v1/preview/1/rules", "endpointHeaders": [], "endpointPathParameters": [], @@ -1453,7 +1335,7 @@ "request": { "jsonExample": { "name": "name", - "executionContext": {} + "executionContext": "prod" }, "shape": { "shape": { @@ -1492,19 +1374,19 @@ "name": "RuleCreateRequest" }, "value": { - "jsonExample": {}, + "jsonExample": "prod", "shape": { "shape": { - "properties": [], - "type": "object" + "value": "prod", + "type": "enum" }, "typeName": { - "typeId": "RuleCreateRequestExecutionContext", + "typeId": "RuleExecutionContext", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "RuleCreateRequestExecutionContext" + "name": "RuleExecutionContext" }, "type": "named" } diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts index 5735c3c9c2a8..7f3d753c64b9 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts @@ -90,7 +90,16 @@ describe("allOf SPS Commerce edge cases", () => { generateV1Examples: true, logWarnings: false }); - ir = intermediateRepresentation as unknown as IR; + // Serialize to JSON, renaming discriminant `type` → `_type` on union objects + ir = JSON.parse( + JSON.stringify(intermediateRepresentation, (_key, value) => { + if (value && typeof value === "object" && "_visit" in value && "type" in value) { + const { type, _visit, ...rest } = value; + return { _type: type, ...rest }; + } + return value; + }) + ) as IR; }, 30_000); describe("Case A: array items narrowing via allOf", () => { From 197e1c89caf749c1f56bae93b520211e8d736ed8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:14:50 +0000 Subject: [PATCH 05/29] fix: resolve biome noNonNullAssertion lint errors in SchemaOrReferenceConverter Co-Authored-By: bot_apk --- .../src/converters/schema/SchemaOrReferenceConverter.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts index a3db56ee9d76..beede8e0b4f6 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts @@ -112,18 +112,19 @@ export class SchemaOrReferenceConverter extends AbstractConverter< const inlineElements = this.schemaOrReference.allOf.filter( (s) => !this.context.isReferenceObject(s) ) as OpenAPIV3_1.SchemaObject[]; + const singleRef = refElements.length === 1 ? refElements[0] : undefined; if ( - refElements.length === 1 && + singleRef != null && inlineElements.every((s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf) ) { const resolved = this.context.resolveMaybeReference({ - schemaOrReference: refElements[0]!, + schemaOrReference: singleRef, breadcrumbs: this.breadcrumbs }); if (resolved != null && resolved.type !== "object" && !resolved.properties) { const response = this.context.convertReferenceToTypeReference({ - reference: refElements[0]! as OpenAPIV3_1.ReferenceObject, + reference: singleRef as OpenAPIV3_1.ReferenceObject, breadcrumbs: this.breadcrumbs }); if (response.ok) { From f35278e41f32a8b9c14956a38f264a8c7a0293f0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:17:45 +0000 Subject: [PATCH 06/29] fix: add biome-ignore for pre-existing noNonNullAssertion in test file Co-Authored-By: bot_apk --- .../src/__test__/allof-spscommerce.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts index 7f3d753c64b9..4f0692b4a570 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts @@ -109,12 +109,14 @@ describe("allOf SPS Commerce edge cases", () => { }); it("results property should be a list, not optional", () => { + // biome-ignore lint/style/noNonNullAssertion: verified by prior test const type = findType(ir, "RuleTypeSearchResponse")!; const results = findProperty(type, "results"); expect(results).toBeDefined(); // results is required in the parent PaginatedResult, so it should NOT // be wrapped in optional/nullable after allOf composition + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const containerType = getContainerType(results!); expect(containerType).not.toBe("optional"); expect(containerType).not.toBe("nullable"); @@ -122,11 +124,13 @@ describe("allOf SPS Commerce edge cases", () => { }); it("results items should be typed as RuleTypeResponse, not unknown", () => { + // biome-ignore lint/style/noNonNullAssertion: verified by prior test const type = findType(ir, "RuleTypeSearchResponse")!; const results = findProperty(type, "results"); expect(results).toBeDefined(); // Drill into the list's element type — it should be named:RuleTypeResponse + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const valueType = results!.valueType; let listType: Record | undefined; if (valueType._type === "container" && valueType.container?._type === "list") { @@ -144,10 +148,12 @@ describe("allOf SPS Commerce edge cases", () => { }); it("paging property should remain required (not optional)", () => { + // biome-ignore lint/style/noNonNullAssertion: verified by prior test const type = findType(ir, "RuleTypeSearchResponse")!; const paging = findProperty(type, "paging"); expect(paging).toBeDefined(); + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const containerType = getContainerType(paging!); expect(containerType).not.toBe("optional"); }); @@ -157,16 +163,19 @@ describe("allOf SPS Commerce edge cases", () => { it("RuleCreateRequest should exist as a type", () => { const type = findType(ir, "RuleCreateRequest"); expect(type).toBeDefined(); + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect expect(type!.shape._type).toBe("object"); }); it("executionContext property should exist", () => { + // biome-ignore lint/style/noNonNullAssertion: verified by prior test const type = findType(ir, "RuleCreateRequest")!; const prop = findProperty(type, "executionContext"); expect(prop).toBeDefined(); }); it("executionContext should reference RuleExecutionContext, not be unknown or empty", () => { + // biome-ignore lint/style/noNonNullAssertion: verified by prior test const type = findType(ir, "RuleCreateRequest")!; const prop = findProperty(type, "executionContext"); expect(prop).toBeDefined(); @@ -174,16 +183,20 @@ describe("allOf SPS Commerce edge cases", () => { // The property should reference the RuleExecutionContext enum. // It should NOT be: unknown, an empty object, or a named reference // to a synthetic type with zero properties. + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const outerType = getOuterType(prop!); expect(outerType).not.toBe("unknown"); // If it's a named reference, check it points to RuleExecutionContext // (not a synthetic empty object type) if (outerType === "named") { + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect expect(prop!.valueType.name).toBe("RuleExecutionContext"); } else if (outerType === "container") { // Could be optional since the enum // doesn't have a default — but the inner type must still reference it + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const container = prop!.valueType.container!; if (container._type === "optional") { const inner = container.optional as Record; @@ -200,8 +213,10 @@ describe("allOf SPS Commerce edge cases", () => { expect(type).toBeDefined(); for (const requiredProp of ["id", "name", "status", "executionContext"]) { + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const prop = findProperty(type!, requiredProp); expect(prop).toBeDefined(); + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const containerType = getContainerType(prop!); expect(containerType).not.toBe("optional"); } From 77c5f8f814a33461c5af4f84a26dc941e80bf9c4 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:25:08 +0000 Subject: [PATCH 07/29] fix: update v3-sdks snapshots for allOf merge changes Co-Authored-By: bot_apk --- .../v3-sdks/allof-array-items-narrowing.json | 70 +--- .../examples-endpoint-level-named.json | 314 ++++++++++---- .../v3-sdks/examples-endpoint-level.json | 394 ++++++++++++------ .../__snapshots__/v3-sdks/url-reference.json | 70 ++-- 4 files changed, 554 insertions(+), 294 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-array-items-narrowing.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-array-items-narrowing.json index 07f52caa9fcd..270bab4f5765 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-array-items-narrowing.json +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-array-items-narrowing.json @@ -183,17 +183,7 @@ } } ], - "extends": [ - { - "typeId": "PaginatedList", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PaginatedList", - "displayName": "PaginatedList" - } - ], + "extends": [], "extendedProperties": [], "extraProperties": false, "type": "object" @@ -201,7 +191,7 @@ "autogeneratedExamples": [], "userProvidedExamples": [], "referencedTypes": {}, - "inline": false, + "inline": true, "v2Examples": { "userSpecifiedExamples": {}, "autogeneratedExamples": { @@ -255,17 +245,7 @@ } } ], - "extends": [ - { - "typeId": "PaginatedList", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PaginatedList", - "displayName": "PaginatedList" - } - ], + "extends": [], "extendedProperties": [], "extraProperties": false, "type": "object" @@ -273,7 +253,7 @@ "autogeneratedExamples": [], "userProvidedExamples": [], "referencedTypes": {}, - "inline": false, + "inline": true, "v2Examples": { "userSpecifiedExamples": {}, "autogeneratedExamples": { @@ -315,43 +295,6 @@ } } }, - "RuleConfigExecutionContext": { - "name": { - "typeId": "RuleConfigExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleConfigExecutionContext" - }, - "shape": { - "properties": [], - "extends": [ - { - "typeId": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleExecutionContext", - "displayName": "RuleExecutionContext" - } - ], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleConfigExecutionContext_example_autogenerated": "prod" - } - } - }, "RuleConfig": { "name": { "typeId": "RuleConfig", @@ -397,9 +340,10 @@ "allParts": [], "packagePath": [] }, - "name": "RuleConfigExecutionContext", - "typeId": "RuleConfigExecutionContext", + "name": "RuleExecutionContext", + "typeId": "RuleExecutionContext", "inline": false, + "displayName": "RuleExecutionContext", "type": "named" }, "type": "optional" diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level-named.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level-named.json index 3190dc9670ff..425ae13be164 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level-named.json +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level-named.json @@ -202,6 +202,84 @@ } } }, + "TreeLocation": { + "name": { + "typeId": "TreeLocation", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "TreeLocation" + }, + "shape": { + "properties": [ + { + "name": "latitude", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "FLOAT", + "v2": { + "validation": {}, + "type": "float" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocationLatitude_example_autogenerated": 1.1 + } + } + }, + { + "name": "longitude", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "FLOAT", + "v2": { + "validation": {}, + "type": "float" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocationLongitude_example_autogenerated": 1.1 + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocation_example_autogenerated": {} + } + } + }, "Tree": { "name": { "typeId": "Tree", @@ -213,6 +291,94 @@ }, "shape": { "properties": [ + { + "name": "species", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": { + "TreeSpecies_example_0": "Quercus alba" + }, + "autogeneratedExamples": {} + } + }, + { + "name": "height", + "valueType": { + "primitive": { + "v1": "FLOAT", + "v2": { + "validation": {}, + "type": "float" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": { + "TreeHeight_example_0": 25.4 + }, + "autogeneratedExamples": {} + } + }, + { + "name": "age", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "INTEGER", + "v2": { + "validation": {}, + "type": "integer" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": { + "TreeAge_example_0": 50 + }, + "autogeneratedExamples": {} + } + }, + { + "name": "location", + "valueType": { + "container": { + "optional": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "TreeLocation", + "typeId": "TreeLocation", + "inline": false, + "type": "named" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocation_example_autogenerated": {} + } + } + }, { "name": "id", "valueType": { @@ -259,17 +425,7 @@ } } ], - "extends": [ - { - "typeId": "TreeCreate", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "TreeCreate", - "displayName": "TreeCreate" - } - ], + "extends": [], "extendedProperties": [], "extraProperties": false, "type": "object" @@ -277,7 +433,7 @@ "autogeneratedExamples": [], "userProvidedExamples": [], "referencedTypes": {}, - "inline": false, + "inline": true, "v2Examples": { "userSpecifiedExamples": {}, "autogeneratedExamples": { @@ -558,7 +714,7 @@ "autogeneratedExamples": [ { "example": { - "id": "4f74990c", + "id": "3598d6b4", "url": "/trees", "endpointHeaders": [], "endpointPathParameters": [], @@ -614,12 +770,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -637,12 +793,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -658,12 +814,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -698,12 +854,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -723,12 +879,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -763,12 +919,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -804,12 +960,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -819,8 +975,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -925,12 +1081,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -948,12 +1104,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -969,12 +1125,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -1009,12 +1165,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -1034,12 +1190,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -1074,12 +1230,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -1115,12 +1271,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -1130,8 +1286,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -1438,7 +1594,7 @@ "autogeneratedExamples": [ { "example": { - "id": "57bea735", + "id": "d368f1a1", "url": "/trees", "endpointHeaders": [], "endpointPathParameters": [], @@ -1591,12 +1747,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -1614,12 +1770,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -1635,12 +1791,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -1675,12 +1831,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -1700,12 +1856,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -1740,12 +1896,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -1781,12 +1937,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -1796,8 +1952,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -2270,7 +2426,7 @@ "autogeneratedExamples": [ { "example": { - "id": "47fd375b", + "id": "6a51944f", "url": "/trees/treeId", "endpointHeaders": [], "endpointPathParameters": [ @@ -2314,12 +2470,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -2337,12 +2493,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -2358,12 +2514,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -2398,12 +2554,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -2423,12 +2579,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -2463,12 +2619,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -2504,12 +2660,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -2519,8 +2675,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level.json index e4746b2c53cd..00f40493e4da 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level.json +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/examples-endpoint-level.json @@ -206,6 +206,84 @@ "autogeneratedExamples": {} } }, + "TreeLocation": { + "name": { + "typeId": "TreeLocation", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "TreeLocation" + }, + "shape": { + "properties": [ + { + "name": "latitude", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "FLOAT", + "v2": { + "validation": {}, + "type": "float" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocationLatitude_example_autogenerated": 1.1 + } + } + }, + { + "name": "longitude", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "FLOAT", + "v2": { + "validation": {}, + "type": "float" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocationLongitude_example_autogenerated": 1.1 + } + } + } + ], + "extends": [], + "extendedProperties": [], + "extraProperties": false, + "type": "object" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocation_example_autogenerated": {} + } + } + }, "Tree": { "name": { "typeId": "Tree", @@ -217,6 +295,94 @@ }, "shape": { "properties": [ + { + "name": "species", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "validation": {}, + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": { + "TreeSpecies_example_0": "Quercus alba" + }, + "autogeneratedExamples": {} + } + }, + { + "name": "height", + "valueType": { + "primitive": { + "v1": "FLOAT", + "v2": { + "validation": {}, + "type": "float" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": { + "TreeHeight_example_0": 25.4 + }, + "autogeneratedExamples": {} + } + }, + { + "name": "age", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "INTEGER", + "v2": { + "validation": {}, + "type": "integer" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": { + "TreeAge_example_0": 50 + }, + "autogeneratedExamples": {} + } + }, + { + "name": "location", + "valueType": { + "container": { + "optional": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "TreeLocation", + "typeId": "TreeLocation", + "inline": false, + "type": "named" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "TreeLocation_example_autogenerated": {} + } + } + }, { "name": "id", "valueType": { @@ -263,17 +429,7 @@ } } ], - "extends": [ - { - "typeId": "TreeCreate", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "TreeCreate", - "displayName": "TreeCreate" - } - ], + "extends": [], "extendedProperties": [], "extraProperties": false, "type": "object" @@ -281,16 +437,20 @@ "autogeneratedExamples": [], "userProvidedExamples": [], "referencedTypes": {}, - "inline": false, + "inline": true, "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "Tree_example_autogenerated": { + "userSpecifiedExamples": { + "Tree_example_0": { "species": "Quercus alba", "height": 25.4, + "location": { + "latitude": 40.7128, + "longitude": -74.006 + }, "id": "string" } - } + }, + "autogeneratedExamples": {} } }, "createFern_Response_201": { @@ -431,7 +591,7 @@ "autogeneratedExamples": [ { "example": { - "id": "4f74990c", + "id": "3598d6b4", "url": "/trees", "endpointHeaders": [], "endpointPathParameters": [], @@ -487,12 +647,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -510,12 +670,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -531,12 +691,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -571,12 +731,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -596,12 +756,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -636,12 +796,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -677,12 +837,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -692,8 +852,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -798,12 +958,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -821,12 +981,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -842,12 +1002,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -882,12 +1042,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -907,12 +1067,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -947,12 +1107,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -988,12 +1148,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -1003,8 +1163,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -1311,7 +1471,7 @@ "autogeneratedExamples": [ { "example": { - "id": "57bea735", + "id": "d368f1a1", "url": "/trees", "endpointHeaders": [], "endpointPathParameters": [], @@ -1464,12 +1624,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -1487,12 +1647,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -1508,12 +1668,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -1548,12 +1708,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -1573,12 +1733,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -1613,12 +1773,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -1654,12 +1814,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -1669,8 +1829,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -2063,7 +2223,7 @@ "autogeneratedExamples": [ { "example": { - "id": "47fd375b", + "id": "6a51944f", "url": "/trees/treeId", "endpointHeaders": [], "endpointPathParameters": [ @@ -2107,12 +2267,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -2130,12 +2290,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -2151,12 +2311,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -2191,12 +2351,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -2216,12 +2376,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -2256,12 +2416,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -2297,12 +2457,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -2312,8 +2472,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -3656,7 +3816,7 @@ "autogeneratedExamples": [ { "example": { - "id": "2889b501", + "id": "8227317d", "url": "/trees/pines", "endpointHeaders": [], "endpointPathParameters": [], @@ -3684,12 +3844,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -3707,12 +3867,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -3728,12 +3888,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -3768,12 +3928,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -3793,12 +3953,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -3833,12 +3993,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -3874,12 +4034,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -3889,8 +4049,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, @@ -4180,7 +4340,7 @@ "autogeneratedExamples": [ { "example": { - "id": "2889b501", + "id": "8227317d", "url": "/trees/alias", "endpointHeaders": [], "endpointPathParameters": [], @@ -4208,12 +4368,12 @@ { "name": "species", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": "species", @@ -4231,12 +4391,12 @@ { "name": "height", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1.1, @@ -4252,12 +4412,12 @@ { "name": "age", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": 1, @@ -4292,12 +4452,12 @@ { "name": "location", "originalTypeDeclaration": { - "typeId": "TreeCreate", + "typeId": "Tree", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreate" + "name": "Tree" }, "value": { "jsonExample": { @@ -4317,12 +4477,12 @@ { "name": "latitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -4357,12 +4517,12 @@ { "name": "longitude", "originalTypeDeclaration": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "value": { "jsonExample": 1.1, @@ -4398,12 +4558,12 @@ "type": "object" }, "typeName": { - "typeId": "TreeCreateLocation", + "typeId": "TreeLocation", "fernFilepath": { "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation" + "name": "TreeLocation" }, "type": "named" } @@ -4413,8 +4573,8 @@ "allParts": [], "packagePath": [] }, - "name": "TreeCreateLocation", - "typeId": "TreeCreateLocation", + "name": "TreeLocation", + "typeId": "TreeLocation", "inline": false, "type": "named" }, diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/url-reference.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/url-reference.json index 8cd096e2dbc2..3d5bb4b14d6f 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/url-reference.json +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/url-reference.json @@ -345,6 +345,41 @@ "autogeneratedExamples": {} } }, + "PetOwnerStatus": { + "name": { + "typeId": "PetOwnerStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "name": "PetOwnerStatus" + }, + "shape": { + "values": [ + { + "name": "available" + }, + { + "name": "pending" + }, + { + "name": "sold" + } + ], + "type": "enum" + }, + "autogeneratedExamples": [], + "userProvidedExamples": [], + "docs": "pet status in the store", + "referencedTypes": {}, + "inline": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": { + "PetOwnerStatus_example_autogenerated": "available" + } + } + }, "PetOwner": { "name": { "typeId": "PetOwner", @@ -1526,41 +1561,6 @@ "autogeneratedExamples": {} } }, - "PetOwnerStatus": { - "name": { - "typeId": "PetOwnerStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PetOwnerStatus" - }, - "shape": { - "values": [ - { - "name": "available" - }, - { - "name": "pending" - }, - { - "name": "sold" - } - ], - "type": "enum" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "docs": "pet status in the store", - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "PetOwnerStatus_example_autogenerated": "available" - } - } - }, "ChannelsPetownersSubscribeParamsData": { "name": { "typeId": "ChannelsPetownersSubscribeParamsData", From 11d04fcb21412b509bbea3e853381647627af10e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:32:29 +0000 Subject: [PATCH 08/29] fix: update convertIRtoJsonSchema snapshots for allof/allof-inline Co-Authored-By: bot_apk --- .../allof-inline/type__AuditInfo.json | 49 ++++++++++ .../allof-inline/type__CombinedEntity.json | 45 ++++++++++ .../type__CombinedEntityStatus.json | 8 ++ .../allof-inline/type__Describable.json | 27 ++++++ .../allof-inline/type__Identifiable.json | 23 +++++ .../allof-inline/type__PaginatedResult.json | 50 +++++++++++ .../allof-inline/type__PagingCursors.json | 23 +++++ .../type__RuleExecutionContext.json | 9 ++ .../allof-inline/type__RuleResponse.json | 90 +++++++++++++++++++ .../type__RuleResponseStatus.json | 9 ++ .../allof-inline/type__RuleType.json | 27 ++++++ .../type__RuleTypeSearchResponse.json | 75 ++++++++++++++++ .../allof-inline/type__User.json | 17 ++++ .../type__UserSearchResponse.json | 65 ++++++++++++++ .../__snapshots__/allof/type__AuditInfo.json | 49 ++++++++++ .../allof/type__CombinedEntity.json | 45 ++++++++++ .../allof/type__CombinedEntityStatus.json | 8 ++ .../allof/type__Describable.json | 27 ++++++ .../allof/type__Identifiable.json | 23 +++++ .../allof/type__PaginatedResult.json | 50 +++++++++++ .../allof/type__PagingCursors.json | 23 +++++ .../allof/type__RuleExecutionContext.json | 9 ++ .../allof/type__RuleResponse.json | 90 +++++++++++++++++++ .../allof/type__RuleResponseStatus.json | 9 ++ .../__snapshots__/allof/type__RuleType.json | 27 ++++++ .../allof/type__RuleTypeSearchResponse.json | 75 ++++++++++++++++ .../__snapshots__/allof/type__User.json | 17 ++++ .../allof/type__UserSearchResponse.json | 65 ++++++++++++++ 28 files changed, 1034 insertions(+) create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__AuditInfo.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntity.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntityStatus.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Describable.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Identifiable.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PaginatedResult.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PagingCursors.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleExecutionContext.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponse.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponseStatus.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleType.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleTypeSearchResponse.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__User.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__UserSearchResponse.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__AuditInfo.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntity.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntityStatus.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Describable.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Identifiable.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PaginatedResult.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PagingCursors.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleExecutionContext.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponse.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponseStatus.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleType.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleTypeSearchResponse.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__User.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__UserSearchResponse.json diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__AuditInfo.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__AuditInfo.json new file mode 100644 index 000000000000..4d5ef60c2788 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__AuditInfo.json @@ -0,0 +1,49 @@ +{ + "type": "object", + "properties": { + "createdBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "createdDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "modifiedBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "modifiedDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntity.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntity.json new file mode 100644 index 000000000000..f0fc19dd490d --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntity.json @@ -0,0 +1,45 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "summary": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "status": { + "$ref": "#/definitions/CombinedEntityStatus" + } + }, + "required": [ + "id", + "status" + ], + "additionalProperties": false, + "definitions": { + "CombinedEntityStatus": { + "type": "string", + "enum": [ + "active", + "archived" + ] + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntityStatus.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntityStatus.json new file mode 100644 index 000000000000..6f3966bf00a4 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__CombinedEntityStatus.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "enum": [ + "active", + "archived" + ], + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Describable.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Describable.json new file mode 100644 index 000000000000..074237ad3a48 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Describable.json @@ -0,0 +1,27 @@ +{ + "type": "object", + "properties": { + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "summary": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Identifiable.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Identifiable.json new file mode 100644 index 000000000000..2cb8231306b8 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Identifiable.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PaginatedResult.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PaginatedResult.json new file mode 100644 index 000000000000..6b9b2f9e8f4e --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PaginatedResult.json @@ -0,0 +1,50 @@ +{ + "type": "object", + "properties": { + "paging": { + "$ref": "#/definitions/PagingCursors" + }, + "results": { + "type": "array", + "items": { + "type": [ + "string", + "number", + "boolean", + "object", + "array", + "null" + ] + } + } + }, + "required": [ + "paging", + "results" + ], + "additionalProperties": false, + "definitions": { + "PagingCursors": { + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PagingCursors.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PagingCursors.json new file mode 100644 index 000000000000..ba54102aadcf --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__PagingCursors.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleExecutionContext.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleExecutionContext.json new file mode 100644 index 000000000000..f4f3ef54c773 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleExecutionContext.json @@ -0,0 +1,9 @@ +{ + "type": "string", + "enum": [ + "prod", + "staging", + "dev" + ], + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponse.json new file mode 100644 index 000000000000..9d95097233bc --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponse.json @@ -0,0 +1,90 @@ +{ + "type": "object", + "properties": { + "createdBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "createdDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "modifiedBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "modifiedDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/RuleResponseStatus" + }, + "executionContext": { + "oneOf": [ + { + "$ref": "#/definitions/RuleExecutionContext" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "status" + ], + "additionalProperties": false, + "definitions": { + "RuleResponseStatus": { + "type": "string", + "enum": [ + "active", + "inactive", + "draft" + ] + }, + "RuleExecutionContext": { + "type": "string", + "enum": [ + "prod", + "staging", + "dev" + ] + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponseStatus.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponseStatus.json new file mode 100644 index 000000000000..6937b04093ad --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleResponseStatus.json @@ -0,0 +1,9 @@ +{ + "type": "string", + "enum": [ + "active", + "inactive", + "draft" + ], + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleType.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleType.json new file mode 100644 index 000000000000..0f17bcdb70dd --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleType.json @@ -0,0 +1,27 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleTypeSearchResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleTypeSearchResponse.json new file mode 100644 index 000000000000..48f72eb84f5d --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__RuleTypeSearchResponse.json @@ -0,0 +1,75 @@ +{ + "type": "object", + "properties": { + "paging": { + "$ref": "#/definitions/PagingCursors" + }, + "results": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/RuleType" + } + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "paging" + ], + "additionalProperties": false, + "definitions": { + "PagingCursors": { + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false + }, + "RuleType": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__User.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__User.json new file mode 100644 index 000000000000..3b377d3857ee --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__User.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "required": [ + "id", + "email" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__UserSearchResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__UserSearchResponse.json new file mode 100644 index 000000000000..1c28f59367ed --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__UserSearchResponse.json @@ -0,0 +1,65 @@ +{ + "type": "object", + "properties": { + "paging": { + "$ref": "#/definitions/PagingCursors" + }, + "results": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "paging" + ], + "additionalProperties": false, + "definitions": { + "PagingCursors": { + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "required": [ + "id", + "email" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__AuditInfo.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__AuditInfo.json new file mode 100644 index 000000000000..4d5ef60c2788 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__AuditInfo.json @@ -0,0 +1,49 @@ +{ + "type": "object", + "properties": { + "createdBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "createdDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "modifiedBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "modifiedDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntity.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntity.json new file mode 100644 index 000000000000..754e5900f36c --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntity.json @@ -0,0 +1,45 @@ +{ + "type": "object", + "properties": { + "status": { + "$ref": "#/definitions/CombinedEntityStatus" + }, + "id": { + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "summary": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "status", + "id" + ], + "additionalProperties": false, + "definitions": { + "CombinedEntityStatus": { + "type": "string", + "enum": [ + "active", + "archived" + ] + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntityStatus.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntityStatus.json new file mode 100644 index 000000000000..6f3966bf00a4 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__CombinedEntityStatus.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "enum": [ + "active", + "archived" + ], + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Describable.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Describable.json new file mode 100644 index 000000000000..074237ad3a48 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Describable.json @@ -0,0 +1,27 @@ +{ + "type": "object", + "properties": { + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "summary": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Identifiable.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Identifiable.json new file mode 100644 index 000000000000..2cb8231306b8 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Identifiable.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PaginatedResult.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PaginatedResult.json new file mode 100644 index 000000000000..6b9b2f9e8f4e --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PaginatedResult.json @@ -0,0 +1,50 @@ +{ + "type": "object", + "properties": { + "paging": { + "$ref": "#/definitions/PagingCursors" + }, + "results": { + "type": "array", + "items": { + "type": [ + "string", + "number", + "boolean", + "object", + "array", + "null" + ] + } + } + }, + "required": [ + "paging", + "results" + ], + "additionalProperties": false, + "definitions": { + "PagingCursors": { + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PagingCursors.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PagingCursors.json new file mode 100644 index 000000000000..ba54102aadcf --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__PagingCursors.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleExecutionContext.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleExecutionContext.json new file mode 100644 index 000000000000..f4f3ef54c773 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleExecutionContext.json @@ -0,0 +1,9 @@ +{ + "type": "string", + "enum": [ + "prod", + "staging", + "dev" + ], + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponse.json new file mode 100644 index 000000000000..9d95097233bc --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponse.json @@ -0,0 +1,90 @@ +{ + "type": "object", + "properties": { + "createdBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "createdDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "modifiedBy": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "modifiedDateTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/RuleResponseStatus" + }, + "executionContext": { + "oneOf": [ + { + "$ref": "#/definitions/RuleExecutionContext" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "status" + ], + "additionalProperties": false, + "definitions": { + "RuleResponseStatus": { + "type": "string", + "enum": [ + "active", + "inactive", + "draft" + ] + }, + "RuleExecutionContext": { + "type": "string", + "enum": [ + "prod", + "staging", + "dev" + ] + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponseStatus.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponseStatus.json new file mode 100644 index 000000000000..6937b04093ad --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleResponseStatus.json @@ -0,0 +1,9 @@ +{ + "type": "string", + "enum": [ + "active", + "inactive", + "draft" + ], + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleType.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleType.json new file mode 100644 index 000000000000..0f17bcdb70dd --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleType.json @@ -0,0 +1,27 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleTypeSearchResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleTypeSearchResponse.json new file mode 100644 index 000000000000..3a6a4c6848fe --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__RuleTypeSearchResponse.json @@ -0,0 +1,75 @@ +{ + "type": "object", + "properties": { + "results": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/RuleType" + } + }, + { + "type": "null" + } + ] + }, + "paging": { + "$ref": "#/definitions/PagingCursors" + } + }, + "required": [ + "paging" + ], + "additionalProperties": false, + "definitions": { + "RuleType": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false + }, + "PagingCursors": { + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__User.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__User.json new file mode 100644 index 000000000000..3b377d3857ee --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__User.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "required": [ + "id", + "email" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__UserSearchResponse.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__UserSearchResponse.json new file mode 100644 index 000000000000..49d4bbc2028b --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__UserSearchResponse.json @@ -0,0 +1,65 @@ +{ + "type": "object", + "properties": { + "results": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + }, + { + "type": "null" + } + ] + }, + "paging": { + "$ref": "#/definitions/PagingCursors" + } + }, + "required": [ + "paging" + ], + "additionalProperties": false, + "definitions": { + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "required": [ + "id", + "email" + ], + "additionalProperties": false + }, + "PagingCursors": { + "type": "object", + "properties": { + "next": { + "type": "string" + }, + "previous": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "next" + ], + "additionalProperties": false + } + } +} \ No newline at end of file From 252c4613397ca22562a89135e4f0d83f092f4023 Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 09:52:02 -0400 Subject: [PATCH 09/29] update config --- seed/python-sdk/seed.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/seed/python-sdk/seed.yml b/seed/python-sdk/seed.yml index 43a30281724f..74d0902fecbb 100644 --- a/seed/python-sdk/seed.yml +++ b/seed/python-sdk/seed.yml @@ -56,6 +56,14 @@ fixtures: - customConfig: use_inheritance_for_extended_models: false outputFolder: no-inheritance-for-extended-models + allof: + - outputFolder: no-custom-config + customConfig: + enable_wire_tests: true + allof-inline: + - outputFolder: no-custom-config + customConfig: + enable_wire_tests: true circular-references: - customConfig: null outputFolder: no-custom-config From 4b0f3b1edeb0a891f6585303eb05567b1b61cee5 Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 09:55:28 -0400 Subject: [PATCH 10/29] Add case 6 --- test-definitions/fern/apis/allof/openapi.yml | 65 ++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test-definitions/fern/apis/allof/openapi.yml b/test-definitions/fern/apis/allof/openapi.yml index e1f054848c18..b8564ac0700a 100644 --- a/test-definitions/fern/apis/allof/openapi.yml +++ b/test-definitions/fern/apis/allof/openapi.yml @@ -70,6 +70,18 @@ paths: schema: $ref: "#/components/schemas/CombinedEntity" + /organizations: + get: + operationId: getOrganization + summary: Get an organization with merged object-typed properties + responses: + "200": + description: An organization whose metadata is merged from two parents + content: + application/json: + schema: + $ref: "#/components/schemas/Organization" + components: schemas: # ----------------------------------------------------------------------- @@ -283,3 +295,56 @@ components: enum: - active - archived + + # ----------------------------------------------------------------------- + # Case 6: allOf merging object-typed properties with partial key overlap + # + # Two parents define the same property (`metadata`) as an object type. + # Each parent's `metadata` shares one common key (`region`, same type) + # but also contributes a unique key. The merged result should be an + # object with all three keys: region, tier, and domain. + # ----------------------------------------------------------------------- + BaseOrg: + type: object + required: + - id + properties: + id: + type: string + metadata: + type: object + required: + - region + properties: + region: + type: string + description: Deployment region from BaseOrg. + tier: + type: string + description: Subscription tier. + + DetailedOrg: + type: object + properties: + metadata: + type: object + required: + - region + properties: + region: + type: string + description: Deployment region from DetailedOrg. + domain: + type: string + description: Custom domain name. + + Organization: + allOf: + - $ref: "#/components/schemas/BaseOrg" + - $ref: "#/components/schemas/DetailedOrg" + - type: object + required: + - name + properties: + name: + type: string From fa8952f3f5c1d497699c31fd062807c7fbe89046 Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 10:21:56 -0400 Subject: [PATCH 11/29] seed outputs --- seed/csharp-sdk/allof-inline/.editorconfig | 35 + .../allof-inline/.fern/metadata.json | 8 + .../allof-inline/.github/workflows/ci.yml | 52 + seed/csharp-sdk/allof-inline/.gitignore | 484 ++ seed/csharp-sdk/allof-inline/README.md | 216 + seed/csharp-sdk/allof-inline/SeedApi.slnx | 4 + seed/csharp-sdk/allof-inline/reference.md | 158 + seed/csharp-sdk/allof-inline/snippet.json | 65 + .../src/SeedApi.DynamicSnippets/Example0.cs | 19 + .../src/SeedApi.DynamicSnippets/Example1.cs | 21 + .../src/SeedApi.DynamicSnippets/Example2.cs | 22 + .../src/SeedApi.DynamicSnippets/Example3.cs | 22 + .../src/SeedApi.DynamicSnippets/Example4.cs | 17 + .../src/SeedApi.DynamicSnippets/Example5.cs | 17 + .../src/SeedApi.DynamicSnippets/Example6.cs | 17 + .../src/SeedApi.DynamicSnippets/Example7.cs | 17 + .../src/SeedApi.DynamicSnippets/Example8.cs | 17 + .../src/SeedApi.DynamicSnippets/Example9.cs | 17 + .../SeedApi.DynamicSnippets.csproj | 13 + .../SeedApi.Test/Core/HeadersBuilderTests.cs | 326 ++ .../Core/Json/AdditionalPropertiesTests.cs | 365 ++ .../Core/Json/DateOnlyJsonTests.cs | 100 + .../Core/Json/DateTimeJsonTests.cs | 134 + .../Core/Json/JsonAccessAttributeTests.cs | 160 + .../Core/QueryStringBuilderTests.cs | 658 +++ .../Core/QueryStringConverterTests.cs | 158 + .../Core/RawClientTests/MultipartFormTests.cs | 1120 ++++ .../RawClientTests/QueryParameterTests.cs | 108 + .../Core/RawClientTests/RetriesTests.cs | 406 ++ .../SeedApi.Test/Core/WithRawResponseTests.cs | 269 + .../SeedApi.Test/SeedApi.Test.Custom.props | 6 + .../src/SeedApi.Test/SeedApi.Test.csproj | 33 + .../src/SeedApi.Test/TestClient.cs | 6 + .../Unit/MockServer/BaseMockServerTest.cs | 37 + .../Unit/MockServer/CreateRuleTest.cs | 92 + .../Unit/MockServer/GetEntityTest.cs | 59 + .../Unit/MockServer/GetOrganizationTest.cs | 63 + .../Unit/MockServer/ListUsersTest.cs | 75 + .../Unit/MockServer/SearchRuleTypesTest.cs | 87 + .../Utils/AdditionalPropertiesComparer.cs | 219 + .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 + .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 + .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 + .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 + .../SeedApi.Test/Utils/OptionalComparer.cs | 104 + .../Utils/ReadOnlyMemoryComparer.cs | 87 + .../src/SeedApi/Core/ApiResponse.cs | 13 + .../src/SeedApi/Core/BaseRequest.cs | 67 + .../SeedApi/Core/CollectionItemSerializer.cs | 91 + .../src/SeedApi/Core/Constants.cs | 7 + .../src/SeedApi/Core/DateOnlyConverter.cs | 747 +++ .../src/SeedApi/Core/DateTimeSerializer.cs | 40 + .../src/SeedApi/Core/EmptyRequest.cs | 11 + .../src/SeedApi/Core/EncodingCache.cs | 11 + .../src/SeedApi/Core/Extensions.cs | 55 + .../src/SeedApi/Core/FormUrlEncoder.cs | 33 + .../src/SeedApi/Core/HeaderValue.cs | 52 + .../allof-inline/src/SeedApi/Core/Headers.cs | 28 + .../src/SeedApi/Core/HeadersBuilder.cs | 197 + .../src/SeedApi/Core/HttpContentExtensions.cs | 20 + .../src/SeedApi/Core/HttpMethodExtensions.cs | 8 + .../src/SeedApi/Core/IIsRetryableContent.cs | 6 + .../src/SeedApi/Core/IRequestOptions.cs | 83 + .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 + .../src/SeedApi/Core/JsonConfiguration.cs | 275 + .../src/SeedApi/Core/JsonRequest.cs | 36 + .../src/SeedApi/Core/MultipartFormRequest.cs | 294 ++ .../src/SeedApi/Core/NullableAttribute.cs | 18 + .../src/SeedApi/Core/OneOfSerializer.cs | 145 + .../allof-inline/src/SeedApi/Core/Optional.cs | 474 ++ .../src/SeedApi/Core/OptionalAttribute.cs | 17 + .../Core/Public/AdditionalProperties.cs | 353 ++ .../src/SeedApi/Core/Public/ClientOptions.cs | 84 + .../src/SeedApi/Core/Public/FileParameter.cs | 63 + .../src/SeedApi/Core/Public/RawResponse.cs | 24 + .../src/SeedApi/Core/Public/RequestOptions.cs | 86 + .../Core/Public/SeedApiApiException.cs | 22 + .../SeedApi/Core/Public/SeedApiEnvironment.cs | 7 + .../SeedApi/Core/Public/SeedApiException.cs | 7 + .../src/SeedApi/Core/Public/Version.cs | 7 + .../SeedApi/Core/Public/WithRawResponse.cs | 18 + .../Core/Public/WithRawResponseTask.cs | 144 + .../src/SeedApi/Core/QueryStringBuilder.cs | 654 +++ .../src/SeedApi/Core/QueryStringConverter.cs | 259 + .../src/SeedApi/Core/RawClient.cs | 343 ++ .../src/SeedApi/Core/RawResponse.cs | 24 + .../src/SeedApi/Core/ResponseHeaders.cs | 108 + .../src/SeedApi/Core/StreamRequest.cs | 29 + .../src/SeedApi/Core/StringEnum.cs | 6 + .../src/SeedApi/Core/StringEnumExtensions.cs | 6 + .../src/SeedApi/Core/ValueConvert.cs | 115 + .../src/SeedApi/ISeedApiClient.cs | 31 + .../src/SeedApi/Requests/RuleCreateRequest.cs | 20 + .../Requests/SearchRuleTypesRequest.cs | 17 + .../src/SeedApi/SeedApi.Custom.props | 20 + .../allof-inline/src/SeedApi/SeedApi.csproj | 56 + .../allof-inline/src/SeedApi/SeedApiClient.cs | 429 ++ .../src/SeedApi/Types/AuditInfo.cs | 56 + .../allof-inline/src/SeedApi/Types/BaseOrg.cs | 31 + .../src/SeedApi/Types/BaseOrgMetadata.cs | 37 + .../src/SeedApi/Types/CombinedEntity.cs | 46 + .../src/SeedApi/Types/CombinedEntityStatus.cs | 115 + .../src/SeedApi/Types/Describable.cs | 37 + .../src/SeedApi/Types/DetailedOrg.cs | 28 + .../src/SeedApi/Types/DetailedOrgMetadata.cs | 37 + .../src/SeedApi/Types/Identifiable.cs | 37 + .../src/SeedApi/Types/Organization.cs | 34 + .../src/SeedApi/Types/OrganizationMetadata.cs | 37 + .../src/SeedApi/Types/PaginatedResult.cs | 34 + .../src/SeedApi/Types/PagingCursors.cs | 37 + .../src/SeedApi/Types/RuleExecutionContext.cs | 119 + .../src/SeedApi/Types/RuleResponse.cs | 65 + .../src/SeedApi/Types/RuleResponseStatus.cs | 119 + .../src/SeedApi/Types/RuleType.cs | 34 + .../SeedApi/Types/RuleTypeSearchResponse.cs | 34 + .../allof-inline/src/SeedApi/Types/User.cs | 31 + .../src/SeedApi/Types/UserSearchResponse.cs | 34 + seed/csharp-sdk/allof/.editorconfig | 35 + seed/csharp-sdk/allof/.fern/metadata.json | 8 + .../csharp-sdk/allof/.github/workflows/ci.yml | 52 + seed/csharp-sdk/allof/.gitignore | 484 ++ seed/csharp-sdk/allof/README.md | 216 + seed/csharp-sdk/allof/SeedApi.slnx | 4 + seed/csharp-sdk/allof/reference.md | 158 + seed/csharp-sdk/allof/snippet.json | 65 + .../src/SeedApi.DynamicSnippets/Example0.cs | 19 + .../src/SeedApi.DynamicSnippets/Example1.cs | 21 + .../src/SeedApi.DynamicSnippets/Example2.cs | 22 + .../src/SeedApi.DynamicSnippets/Example3.cs | 22 + .../src/SeedApi.DynamicSnippets/Example4.cs | 17 + .../src/SeedApi.DynamicSnippets/Example5.cs | 17 + .../src/SeedApi.DynamicSnippets/Example6.cs | 17 + .../src/SeedApi.DynamicSnippets/Example7.cs | 17 + .../src/SeedApi.DynamicSnippets/Example8.cs | 17 + .../src/SeedApi.DynamicSnippets/Example9.cs | 17 + .../SeedApi.DynamicSnippets.csproj | 13 + .../SeedApi.Test/Core/HeadersBuilderTests.cs | 326 ++ .../Core/Json/AdditionalPropertiesTests.cs | 365 ++ .../Core/Json/DateOnlyJsonTests.cs | 100 + .../Core/Json/DateTimeJsonTests.cs | 134 + .../Core/Json/JsonAccessAttributeTests.cs | 160 + .../Core/QueryStringBuilderTests.cs | 658 +++ .../Core/QueryStringConverterTests.cs | 158 + .../Core/RawClientTests/MultipartFormTests.cs | 1120 ++++ .../RawClientTests/QueryParameterTests.cs | 108 + .../Core/RawClientTests/RetriesTests.cs | 406 ++ .../SeedApi.Test/Core/WithRawResponseTests.cs | 269 + .../SeedApi.Test/SeedApi.Test.Custom.props | 6 + .../src/SeedApi.Test/SeedApi.Test.csproj | 33 + .../allof/src/SeedApi.Test/TestClient.cs | 6 + .../Unit/MockServer/BaseMockServerTest.cs | 37 + .../Unit/MockServer/CreateRuleTest.cs | 92 + .../Unit/MockServer/GetEntityTest.cs | 59 + .../Unit/MockServer/GetOrganizationTest.cs | 63 + .../Unit/MockServer/ListUsersTest.cs | 75 + .../Unit/MockServer/SearchRuleTypesTest.cs | 87 + .../Utils/AdditionalPropertiesComparer.cs | 219 + .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 + .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 + .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 + .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 + .../SeedApi.Test/Utils/OptionalComparer.cs | 104 + .../Utils/ReadOnlyMemoryComparer.cs | 87 + .../allof/src/SeedApi/Core/ApiResponse.cs | 13 + .../allof/src/SeedApi/Core/BaseRequest.cs | 67 + .../SeedApi/Core/CollectionItemSerializer.cs | 91 + .../allof/src/SeedApi/Core/Constants.cs | 7 + .../src/SeedApi/Core/DateOnlyConverter.cs | 747 +++ .../src/SeedApi/Core/DateTimeSerializer.cs | 40 + .../allof/src/SeedApi/Core/EmptyRequest.cs | 11 + .../allof/src/SeedApi/Core/EncodingCache.cs | 11 + .../allof/src/SeedApi/Core/Extensions.cs | 55 + .../allof/src/SeedApi/Core/FormUrlEncoder.cs | 33 + .../allof/src/SeedApi/Core/HeaderValue.cs | 52 + .../allof/src/SeedApi/Core/Headers.cs | 28 + .../allof/src/SeedApi/Core/HeadersBuilder.cs | 197 + .../src/SeedApi/Core/HttpContentExtensions.cs | 20 + .../src/SeedApi/Core/HttpMethodExtensions.cs | 8 + .../src/SeedApi/Core/IIsRetryableContent.cs | 6 + .../allof/src/SeedApi/Core/IRequestOptions.cs | 83 + .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 + .../src/SeedApi/Core/JsonConfiguration.cs | 275 + .../allof/src/SeedApi/Core/JsonRequest.cs | 36 + .../src/SeedApi/Core/MultipartFormRequest.cs | 294 ++ .../src/SeedApi/Core/NullableAttribute.cs | 18 + .../allof/src/SeedApi/Core/OneOfSerializer.cs | 145 + .../allof/src/SeedApi/Core/Optional.cs | 474 ++ .../src/SeedApi/Core/OptionalAttribute.cs | 17 + .../Core/Public/AdditionalProperties.cs | 353 ++ .../src/SeedApi/Core/Public/ClientOptions.cs | 84 + .../src/SeedApi/Core/Public/FileParameter.cs | 63 + .../src/SeedApi/Core/Public/RawResponse.cs | 24 + .../src/SeedApi/Core/Public/RequestOptions.cs | 86 + .../Core/Public/SeedApiApiException.cs | 22 + .../SeedApi/Core/Public/SeedApiEnvironment.cs | 7 + .../SeedApi/Core/Public/SeedApiException.cs | 7 + .../allof/src/SeedApi/Core/Public/Version.cs | 7 + .../SeedApi/Core/Public/WithRawResponse.cs | 18 + .../Core/Public/WithRawResponseTask.cs | 144 + .../src/SeedApi/Core/QueryStringBuilder.cs | 654 +++ .../src/SeedApi/Core/QueryStringConverter.cs | 259 + .../allof/src/SeedApi/Core/RawClient.cs | 343 ++ .../allof/src/SeedApi/Core/RawResponse.cs | 24 + .../allof/src/SeedApi/Core/ResponseHeaders.cs | 108 + .../allof/src/SeedApi/Core/StreamRequest.cs | 29 + .../allof/src/SeedApi/Core/StringEnum.cs | 6 + .../src/SeedApi/Core/StringEnumExtensions.cs | 6 + .../allof/src/SeedApi/Core/ValueConvert.cs | 115 + .../allof/src/SeedApi/ISeedApiClient.cs | 31 + .../src/SeedApi/Requests/RuleCreateRequest.cs | 20 + .../Requests/SearchRuleTypesRequest.cs | 17 + .../allof/src/SeedApi/SeedApi.Custom.props | 20 + .../allof/src/SeedApi/SeedApi.csproj | 56 + .../allof/src/SeedApi/SeedApiClient.cs | 429 ++ .../allof/src/SeedApi/Types/AuditInfo.cs | 56 + .../allof/src/SeedApi/Types/BaseOrg.cs | 31 + .../src/SeedApi/Types/BaseOrgMetadata.cs | 37 + .../allof/src/SeedApi/Types/CombinedEntity.cs | 46 + .../src/SeedApi/Types/CombinedEntityStatus.cs | 115 + .../allof/src/SeedApi/Types/Describable.cs | 37 + .../allof/src/SeedApi/Types/DetailedOrg.cs | 28 + .../src/SeedApi/Types/DetailedOrgMetadata.cs | 37 + .../allof/src/SeedApi/Types/Identifiable.cs | 37 + .../allof/src/SeedApi/Types/Organization.cs | 34 + .../src/SeedApi/Types/PaginatedResult.cs | 34 + .../allof/src/SeedApi/Types/PagingCursors.cs | 37 + .../src/SeedApi/Types/RuleExecutionContext.cs | 119 + .../allof/src/SeedApi/Types/RuleResponse.cs | 65 + .../src/SeedApi/Types/RuleResponseStatus.cs | 119 + .../allof/src/SeedApi/Types/RuleType.cs | 34 + .../SeedApi/Types/RuleTypeSearchResponse.cs | 34 + .../allof/src/SeedApi/Types/User.cs | 31 + .../src/SeedApi/Types/UserSearchResponse.cs | 34 + seed/csharp-sdk/seed.yml | 2 + seed/go-sdk/allof-inline/.fern/metadata.json | 10 + .../allof-inline/.github/workflows/ci.yml | 62 + seed/go-sdk/allof-inline/README.md | 195 + seed/go-sdk/allof-inline/client/client.go | 109 + .../go-sdk/allof-inline/client/client_test.go | 45 + seed/go-sdk/allof-inline/client/raw_client.go | 238 + seed/go-sdk/allof-inline/core/api_error.go | 47 + seed/go-sdk/allof-inline/core/http.go | 15 + .../allof-inline/core/request_option.go | 119 + .../dynamic-snippets/example0/snippet.go | 22 + .../dynamic-snippets/example1/snippet.go | 26 + .../dynamic-snippets/example2/snippet.go | 25 + .../dynamic-snippets/example3/snippet.go | 25 + .../dynamic-snippets/example4/snippet.go | 19 + .../dynamic-snippets/example5/snippet.go | 19 + .../dynamic-snippets/example6/snippet.go | 19 + .../dynamic-snippets/example7/snippet.go | 19 + .../dynamic-snippets/example8/snippet.go | 19 + .../dynamic-snippets/example9/snippet.go | 19 + seed/go-sdk/allof-inline/environments.go | 13 + seed/go-sdk/allof-inline/error_codes.go | 9 + seed/go-sdk/allof-inline/file_param.go | 41 + seed/go-sdk/allof-inline/go.mod | 16 + seed/go-sdk/allof-inline/go.sum | 12 + seed/go-sdk/allof-inline/internal/caller.go | 311 ++ .../allof-inline/internal/caller_test.go | 705 +++ .../allof-inline/internal/error_decoder.go | 64 + .../internal/error_decoder_test.go | 59 + .../allof-inline/internal/explicit_fields.go | 116 + .../internal/explicit_fields_test.go | 645 +++ .../allof-inline/internal/extra_properties.go | 141 + .../internal/extra_properties_test.go | 228 + seed/go-sdk/allof-inline/internal/http.go | 71 + seed/go-sdk/allof-inline/internal/query.go | 358 ++ .../allof-inline/internal/query_test.go | 395 ++ seed/go-sdk/allof-inline/internal/retrier.go | 239 + .../allof-inline/internal/retrier_test.go | 352 ++ seed/go-sdk/allof-inline/internal/stringer.go | 13 + seed/go-sdk/allof-inline/internal/time.go | 165 + .../allof-inline/option/request_option.go | 73 + seed/go-sdk/allof-inline/pointer.go | 137 + seed/go-sdk/allof-inline/pointer_test.go | 211 + seed/go-sdk/allof-inline/reference.md | 186 + seed/go-sdk/allof-inline/snippet.json | 59 + seed/go-sdk/allof-inline/types.go | 2091 ++++++++ seed/go-sdk/allof-inline/types_test.go | 4688 +++++++++++++++++ seed/go-sdk/allof/.fern/metadata.json | 10 + seed/go-sdk/allof/.github/workflows/ci.yml | 62 + seed/go-sdk/allof/README.md | 195 + seed/go-sdk/allof/client/client.go | 109 + seed/go-sdk/allof/client/client_test.go | 45 + seed/go-sdk/allof/client/raw_client.go | 238 + seed/go-sdk/allof/core/api_error.go | 47 + seed/go-sdk/allof/core/http.go | 15 + seed/go-sdk/allof/core/request_option.go | 119 + .../dynamic-snippets/example0/snippet.go | 22 + .../dynamic-snippets/example1/snippet.go | 26 + .../dynamic-snippets/example2/snippet.go | 25 + .../dynamic-snippets/example3/snippet.go | 25 + .../dynamic-snippets/example4/snippet.go | 19 + .../dynamic-snippets/example5/snippet.go | 19 + .../dynamic-snippets/example6/snippet.go | 19 + .../dynamic-snippets/example7/snippet.go | 19 + .../dynamic-snippets/example8/snippet.go | 19 + .../dynamic-snippets/example9/snippet.go | 19 + seed/go-sdk/allof/environments.go | 13 + seed/go-sdk/allof/error_codes.go | 9 + seed/go-sdk/allof/file_param.go | 41 + seed/go-sdk/allof/go.mod | 16 + seed/go-sdk/allof/go.sum | 12 + seed/go-sdk/allof/internal/caller.go | 311 ++ seed/go-sdk/allof/internal/caller_test.go | 705 +++ seed/go-sdk/allof/internal/error_decoder.go | 64 + .../allof/internal/error_decoder_test.go | 59 + seed/go-sdk/allof/internal/explicit_fields.go | 116 + .../allof/internal/explicit_fields_test.go | 645 +++ .../go-sdk/allof/internal/extra_properties.go | 141 + .../allof/internal/extra_properties_test.go | 228 + seed/go-sdk/allof/internal/http.go | 71 + seed/go-sdk/allof/internal/query.go | 358 ++ seed/go-sdk/allof/internal/query_test.go | 395 ++ seed/go-sdk/allof/internal/retrier.go | 239 + seed/go-sdk/allof/internal/retrier_test.go | 352 ++ seed/go-sdk/allof/internal/stringer.go | 13 + seed/go-sdk/allof/internal/time.go | 165 + seed/go-sdk/allof/option/request_option.go | 73 + seed/go-sdk/allof/pointer.go | 137 + seed/go-sdk/allof/pointer_test.go | 211 + seed/go-sdk/allof/reference.md | 186 + seed/go-sdk/allof/snippet.json | 59 + seed/go-sdk/allof/types.go | 1989 +++++++ seed/go-sdk/allof/types_test.go | 4473 ++++++++++++++++ .../java-sdk/allof-inline/.fern/metadata.json | 7 + .../allof-inline/.github/workflows/ci.yml | 65 + seed/java-sdk/allof-inline/.gitignore | 24 + seed/java-sdk/allof-inline/README.md | 235 + seed/java-sdk/allof-inline/build.gradle | 102 + seed/java-sdk/allof-inline/reference.md | 174 + .../allof-inline/sample-app/build.gradle | 19 + .../sample-app/src/main/java/sample/App.java | 13 + seed/java-sdk/allof-inline/settings.gradle | 3 + seed/java-sdk/allof-inline/snippet.json | 135 + .../com/seed/api/AsyncRawSeedApiClient.java | 323 ++ .../java/com/seed/api/AsyncSeedApiClient.java | 86 + .../seed/api/AsyncSeedApiClientBuilder.java | 194 + .../java/com/seed/api/RawSeedApiClient.java | 249 + .../main/java/com/seed/api/SeedApiClient.java | 84 + .../com/seed/api/SeedApiClientBuilder.java | 194 + .../java/com/seed/api/core/ClientOptions.java | 221 + .../java/com/seed/api/core/ConsoleLogger.java | 51 + .../seed/api/core/DateTimeDeserializer.java | 55 + .../com/seed/api/core/DoubleSerializer.java | 43 + .../java/com/seed/api/core/Environment.java | 22 + .../java/com/seed/api/core/FileStream.java | 60 + .../main/java/com/seed/api/core/ILogger.java | 38 + .../seed/api/core/InputStreamRequestBody.java | 74 + .../java/com/seed/api/core/LogConfig.java | 98 + .../main/java/com/seed/api/core/LogLevel.java | 36 + .../main/java/com/seed/api/core/Logger.java | 97 + .../com/seed/api/core/LoggingInterceptor.java | 104 + .../java/com/seed/api/core/MediaTypes.java | 13 + .../main/java/com/seed/api/core/Nullable.java | 140 + .../seed/api/core/NullableNonemptyFilter.java | 22 + .../java/com/seed/api/core/ObjectMappers.java | 46 + .../com/seed/api/core/QueryStringMapper.java | 142 + .../com/seed/api/core/RequestOptions.java | 118 + .../api/core/ResponseBodyInputStream.java | 45 + .../com/seed/api/core/ResponseBodyReader.java | 44 + .../com/seed/api/core/RetryInterceptor.java | 181 + .../api/core/Rfc2822DateTimeDeserializer.java | 25 + .../seed/api/core/SeedApiApiException.java | 73 + .../com/seed/api/core/SeedApiException.java | 17 + .../seed/api/core/SeedApiHttpResponse.java | 37 + .../main/java/com/seed/api/core/SseEvent.java | 114 + .../com/seed/api/core/SseEventParser.java | 228 + .../main/java/com/seed/api/core/Stream.java | 513 ++ .../java/com/seed/api/core/Suppliers.java | 23 + .../seed/api/requests/RuleCreateRequest.java | 142 + .../api/requests/SearchRuleTypesRequest.java | 105 + .../java/com/seed/api/types/AuditInfo.java | 204 + .../main/java/com/seed/api/types/BaseOrg.java | 148 + .../com/seed/api/types/BaseOrgMetadata.java | 172 + .../com/seed/api/types/CombinedEntity.java | 243 + .../seed/api/types/CombinedEntityStatus.java | 83 + .../java/com/seed/api/types/Describable.java | 139 + .../java/com/seed/api/types/DetailedOrg.java | 105 + .../seed/api/types/DetailedOrgMetadata.java | 172 + .../java/com/seed/api/types/Identifiable.java | 172 + .../java/com/seed/api/types/Organization.java | 171 + .../seed/api/types/OrganizationMetadata.java | 172 + .../com/seed/api/types/PaginatedResult.java | 179 + .../com/seed/api/types/PagingCursors.java | 172 + .../seed/api/types/RuleExecutionContext.java | 93 + .../java/com/seed/api/types/RuleResponse.java | 390 ++ .../seed/api/types/RuleResponseStatus.java | 93 + .../java/com/seed/api/types/RuleType.java | 170 + .../api/types/RuleTypeSearchResponse.java | 163 + .../main/java/com/seed/api/types/User.java | 140 + .../seed/api/types/UserSearchResponse.java | 163 + .../src/main/java/com/snippets/Example0.java | 13 + .../src/main/java/com/snippets/Example1.java | 13 + .../src/main/java/com/snippets/Example2.java | 17 + .../src/main/java/com/snippets/Example3.java | 17 + .../src/main/java/com/snippets/Example4.java | 12 + .../src/main/java/com/snippets/Example5.java | 12 + .../src/main/java/com/snippets/Example6.java | 12 + .../src/main/java/com/snippets/Example7.java | 12 + .../src/main/java/com/snippets/Example8.java | 12 + .../src/main/java/com/snippets/Example9.java | 12 + .../test/java/com/seed/api/StreamTest.java | 120 + .../test/java/com/seed/api/TestClient.java | 11 + .../seed/api/core/QueryStringMapperTest.java | 339 ++ seed/java-sdk/allof/.fern/metadata.json | 7 + seed/java-sdk/allof/.github/workflows/ci.yml | 65 + seed/java-sdk/allof/.gitignore | 24 + seed/java-sdk/allof/README.md | 235 + seed/java-sdk/allof/build.gradle | 102 + seed/java-sdk/allof/reference.md | 174 + seed/java-sdk/allof/sample-app/build.gradle | 19 + .../sample-app/src/main/java/sample/App.java | 13 + seed/java-sdk/allof/settings.gradle | 3 + seed/java-sdk/allof/snippet.json | 135 + .../com/seed/api/AsyncRawSeedApiClient.java | 323 ++ .../java/com/seed/api/AsyncSeedApiClient.java | 86 + .../seed/api/AsyncSeedApiClientBuilder.java | 194 + .../java/com/seed/api/RawSeedApiClient.java | 249 + .../main/java/com/seed/api/SeedApiClient.java | 84 + .../com/seed/api/SeedApiClientBuilder.java | 194 + .../java/com/seed/api/core/ClientOptions.java | 221 + .../java/com/seed/api/core/ConsoleLogger.java | 51 + .../seed/api/core/DateTimeDeserializer.java | 55 + .../com/seed/api/core/DoubleSerializer.java | 43 + .../java/com/seed/api/core/Environment.java | 22 + .../java/com/seed/api/core/FileStream.java | 60 + .../main/java/com/seed/api/core/ILogger.java | 38 + .../seed/api/core/InputStreamRequestBody.java | 74 + .../java/com/seed/api/core/LogConfig.java | 98 + .../main/java/com/seed/api/core/LogLevel.java | 36 + .../main/java/com/seed/api/core/Logger.java | 97 + .../com/seed/api/core/LoggingInterceptor.java | 104 + .../java/com/seed/api/core/MediaTypes.java | 13 + .../main/java/com/seed/api/core/Nullable.java | 140 + .../seed/api/core/NullableNonemptyFilter.java | 22 + .../java/com/seed/api/core/ObjectMappers.java | 46 + .../com/seed/api/core/QueryStringMapper.java | 142 + .../com/seed/api/core/RequestOptions.java | 118 + .../api/core/ResponseBodyInputStream.java | 45 + .../com/seed/api/core/ResponseBodyReader.java | 44 + .../com/seed/api/core/RetryInterceptor.java | 181 + .../api/core/Rfc2822DateTimeDeserializer.java | 25 + .../seed/api/core/SeedApiApiException.java | 73 + .../com/seed/api/core/SeedApiException.java | 17 + .../seed/api/core/SeedApiHttpResponse.java | 37 + .../main/java/com/seed/api/core/SseEvent.java | 114 + .../com/seed/api/core/SseEventParser.java | 228 + .../main/java/com/seed/api/core/Stream.java | 513 ++ .../java/com/seed/api/core/Suppliers.java | 23 + .../seed/api/requests/RuleCreateRequest.java | 142 + .../api/requests/SearchRuleTypesRequest.java | 105 + .../java/com/seed/api/types/AuditInfo.java | 208 + .../main/java/com/seed/api/types/BaseOrg.java | 148 + .../com/seed/api/types/BaseOrgMetadata.java | 172 + .../com/seed/api/types/CombinedEntity.java | 243 + .../seed/api/types/CombinedEntityStatus.java | 83 + .../java/com/seed/api/types/Describable.java | 139 + .../java/com/seed/api/types/DetailedOrg.java | 105 + .../seed/api/types/DetailedOrgMetadata.java | 172 + .../java/com/seed/api/types/IAuditInfo.java | 17 + .../java/com/seed/api/types/Identifiable.java | 172 + .../java/com/seed/api/types/Organization.java | 171 + .../com/seed/api/types/PaginatedResult.java | 179 + .../com/seed/api/types/PagingCursors.java | 172 + .../seed/api/types/RuleExecutionContext.java | 93 + .../java/com/seed/api/types/RuleResponse.java | 394 ++ .../seed/api/types/RuleResponseStatus.java | 93 + .../java/com/seed/api/types/RuleType.java | 170 + .../api/types/RuleTypeSearchResponse.java | 163 + .../main/java/com/seed/api/types/User.java | 140 + .../seed/api/types/UserSearchResponse.java | 163 + .../src/main/java/com/snippets/Example0.java | 13 + .../src/main/java/com/snippets/Example1.java | 13 + .../src/main/java/com/snippets/Example2.java | 17 + .../src/main/java/com/snippets/Example3.java | 17 + .../src/main/java/com/snippets/Example4.java | 12 + .../src/main/java/com/snippets/Example5.java | 12 + .../src/main/java/com/snippets/Example6.java | 12 + .../src/main/java/com/snippets/Example7.java | 12 + .../src/main/java/com/snippets/Example8.java | 12 + .../src/main/java/com/snippets/Example9.java | 12 + .../test/java/com/seed/api/StreamTest.java | 120 + .../test/java/com/seed/api/TestClient.java | 11 + .../seed/api/core/QueryStringMapperTest.java | 339 ++ seed/php-sdk/allof-inline/.fern/metadata.json | 7 + .../allof-inline/.github/workflows/ci.yml | 52 + seed/php-sdk/allof-inline/.gitignore | 5 + seed/php-sdk/allof-inline/README.md | 169 + seed/php-sdk/allof-inline/composer.json | 46 + seed/php-sdk/allof-inline/phpstan.neon | 6 + seed/php-sdk/allof-inline/phpunit.xml | 7 + seed/php-sdk/allof-inline/reference.md | 171 + seed/php-sdk/allof-inline/snippet.json | 0 .../src/Core/Client/BaseApiRequest.php | 22 + .../src/Core/Client/HttpClientBuilder.php | 56 + .../src/Core/Client/HttpMethod.php | 12 + .../src/Core/Client/MockHttpClient.php | 75 + .../src/Core/Client/RawClient.php | 310 ++ .../src/Core/Client/RetryDecoratingClient.php | 241 + .../src/Core/Json/JsonApiRequest.php | 28 + .../src/Core/Json/JsonDecoder.php | 161 + .../src/Core/Json/JsonDeserializer.php | 218 + .../src/Core/Json/JsonEncoder.php | 20 + .../src/Core/Json/JsonProperty.php | 13 + .../src/Core/Json/JsonSerializableType.php | 225 + .../src/Core/Json/JsonSerializer.php | 205 + .../allof-inline/src/Core/Json/Utils.php | 62 + .../Core/Multipart/MultipartApiRequest.php | 28 + .../src/Core/Multipart/MultipartFormData.php | 58 + .../Core/Multipart/MultipartFormDataPart.php | 62 + .../allof-inline/src/Core/Types/ArrayType.php | 16 + .../allof-inline/src/Core/Types/Constant.php | 12 + .../allof-inline/src/Core/Types/Date.php | 16 + .../allof-inline/src/Core/Types/Union.php | 62 + .../php-sdk/allof-inline/src/Environments.php | 8 + .../src/Exceptions/SeedApiException.php | 53 + .../src/Exceptions/SeedException.php | 12 + .../src/Requests/RuleCreateRequest.php | 35 + .../src/Requests/SearchRuleTypesRequest.php | 24 + seed/php-sdk/allof-inline/src/SeedClient.php | 302 ++ .../allof-inline/src/Types/AuditInfo.php | 63 + .../allof-inline/src/Types/BaseOrg.php | 42 + .../src/Types/BaseOrgMetadata.php | 42 + .../allof-inline/src/Types/CombinedEntity.php | 58 + .../src/Types/CombinedEntityStatus.php | 9 + .../allof-inline/src/Types/Describable.php | 42 + .../allof-inline/src/Types/DetailedOrg.php | 34 + .../src/Types/DetailedOrgMetadata.php | 42 + .../allof-inline/src/Types/Identifiable.php | 42 + .../allof-inline/src/Types/Organization.php | 50 + .../src/Types/OrganizationMetadata.php | 42 + .../src/Types/PaginatedResult.php | 43 + .../allof-inline/src/Types/PagingCursors.php | 42 + .../src/Types/RuleExecutionContext.php | 10 + .../allof-inline/src/Types/RuleResponse.php | 92 + .../src/Types/RuleResponseStatus.php | 10 + .../allof-inline/src/Types/RuleType.php | 50 + .../src/Types/RuleTypeSearchResponse.php | 43 + seed/php-sdk/allof-inline/src/Types/User.php | 42 + .../src/Types/UserSearchResponse.php | 43 + seed/php-sdk/allof-inline/src/Utils/File.php | 129 + .../src/dynamic-snippets/example0/snippet.php | 15 + .../src/dynamic-snippets/example1/snippet.php | 17 + .../src/dynamic-snippets/example2/snippet.php | 19 + .../src/dynamic-snippets/example3/snippet.php | 19 + .../src/dynamic-snippets/example4/snippet.php | 12 + .../src/dynamic-snippets/example5/snippet.php | 12 + .../src/dynamic-snippets/example6/snippet.php | 12 + .../src/dynamic-snippets/example7/snippet.php | 12 + .../src/dynamic-snippets/example8/snippet.php | 12 + .../src/dynamic-snippets/example9/snippet.php | 12 + .../tests/Core/Client/RawClientTest.php | 1074 ++++ .../Core/Json/AdditionalPropertiesTest.php | 76 + .../tests/Core/Json/DateArrayTest.php | 54 + .../tests/Core/Json/EmptyArrayTest.php | 71 + .../allof-inline/tests/Core/Json/EnumTest.php | 77 + .../tests/Core/Json/ExhaustiveTest.php | 197 + .../tests/Core/Json/InvalidTest.php | 42 + .../tests/Core/Json/NestedUnionArrayTest.php | 89 + .../tests/Core/Json/NullPropertyTest.php | 53 + .../tests/Core/Json/NullableArrayTest.php | 49 + .../tests/Core/Json/ScalarTest.php | 116 + .../tests/Core/Json/TraitTest.php | 60 + .../tests/Core/Json/UnionArrayTest.php | 57 + .../tests/Core/Json/UnionPropertyTest.php | 111 + seed/php-sdk/allof/.fern/metadata.json | 7 + seed/php-sdk/allof/.github/workflows/ci.yml | 52 + seed/php-sdk/allof/.gitignore | 5 + seed/php-sdk/allof/README.md | 169 + seed/php-sdk/allof/composer.json | 46 + seed/php-sdk/allof/phpstan.neon | 6 + seed/php-sdk/allof/phpunit.xml | 7 + seed/php-sdk/allof/reference.md | 171 + seed/php-sdk/allof/snippet.json | 0 .../allof/src/Core/Client/BaseApiRequest.php | 22 + .../src/Core/Client/HttpClientBuilder.php | 56 + .../allof/src/Core/Client/HttpMethod.php | 12 + .../allof/src/Core/Client/MockHttpClient.php | 75 + .../allof/src/Core/Client/RawClient.php | 310 ++ .../src/Core/Client/RetryDecoratingClient.php | 241 + .../allof/src/Core/Json/JsonApiRequest.php | 28 + .../allof/src/Core/Json/JsonDecoder.php | 161 + .../allof/src/Core/Json/JsonDeserializer.php | 218 + .../allof/src/Core/Json/JsonEncoder.php | 20 + .../allof/src/Core/Json/JsonProperty.php | 13 + .../src/Core/Json/JsonSerializableType.php | 225 + .../allof/src/Core/Json/JsonSerializer.php | 205 + seed/php-sdk/allof/src/Core/Json/Utils.php | 62 + .../Core/Multipart/MultipartApiRequest.php | 28 + .../src/Core/Multipart/MultipartFormData.php | 58 + .../Core/Multipart/MultipartFormDataPart.php | 62 + .../allof/src/Core/Types/ArrayType.php | 16 + .../php-sdk/allof/src/Core/Types/Constant.php | 12 + seed/php-sdk/allof/src/Core/Types/Date.php | 16 + seed/php-sdk/allof/src/Core/Types/Union.php | 62 + seed/php-sdk/allof/src/Environments.php | 8 + .../allof/src/Exceptions/SeedApiException.php | 53 + .../allof/src/Exceptions/SeedException.php | 12 + .../allof/src/Requests/RuleCreateRequest.php | 35 + .../src/Requests/SearchRuleTypesRequest.php | 24 + seed/php-sdk/allof/src/SeedClient.php | 302 ++ seed/php-sdk/allof/src/Traits/AuditInfo.php | 42 + seed/php-sdk/allof/src/Types/AuditInfo.php | 63 + seed/php-sdk/allof/src/Types/BaseOrg.php | 42 + .../allof/src/Types/BaseOrgMetadata.php | 42 + .../allof/src/Types/CombinedEntity.php | 58 + .../allof/src/Types/CombinedEntityStatus.php | 9 + seed/php-sdk/allof/src/Types/Describable.php | 42 + seed/php-sdk/allof/src/Types/DetailedOrg.php | 34 + .../allof/src/Types/DetailedOrgMetadata.php | 42 + seed/php-sdk/allof/src/Types/Identifiable.php | 42 + seed/php-sdk/allof/src/Types/Organization.php | 50 + .../allof/src/Types/PaginatedResult.php | 43 + .../php-sdk/allof/src/Types/PagingCursors.php | 42 + .../allof/src/Types/RuleExecutionContext.php | 10 + seed/php-sdk/allof/src/Types/RuleResponse.php | 70 + .../allof/src/Types/RuleResponseStatus.php | 10 + seed/php-sdk/allof/src/Types/RuleType.php | 50 + .../src/Types/RuleTypeSearchResponse.php | 43 + seed/php-sdk/allof/src/Types/User.php | 42 + .../allof/src/Types/UserSearchResponse.php | 43 + seed/php-sdk/allof/src/Utils/File.php | 129 + .../src/dynamic-snippets/example0/snippet.php | 15 + .../src/dynamic-snippets/example1/snippet.php | 17 + .../src/dynamic-snippets/example2/snippet.php | 19 + .../src/dynamic-snippets/example3/snippet.php | 19 + .../src/dynamic-snippets/example4/snippet.php | 12 + .../src/dynamic-snippets/example5/snippet.php | 12 + .../src/dynamic-snippets/example6/snippet.php | 12 + .../src/dynamic-snippets/example7/snippet.php | 12 + .../src/dynamic-snippets/example8/snippet.php | 12 + .../src/dynamic-snippets/example9/snippet.php | 12 + .../allof/tests/Core/Client/RawClientTest.php | 1074 ++++ .../Core/Json/AdditionalPropertiesTest.php | 76 + .../allof/tests/Core/Json/DateArrayTest.php | 54 + .../allof/tests/Core/Json/EmptyArrayTest.php | 71 + .../allof/tests/Core/Json/EnumTest.php | 77 + .../allof/tests/Core/Json/ExhaustiveTest.php | 197 + .../allof/tests/Core/Json/InvalidTest.php | 42 + .../tests/Core/Json/NestedUnionArrayTest.php | 89 + .../tests/Core/Json/NullPropertyTest.php | 53 + .../tests/Core/Json/NullableArrayTest.php | 49 + .../allof/tests/Core/Json/ScalarTest.php | 116 + .../allof/tests/Core/Json/TraitTest.php | 60 + .../allof/tests/Core/Json/UnionArrayTest.php | 57 + .../tests/Core/Json/UnionPropertyTest.php | 111 + .../no-custom-config/.fern/metadata.json | 10 + .../no-custom-config/.github/workflows/ci.yml | 71 + .../allof-inline/no-custom-config/.gitignore | 5 + .../allof-inline/no-custom-config/README.md | 176 + .../allof-inline/no-custom-config/poetry.lock | 1613 ++++++ .../no-custom-config/pyproject.toml | 102 + .../no-custom-config/reference.md | 268 + .../no-custom-config/requirements.txt | 4 + .../no-custom-config/snippet.json | 70 + .../no-custom-config/src/seed/__init__.py | 113 + .../src/seed/_default_clients.py | 32 + .../no-custom-config/src/seed/client.py | 500 ++ .../src/seed/core/__init__.py | 127 + .../src/seed/core/api_error.py | 23 + .../src/seed/core/client_wrapper.py | 95 + .../src/seed/core/datetime_utils.py | 70 + .../no-custom-config/src/seed/core/file.py | 67 + .../src/seed/core/force_multipart.py | 18 + .../src/seed/core/http_client.py | 840 +++ .../src/seed/core/http_response.py | 59 + .../src/seed/core/http_sse/__init__.py | 42 + .../src/seed/core/http_sse/_api.py | 112 + .../src/seed/core/http_sse/_decoders.py | 61 + .../src/seed/core/http_sse/_exceptions.py | 7 + .../src/seed/core/http_sse/_models.py | 17 + .../src/seed/core/jsonable_encoder.py | 120 + .../no-custom-config/src/seed/core/logging.py | 107 + .../src/seed/core/parse_error.py | 36 + .../src/seed/core/pydantic_utilities.py | 634 +++ .../src/seed/core/query_encoder.py | 58 + .../src/seed/core/remove_none_from_dict.py | 11 + .../src/seed/core/request_options.py | 35 + .../src/seed/core/serialization.py | 276 + .../no-custom-config/src/seed/environment.py | 7 + .../no-custom-config/src/seed/py.typed | 0 .../no-custom-config/src/seed/raw_client.py | 451 ++ .../src/seed/types/__init__.py | 95 + .../src/seed/types/audit_info.py | 45 + .../src/seed/types/base_org.py | 21 + .../src/seed/types/base_org_metadata.py | 27 + .../src/seed/types/combined_entity.py | 35 + .../src/seed/types/combined_entity_status.py | 5 + .../src/seed/types/describable.py | 27 + .../src/seed/types/detailed_org.py | 20 + .../src/seed/types/detailed_org_metadata.py | 27 + .../src/seed/types/identifiable.py | 27 + .../src/seed/types/organization.py | 22 + .../src/seed/types/organization_metadata.py | 27 + .../src/seed/types/paginated_result.py | 24 + .../src/seed/types/paging_cursors.py | 27 + .../src/seed/types/rule_execution_context.py | 5 + .../src/seed/types/rule_response.py | 51 + .../src/seed/types/rule_response_status.py | 5 + .../src/seed/types/rule_type.py | 21 + .../seed/types/rule_type_search_response.py | 25 + .../no-custom-config/src/seed/types/user.py | 20 + .../src/seed/types/user_search_response.py | 25 + .../no-custom-config/src/seed/version.py | 3 + .../no-custom-config/tests/conftest.py | 147 + .../tests/custom/test_client.py | 7 + .../tests/test_aiohttp_autodetect.py | 116 + .../no-custom-config/tests/utils/__init__.py | 2 + .../tests/utils/assets/models/__init__.py | 21 + .../tests/utils/assets/models/circle.py | 11 + .../tests/utils/assets/models/color.py | 7 + .../assets/models/object_with_defaults.py | 15 + .../models/object_with_optional_field.py | 35 + .../tests/utils/assets/models/shape.py | 28 + .../tests/utils/assets/models/square.py | 11 + .../assets/models/undiscriminated_shape.py | 10 + .../tests/utils/test_http_client.py | 662 +++ .../tests/utils/test_query_encoding.py | 36 + .../tests/utils/test_serialization.py | 72 + .../no-custom-config/tests/wire/__init__.py | 0 .../no-custom-config/tests/wire/conftest.py | 78 + .../no-custom-config/tests/wire/test_.py | 44 + .../wiremock/docker-compose.test.yml | 14 + .../wiremock/wiremock-mappings.json | 141 + .../no-custom-config/.fern/metadata.json | 10 + .../no-custom-config/.github/workflows/ci.yml | 71 + .../allof/no-custom-config/.gitignore | 5 + .../allof/no-custom-config/README.md | 176 + .../allof/no-custom-config/poetry.lock | 1613 ++++++ .../allof/no-custom-config/pyproject.toml | 102 + .../allof/no-custom-config/reference.md | 268 + .../allof/no-custom-config/requirements.txt | 4 + .../allof/no-custom-config/snippet.json | 70 + .../no-custom-config/src/seed/__init__.py | 110 + .../src/seed/_default_clients.py | 30 + .../allof/no-custom-config/src/seed/client.py | 500 ++ .../src/seed/core/__init__.py | 127 + .../src/seed/core/api_error.py | 23 + .../src/seed/core/client_wrapper.py | 95 + .../src/seed/core/datetime_utils.py | 70 + .../no-custom-config/src/seed/core/file.py | 67 + .../src/seed/core/force_multipart.py | 18 + .../src/seed/core/http_client.py | 840 +++ .../src/seed/core/http_response.py | 59 + .../src/seed/core/http_sse/__init__.py | 42 + .../src/seed/core/http_sse/_api.py | 112 + .../src/seed/core/http_sse/_decoders.py | 61 + .../src/seed/core/http_sse/_exceptions.py | 7 + .../src/seed/core/http_sse/_models.py | 17 + .../src/seed/core/jsonable_encoder.py | 120 + .../no-custom-config/src/seed/core/logging.py | 107 + .../src/seed/core/parse_error.py | 36 + .../src/seed/core/pydantic_utilities.py | 634 +++ .../src/seed/core/query_encoder.py | 58 + .../src/seed/core/remove_none_from_dict.py | 11 + .../src/seed/core/request_options.py | 35 + .../src/seed/core/serialization.py | 276 + .../no-custom-config/src/seed/environment.py | 7 + .../allof/no-custom-config/src/seed/py.typed | 0 .../no-custom-config/src/seed/raw_client.py | 451 ++ .../src/seed/types/__init__.py | 92 + .../src/seed/types/audit_info.py | 45 + .../src/seed/types/base_org.py | 21 + .../src/seed/types/base_org_metadata.py | 27 + .../src/seed/types/combined_entity.py | 34 + .../src/seed/types/combined_entity_status.py | 5 + .../src/seed/types/describable.py | 27 + .../src/seed/types/detailed_org.py | 20 + .../src/seed/types/detailed_org_metadata.py | 27 + .../src/seed/types/identifiable.py | 27 + .../src/seed/types/organization.py | 22 + .../src/seed/types/paginated_result.py | 24 + .../src/seed/types/paging_cursors.py | 27 + .../src/seed/types/rule_execution_context.py | 5 + .../src/seed/types/rule_response.py | 31 + .../src/seed/types/rule_response_status.py | 5 + .../src/seed/types/rule_type.py | 21 + .../seed/types/rule_type_search_response.py | 26 + .../no-custom-config/src/seed/types/user.py | 20 + .../src/seed/types/user_search_response.py | 26 + .../no-custom-config/src/seed/version.py | 3 + .../allof/no-custom-config/tests/conftest.py | 147 + .../tests/custom/test_client.py | 7 + .../tests/test_aiohttp_autodetect.py | 116 + .../no-custom-config/tests/utils/__init__.py | 2 + .../tests/utils/assets/models/__init__.py | 21 + .../tests/utils/assets/models/circle.py | 11 + .../tests/utils/assets/models/color.py | 7 + .../assets/models/object_with_defaults.py | 15 + .../models/object_with_optional_field.py | 35 + .../tests/utils/assets/models/shape.py | 28 + .../tests/utils/assets/models/square.py | 11 + .../assets/models/undiscriminated_shape.py | 10 + .../tests/utils/test_http_client.py | 662 +++ .../tests/utils/test_query_encoding.py | 36 + .../tests/utils/test_serialization.py | 72 + .../no-custom-config/tests/wire/__init__.py | 0 .../no-custom-config/tests/wire/conftest.py | 78 + .../no-custom-config/tests/wire/test_.py | 44 + .../wiremock/docker-compose.test.yml | 14 + .../wiremock/wiremock-mappings.json | 141 + seed/python-sdk/seed.yml | 2 + .../allof-inline/.fern/metadata.json | 7 + .../allof-inline/.github/workflows/ci.yml | 75 + seed/ruby-sdk-v2/allof-inline/.gitignore | 1 + seed/ruby-sdk-v2/allof-inline/.rubocop.yml | 69 + seed/ruby-sdk-v2/allof-inline/Gemfile | 19 + seed/ruby-sdk-v2/allof-inline/Gemfile.custom | 14 + seed/ruby-sdk-v2/allof-inline/README.md | 165 + seed/ruby-sdk-v2/allof-inline/Rakefile | 20 + .../allof-inline/custom.gemspec.rb | 16 + .../dynamic-snippets/example0/snippet.rb | 5 + .../dynamic-snippets/example1/snippet.rb | 5 + .../dynamic-snippets/example2/snippet.rb | 8 + .../dynamic-snippets/example3/snippet.rb | 8 + .../dynamic-snippets/example4/snippet.rb | 5 + .../dynamic-snippets/example5/snippet.rb | 5 + .../dynamic-snippets/example6/snippet.rb | 5 + .../dynamic-snippets/example7/snippet.rb | 5 + .../dynamic-snippets/example8/snippet.rb | 5 + .../dynamic-snippets/example9/snippet.rb | 5 + seed/ruby-sdk-v2/allof-inline/lib/seed.rb | 58 + .../allof-inline/lib/seed/environment.rb | 7 + .../allof-inline/lib/seed/errors/api_error.rb | 8 + .../lib/seed/errors/client_error.rb | 17 + .../lib/seed/errors/redirect_error.rb | 8 + .../lib/seed/errors/response_error.rb | 42 + .../lib/seed/errors/server_error.rb | 11 + .../lib/seed/errors/timeout_error.rb | 8 + .../seed/internal/errors/constraint_error.rb | 10 + .../lib/seed/internal/errors/type_error.rb | 10 + .../lib/seed/internal/http/base_request.rb | 51 + .../lib/seed/internal/http/raw_client.rb | 214 + .../iterators/cursor_item_iterator.rb | 28 + .../iterators/cursor_page_iterator.rb | 51 + .../seed/internal/iterators/item_iterator.rb | 59 + .../iterators/offset_item_iterator.rb | 30 + .../iterators/offset_page_iterator.rb | 83 + .../lib/seed/internal/json/request.rb | 41 + .../lib/seed/internal/json/serializable.rb | 25 + .../internal/multipart/multipart_encoder.rb | 141 + .../internal/multipart/multipart_form_data.rb | 78 + .../multipart/multipart_form_data_part.rb | 51 + .../internal/multipart/multipart_request.rb | 40 + .../lib/seed/internal/types/array.rb | 47 + .../lib/seed/internal/types/boolean.rb | 34 + .../lib/seed/internal/types/enum.rb | 56 + .../lib/seed/internal/types/hash.rb | 36 + .../lib/seed/internal/types/model.rb | 208 + .../lib/seed/internal/types/model/field.rb | 38 + .../lib/seed/internal/types/type.rb | 35 + .../lib/seed/internal/types/union.rb | 161 + .../lib/seed/internal/types/unknown.rb | 15 + .../lib/seed/internal/types/utils.rb | 116 + .../allof-inline/lib/seed/types/audit_info.rb | 13 + .../allof-inline/lib/seed/types/base_org.rb | 10 + .../lib/seed/types/base_org_metadata.rb | 10 + .../lib/seed/types/combined_entity.rb | 12 + .../lib/seed/types/combined_entity_status.rb | 12 + .../lib/seed/types/describable.rb | 10 + .../lib/seed/types/detailed_org.rb | 9 + .../lib/seed/types/detailed_org_metadata.rb | 10 + .../lib/seed/types/identifiable.rb | 10 + .../lib/seed/types/organization.rb | 11 + .../lib/seed/types/organization_metadata.rb | 10 + .../lib/seed/types/paginated_result.rb | 10 + .../lib/seed/types/paging_cursors.rb | 10 + .../lib/seed/types/rule_execution_context.rb | 13 + .../lib/seed/types/rule_response.rb | 16 + .../lib/seed/types/rule_response_status.rb | 13 + .../allof-inline/lib/seed/types/rule_type.rb | 11 + .../seed/types/rule_type_search_response.rb | 10 + .../allof-inline/lib/seed/types/user.rb | 10 + .../lib/seed/types/user_search_response.rb | 10 + .../allof-inline/lib/seed/version.rb | 5 + seed/ruby-sdk-v2/allof-inline/reference.md | 228 + seed/ruby-sdk-v2/allof-inline/seed.gemspec | 36 + seed/ruby-sdk-v2/allof-inline/snippet.json | 0 .../allof-inline/test/custom.test.rb | 15 + .../allof-inline/test/test_helper.rb | 3 + .../iterators/test_cursor_item_iterator.rb | 189 + .../iterators/test_offset_item_iterator.rb | 151 + .../test/unit/internal/types/test_array.rb | 37 + .../test/unit/internal/types/test_boolean.rb | 35 + .../test/unit/internal/types/test_enum.rb | 42 + .../test/unit/internal/types/test_hash.rb | 50 + .../test/unit/internal/types/test_model.rb | 154 + .../test/unit/internal/types/test_union.rb | 62 + .../test/unit/internal/types/test_utils.rb | 212 + seed/ruby-sdk-v2/allof/.fern/metadata.json | 7 + .../allof/.github/workflows/ci.yml | 75 + seed/ruby-sdk-v2/allof/.gitignore | 1 + seed/ruby-sdk-v2/allof/.rubocop.yml | 69 + seed/ruby-sdk-v2/allof/Gemfile | 19 + seed/ruby-sdk-v2/allof/Gemfile.custom | 14 + seed/ruby-sdk-v2/allof/README.md | 165 + seed/ruby-sdk-v2/allof/Rakefile | 20 + seed/ruby-sdk-v2/allof/custom.gemspec.rb | 16 + .../dynamic-snippets/example0/snippet.rb | 5 + .../dynamic-snippets/example1/snippet.rb | 5 + .../dynamic-snippets/example2/snippet.rb | 8 + .../dynamic-snippets/example3/snippet.rb | 8 + .../dynamic-snippets/example4/snippet.rb | 5 + .../dynamic-snippets/example5/snippet.rb | 5 + .../dynamic-snippets/example6/snippet.rb | 5 + .../dynamic-snippets/example7/snippet.rb | 5 + .../dynamic-snippets/example8/snippet.rb | 5 + .../dynamic-snippets/example9/snippet.rb | 5 + seed/ruby-sdk-v2/allof/lib/seed.rb | 57 + .../ruby-sdk-v2/allof/lib/seed/environment.rb | 7 + .../allof/lib/seed/errors/api_error.rb | 8 + .../allof/lib/seed/errors/client_error.rb | 17 + .../allof/lib/seed/errors/redirect_error.rb | 8 + .../allof/lib/seed/errors/response_error.rb | 42 + .../allof/lib/seed/errors/server_error.rb | 11 + .../allof/lib/seed/errors/timeout_error.rb | 8 + .../seed/internal/errors/constraint_error.rb | 10 + .../lib/seed/internal/errors/type_error.rb | 10 + .../lib/seed/internal/http/base_request.rb | 51 + .../lib/seed/internal/http/raw_client.rb | 214 + .../iterators/cursor_item_iterator.rb | 28 + .../iterators/cursor_page_iterator.rb | 51 + .../seed/internal/iterators/item_iterator.rb | 59 + .../iterators/offset_item_iterator.rb | 30 + .../iterators/offset_page_iterator.rb | 83 + .../allof/lib/seed/internal/json/request.rb | 41 + .../lib/seed/internal/json/serializable.rb | 25 + .../internal/multipart/multipart_encoder.rb | 141 + .../internal/multipart/multipart_form_data.rb | 78 + .../multipart/multipart_form_data_part.rb | 51 + .../internal/multipart/multipart_request.rb | 40 + .../allof/lib/seed/internal/types/array.rb | 47 + .../allof/lib/seed/internal/types/boolean.rb | 34 + .../allof/lib/seed/internal/types/enum.rb | 56 + .../allof/lib/seed/internal/types/hash.rb | 36 + .../allof/lib/seed/internal/types/model.rb | 208 + .../lib/seed/internal/types/model/field.rb | 38 + .../allof/lib/seed/internal/types/type.rb | 35 + .../allof/lib/seed/internal/types/union.rb | 161 + .../allof/lib/seed/internal/types/unknown.rb | 15 + .../allof/lib/seed/internal/types/utils.rb | 116 + .../allof/lib/seed/types/audit_info.rb | 13 + .../allof/lib/seed/types/base_org.rb | 10 + .../allof/lib/seed/types/base_org_metadata.rb | 10 + .../allof/lib/seed/types/combined_entity.rb | 12 + .../lib/seed/types/combined_entity_status.rb | 12 + .../allof/lib/seed/types/describable.rb | 10 + .../allof/lib/seed/types/detailed_org.rb | 9 + .../lib/seed/types/detailed_org_metadata.rb | 10 + .../allof/lib/seed/types/identifiable.rb | 10 + .../allof/lib/seed/types/organization.rb | 11 + .../allof/lib/seed/types/paginated_result.rb | 10 + .../allof/lib/seed/types/paging_cursors.rb | 10 + .../lib/seed/types/rule_execution_context.rb | 13 + .../allof/lib/seed/types/rule_response.rb | 12 + .../lib/seed/types/rule_response_status.rb | 13 + .../allof/lib/seed/types/rule_type.rb | 11 + .../seed/types/rule_type_search_response.rb | 10 + seed/ruby-sdk-v2/allof/lib/seed/types/user.rb | 10 + .../lib/seed/types/user_search_response.rb | 10 + seed/ruby-sdk-v2/allof/lib/seed/version.rb | 5 + seed/ruby-sdk-v2/allof/reference.md | 228 + seed/ruby-sdk-v2/allof/seed.gemspec | 36 + seed/ruby-sdk-v2/allof/snippet.json | 0 seed/ruby-sdk-v2/allof/test/custom.test.rb | 15 + seed/ruby-sdk-v2/allof/test/test_helper.rb | 3 + .../iterators/test_cursor_item_iterator.rb | 189 + .../iterators/test_offset_item_iterator.rb | 151 + .../test/unit/internal/types/test_array.rb | 37 + .../test/unit/internal/types/test_boolean.rb | 35 + .../test/unit/internal/types/test_enum.rb | 42 + .../test/unit/internal/types/test_hash.rb | 50 + .../test/unit/internal/types/test_model.rb | 154 + .../test/unit/internal/types/test_union.rb | 62 + .../test/unit/internal/types/test_utils.rb | 212 + .../rust-sdk/allof-inline/.fern/metadata.json | 7 + .../allof-inline/.github/workflows/ci.yml | 63 + seed/rust-sdk/allof-inline/.gitignore | 5 + seed/rust-sdk/allof-inline/Cargo.toml | 25 + seed/rust-sdk/allof-inline/README.md | 181 + .../allof-inline/dynamic-snippets/example0.rs | 18 + .../allof-inline/dynamic-snippets/example1.rs | 19 + .../allof-inline/dynamic-snippets/example2.rs | 19 + .../allof-inline/dynamic-snippets/example3.rs | 19 + .../allof-inline/dynamic-snippets/example4.rs | 11 + .../allof-inline/dynamic-snippets/example5.rs | 11 + .../allof-inline/dynamic-snippets/example6.rs | 11 + .../allof-inline/dynamic-snippets/example7.rs | 11 + .../allof-inline/dynamic-snippets/example8.rs | 11 + .../allof-inline/dynamic-snippets/example9.rs | 11 + seed/rust-sdk/allof-inline/reference.md | 224 + seed/rust-sdk/allof-inline/rustfmt.toml | 4 + seed/rust-sdk/allof-inline/snippet.json | 0 seed/rust-sdk/allof-inline/src/api/mod.rs | 15 + .../allof-inline/src/api/resources/mod.rs | 82 + .../allof-inline/src/api/types/audit_info.rs | 73 + .../allof-inline/src/api/types/base_org.rs | 44 + .../src/api/types/base_org_metadata.rs | 48 + .../src/api/types/combined_entity.rs | 67 + .../src/api/types/combined_entity_status.rs | 42 + .../allof-inline/src/api/types/describable.rs | 44 + .../src/api/types/detailed_org.rs | 33 + .../src/api/types/detailed_org_metadata.rs | 48 + .../src/api/types/identifiable.rs | 46 + .../allof-inline/src/api/types/mod.rs | 45 + .../src/api/types/organization.rs | 54 + .../src/api/types/organization_metadata.rs | 48 + .../src/api/types/paginated_result.rs | 50 + .../src/api/types/paging_cursors.rs | 46 + .../src/api/types/rule_create_request.rs | 47 + .../src/api/types/rule_execution_context.rs | 47 + .../src/api/types/rule_response.rs | 114 + .../src/api/types/rule_response_status.rs | 46 + .../allof-inline/src/api/types/rule_type.rs | 54 + .../api/types/rule_type_search_response.rs | 47 + .../types/search_rule_types_query_request.rs | 32 + .../allof-inline/src/api/types/user.rs | 47 + .../src/api/types/user_search_response.rs | 47 + seed/rust-sdk/allof-inline/src/client.rs | 247 + seed/rust-sdk/allof-inline/src/config.rs | 39 + .../src/core/flexible_datetime.rs | 270 + .../allof-inline/src/core/http_client.rs | 569 ++ seed/rust-sdk/allof-inline/src/core/mod.rs | 14 + .../src/core/oauth_token_provider.rs | 363 ++ .../allof-inline/src/core/pagination.rs | 541 ++ .../src/core/query_parameter_builder.rs | 576 ++ .../allof-inline/src/core/request_options.rs | 176 + seed/rust-sdk/allof-inline/src/core/utils.rs | 77 + seed/rust-sdk/allof-inline/src/environment.rs | 19 + seed/rust-sdk/allof-inline/src/error.rs | 54 + seed/rust-sdk/allof-inline/src/lib.rs | 49 + seed/rust-sdk/allof-inline/src/prelude.rs | 19 + seed/rust-sdk/allof/.fern/metadata.json | 7 + seed/rust-sdk/allof/.github/workflows/ci.yml | 63 + seed/rust-sdk/allof/.gitignore | 5 + seed/rust-sdk/allof/Cargo.toml | 25 + seed/rust-sdk/allof/README.md | 181 + .../allof/dynamic-snippets/example0.rs | 18 + .../allof/dynamic-snippets/example1.rs | 19 + .../allof/dynamic-snippets/example2.rs | 19 + .../allof/dynamic-snippets/example3.rs | 19 + .../allof/dynamic-snippets/example4.rs | 11 + .../allof/dynamic-snippets/example5.rs | 11 + .../allof/dynamic-snippets/example6.rs | 11 + .../allof/dynamic-snippets/example7.rs | 11 + .../allof/dynamic-snippets/example8.rs | 11 + .../allof/dynamic-snippets/example9.rs | 11 + seed/rust-sdk/allof/reference.md | 224 + seed/rust-sdk/allof/rustfmt.toml | 4 + seed/rust-sdk/allof/snippet.json | 0 seed/rust-sdk/allof/src/api/mod.rs | 15 + seed/rust-sdk/allof/src/api/resources/mod.rs | 82 + .../allof/src/api/types/audit_info.rs | 73 + seed/rust-sdk/allof/src/api/types/base_org.rs | 44 + .../allof/src/api/types/base_org_metadata.rs | 48 + .../allof/src/api/types/combined_entity.rs | 67 + .../src/api/types/combined_entity_status.rs | 42 + .../allof/src/api/types/describable.rs | 44 + .../allof/src/api/types/detailed_org.rs | 33 + .../src/api/types/detailed_org_metadata.rs | 48 + .../allof/src/api/types/identifiable.rs | 46 + seed/rust-sdk/allof/src/api/types/mod.rs | 43 + .../allof/src/api/types/organization.rs | 54 + .../allof/src/api/types/paginated_result.rs | 50 + .../allof/src/api/types/paging_cursors.rs | 46 + .../src/api/types/rule_create_request.rs | 47 + .../src/api/types/rule_execution_context.rs | 47 + .../allof/src/api/types/rule_response.rs | 78 + .../src/api/types/rule_response_status.rs | 46 + .../rust-sdk/allof/src/api/types/rule_type.rs | 54 + .../api/types/rule_type_search_response.rs | 47 + .../types/search_rule_types_query_request.rs | 32 + seed/rust-sdk/allof/src/api/types/user.rs | 47 + .../src/api/types/user_search_response.rs | 47 + seed/rust-sdk/allof/src/client.rs | 247 + seed/rust-sdk/allof/src/config.rs | 39 + .../allof/src/core/flexible_datetime.rs | 270 + seed/rust-sdk/allof/src/core/http_client.rs | 569 ++ seed/rust-sdk/allof/src/core/mod.rs | 14 + .../allof/src/core/oauth_token_provider.rs | 363 ++ seed/rust-sdk/allof/src/core/pagination.rs | 541 ++ .../allof/src/core/query_parameter_builder.rs | 576 ++ .../allof/src/core/request_options.rs | 176 + seed/rust-sdk/allof/src/core/utils.rs | 77 + seed/rust-sdk/allof/src/environment.rs | 19 + seed/rust-sdk/allof/src/error.rs | 54 + seed/rust-sdk/allof/src/lib.rs | 49 + seed/rust-sdk/allof/src/prelude.rs | 19 + .../allof-inline/.fern/metadata.json | 8 + seed/swift-sdk/allof-inline/Package.swift | 31 + seed/swift-sdk/allof-inline/README.md | 180 + .../allof-inline/Snippets/Example0.swift | 10 + .../allof-inline/Snippets/Example1.swift | 10 + .../allof-inline/Snippets/Example2.swift | 13 + .../allof-inline/Snippets/Example3.swift | 13 + .../allof-inline/Snippets/Example4.swift | 10 + .../allof-inline/Snippets/Example5.swift | 10 + .../allof-inline/Snippets/Example6.swift | 10 + .../allof-inline/Snippets/Example7.swift | 10 + .../allof-inline/Snippets/Example8.swift | 10 + .../allof-inline/Snippets/Example9.swift | 10 + .../allof-inline/Sources/ApiClient.swift | 104 + .../allof-inline/Sources/ApiEnvironment.swift | 5 + .../allof-inline/Sources/ApiError.swift | 57 + .../Sources/Core/Data+String.swift | 15 + .../Sources/Core/Networking/HTTP.swift | 24 + .../Sources/Core/Networking/HTTPClient.swift | 397 ++ .../Core/Networking/MultipartFormData.swift | 45 + .../MultipartFormDataConvertible.swift | 42 + .../Core/Networking/MultipartFormField.swift | 17 + .../Core/Networking/QueryParameter.swift | 48 + .../Serde/Decoder+AdditionalProperties.swift | 27 + .../Sources/Core/Serde/EncodableValue.swift | 10 + .../Serde/Encoder+AdditionalProperties.swift | 13 + .../Serde/JSONEncoder+EncodableValue.swift | 20 + .../KeyedDecodingContainer+Nullable.swift | 20 + .../KeyedEncodingContainer+Nullable.swift | 19 + .../Sources/Core/Serde/Serde.swift | 35 + .../Sources/Core/Serde/StringKey.swift | 18 + .../Sources/Core/String+URLEncoding.swift | 11 + .../Sources/Public/CalendarDate.swift | 105 + .../Sources/Public/ClientConfig.swift | 82 + .../Sources/Public/FormFile.swift | 32 + .../Sources/Public/HTTPError.swift | 190 + .../Sources/Public/Indirect.swift | 27 + .../Sources/Public/JSONValue.swift | 136 + .../Sources/Public/Networking.swift | 21 + .../Sources/Public/Nullable.swift | 59 + .../Sources/Public/RequestOptions.swift | 45 + .../Requests/Requests+RuleCreateRequest.swift | 40 + .../Sources/Requests/Requests.swift | 6 + .../Sources/Schemas/AuditInfo.swift | 55 + .../Sources/Schemas/BaseOrg.swift | 38 + .../Sources/Schemas/BaseOrgMetadata.swift | 40 + .../Sources/Schemas/CombinedEntity.swift | 53 + .../Schemas/CombinedEntityStatus.swift | 6 + .../Sources/Schemas/Describable.swift | 40 + .../Sources/Schemas/DetailedOrg.swift | 32 + .../Sources/Schemas/DetailedOrgMetadata.swift | 40 + .../Sources/Schemas/Identifiable.swift | 40 + .../Sources/Schemas/Organization.swift | 44 + .../Schemas/OrganizationMetadata.swift | 40 + .../Sources/Schemas/PaginatedResult.swift | 39 + .../Sources/Schemas/PagingCursors.swift | 40 + .../Schemas/RuleExecutionContext.swift | 8 + .../Sources/Schemas/RuleResponse.swift | 78 + .../Sources/Schemas/RuleResponseStatus.swift | 7 + .../Sources/Schemas/RuleType.swift | 44 + .../Schemas/RuleTypeSearchResponse.swift | 39 + .../allof-inline/Sources/Schemas/User.swift | 38 + .../Sources/Schemas/UserSearchResponse.swift | 39 + .../allof-inline/Sources/Version.swift | 1 + .../Tests/Core/ClientErrorTests.swift | 222 + .../Tests/Core/ClientRetryTests.swift | 355 ++ .../Tests/Utilities/HTTPStub.swift | 317 ++ seed/swift-sdk/allof-inline/reference.md | 265 + seed/swift-sdk/allof-inline/snippet.json | 0 seed/swift-sdk/allof/.fern/metadata.json | 8 + seed/swift-sdk/allof/Package.swift | 31 + seed/swift-sdk/allof/README.md | 180 + seed/swift-sdk/allof/Snippets/Example0.swift | 10 + seed/swift-sdk/allof/Snippets/Example1.swift | 10 + seed/swift-sdk/allof/Snippets/Example2.swift | 13 + seed/swift-sdk/allof/Snippets/Example3.swift | 13 + seed/swift-sdk/allof/Snippets/Example4.swift | 10 + seed/swift-sdk/allof/Snippets/Example5.swift | 10 + seed/swift-sdk/allof/Snippets/Example6.swift | 10 + seed/swift-sdk/allof/Snippets/Example7.swift | 10 + seed/swift-sdk/allof/Snippets/Example8.swift | 10 + seed/swift-sdk/allof/Snippets/Example9.swift | 10 + seed/swift-sdk/allof/Sources/ApiClient.swift | 104 + .../allof/Sources/ApiEnvironment.swift | 5 + seed/swift-sdk/allof/Sources/ApiError.swift | 57 + .../allof/Sources/Core/Data+String.swift | 15 + .../allof/Sources/Core/Networking/HTTP.swift | 24 + .../Sources/Core/Networking/HTTPClient.swift | 397 ++ .../Core/Networking/MultipartFormData.swift | 45 + .../MultipartFormDataConvertible.swift | 42 + .../Core/Networking/MultipartFormField.swift | 17 + .../Core/Networking/QueryParameter.swift | 48 + .../Serde/Decoder+AdditionalProperties.swift | 27 + .../Sources/Core/Serde/EncodableValue.swift | 10 + .../Serde/Encoder+AdditionalProperties.swift | 13 + .../Serde/JSONEncoder+EncodableValue.swift | 20 + .../KeyedDecodingContainer+Nullable.swift | 20 + .../KeyedEncodingContainer+Nullable.swift | 19 + .../allof/Sources/Core/Serde/Serde.swift | 35 + .../allof/Sources/Core/Serde/StringKey.swift | 18 + .../Sources/Core/String+URLEncoding.swift | 11 + .../allof/Sources/Public/CalendarDate.swift | 105 + .../allof/Sources/Public/ClientConfig.swift | 82 + .../allof/Sources/Public/FormFile.swift | 32 + .../allof/Sources/Public/HTTPError.swift | 190 + .../allof/Sources/Public/Indirect.swift | 27 + .../allof/Sources/Public/JSONValue.swift | 136 + .../allof/Sources/Public/Networking.swift | 21 + .../allof/Sources/Public/Nullable.swift | 59 + .../allof/Sources/Public/RequestOptions.swift | 45 + .../Requests/Requests+RuleCreateRequest.swift | 40 + .../allof/Sources/Requests/Requests.swift | 6 + .../allof/Sources/Schemas/AuditInfo.swift | 55 + .../allof/Sources/Schemas/BaseOrg.swift | 38 + .../Sources/Schemas/BaseOrgMetadata.swift | 40 + .../Sources/Schemas/CombinedEntity.swift | 53 + .../Schemas/CombinedEntityStatus.swift | 6 + .../allof/Sources/Schemas/Describable.swift | 40 + .../allof/Sources/Schemas/DetailedOrg.swift | 32 + .../Sources/Schemas/DetailedOrgMetadata.swift | 40 + .../allof/Sources/Schemas/Identifiable.swift | 40 + .../allof/Sources/Schemas/Organization.swift | 44 + .../Sources/Schemas/PaginatedResult.swift | 39 + .../allof/Sources/Schemas/PagingCursors.swift | 40 + .../Schemas/RuleExecutionContext.swift | 8 + .../allof/Sources/Schemas/RuleResponse.swift | 78 + .../Sources/Schemas/RuleResponseStatus.swift | 7 + .../allof/Sources/Schemas/RuleType.swift | 44 + .../Schemas/RuleTypeSearchResponse.swift | 39 + .../allof/Sources/Schemas/User.swift | 38 + .../Sources/Schemas/UserSearchResponse.swift | 39 + seed/swift-sdk/allof/Sources/Version.swift | 1 + .../allof/Tests/Core/ClientErrorTests.swift | 222 + .../allof/Tests/Core/ClientRetryTests.swift | 355 ++ .../allof/Tests/Utilities/HTTPStub.swift | 317 ++ seed/swift-sdk/allof/reference.md | 265 + seed/swift-sdk/allof/snippet.json | 0 seed/ts-sdk/allof-inline/.fern/metadata.json | 7 + .../allof-inline/.github/workflows/ci.yml | 46 + seed/ts-sdk/allof-inline/.gitignore | 3 + seed/ts-sdk/allof-inline/CONTRIBUTING.md | 133 + seed/ts-sdk/allof-inline/README.md | 292 + seed/ts-sdk/allof-inline/biome.json | 74 + seed/ts-sdk/allof-inline/package.json | 69 + seed/ts-sdk/allof-inline/pnpm-workspace.yaml | 1 + seed/ts-sdk/allof-inline/reference.md | 225 + .../scripts/rename-to-esm-files.js | 123 + seed/ts-sdk/allof-inline/snippet.json | 60 + seed/ts-sdk/allof-inline/src/BaseClient.ts | 60 + seed/ts-sdk/allof-inline/src/Client.ts | 303 ++ .../allof-inline/src/api/client/index.ts | 1 + .../api/client/requests/RuleCreateRequest.ts | 15 + .../client/requests/SearchRuleTypesRequest.ts | 9 + .../src/api/client/requests/index.ts | 2 + seed/ts-sdk/allof-inline/src/api/index.ts | 2 + .../allof-inline/src/api/types/AuditInfo.ts | 15 + .../allof-inline/src/api/types/BaseOrg.ts | 15 + .../src/api/types/CombinedEntity.ts | 19 + .../allof-inline/src/api/types/Describable.ts | 8 + .../allof-inline/src/api/types/DetailedOrg.ts | 14 + .../src/api/types/Identifiable.ts | 8 + .../src/api/types/Organization.ts | 16 + .../src/api/types/PaginatedResult.ts | 9 + .../src/api/types/PagingCursors.ts | 8 + .../src/api/types/RuleExecutionContext.ts | 9 + .../src/api/types/RuleResponse.ts | 27 + .../allof-inline/src/api/types/RuleType.ts | 7 + .../src/api/types/RuleTypeSearchResponse.ts | 9 + .../ts-sdk/allof-inline/src/api/types/User.ts | 6 + .../src/api/types/UserSearchResponse.ts | 9 + .../allof-inline/src/api/types/index.ts | 15 + seed/ts-sdk/allof-inline/src/core/exports.ts | 1 + .../src/core/fetcher/APIResponse.ts | 23 + .../src/core/fetcher/BinaryResponse.ts | 34 + .../src/core/fetcher/EndpointMetadata.ts | 13 + .../src/core/fetcher/EndpointSupplier.ts | 14 + .../allof-inline/src/core/fetcher/Fetcher.ts | 404 ++ .../allof-inline/src/core/fetcher/Headers.ts | 93 + .../src/core/fetcher/HttpResponsePromise.ts | 116 + .../src/core/fetcher/RawResponse.ts | 61 + .../allof-inline/src/core/fetcher/Supplier.ts | 11 + .../src/core/fetcher/createRequestUrl.ts | 6 + .../src/core/fetcher/getErrorResponseBody.ts | 33 + .../src/core/fetcher/getFetchFn.ts | 3 + .../src/core/fetcher/getHeader.ts | 8 + .../src/core/fetcher/getRequestBody.ts | 20 + .../src/core/fetcher/getResponseBody.ts | 58 + .../allof-inline/src/core/fetcher/index.ts | 13 + .../core/fetcher/makePassthroughRequest.ts | 189 + .../src/core/fetcher/makeRequest.ts | 70 + .../src/core/fetcher/requestWithRetries.ts | 64 + .../allof-inline/src/core/fetcher/signals.ts | 26 + seed/ts-sdk/allof-inline/src/core/headers.ts | 33 + seed/ts-sdk/allof-inline/src/core/index.ts | 4 + seed/ts-sdk/allof-inline/src/core/json.ts | 27 + .../allof-inline/src/core/logging/exports.ts | 19 + .../allof-inline/src/core/logging/index.ts | 1 + .../allof-inline/src/core/logging/logger.ts | 203 + .../allof-inline/src/core/runtime/index.ts | 1 + .../allof-inline/src/core/runtime/runtime.ts | 134 + .../src/core/url/encodePathParam.ts | 18 + .../ts-sdk/allof-inline/src/core/url/index.ts | 3 + seed/ts-sdk/allof-inline/src/core/url/join.ts | 79 + seed/ts-sdk/allof-inline/src/core/url/qs.ts | 74 + seed/ts-sdk/allof-inline/src/environments.ts | 7 + .../allof-inline/src/errors/SeedApiError.ts | 64 + .../src/errors/SeedApiTimeoutError.ts | 18 + .../src/errors/handleNonStatusCodeError.ts | 40 + seed/ts-sdk/allof-inline/src/errors/index.ts | 2 + seed/ts-sdk/allof-inline/src/exports.ts | 1 + seed/ts-sdk/allof-inline/src/index.ts | 6 + seed/ts-sdk/allof-inline/src/version.ts | 1 + seed/ts-sdk/allof-inline/tests/custom.test.ts | 13 + .../tests/mock-server/MockServer.ts | 29 + .../tests/mock-server/MockServerPool.ts | 106 + .../tests/mock-server/mockEndpointBuilder.ts | 234 + .../tests/mock-server/randomBaseUrl.ts | 4 + .../allof-inline/tests/mock-server/setup.ts | 10 + .../tests/mock-server/withFormUrlEncoded.ts | 104 + .../tests/mock-server/withHeaders.ts | 70 + .../tests/mock-server/withJson.ts | 173 + seed/ts-sdk/allof-inline/tests/setup.ts | 80 + seed/ts-sdk/allof-inline/tests/tsconfig.json | 10 + .../tests/unit/fetcher/Fetcher.test.ts | 262 + .../unit/fetcher/HttpResponsePromise.test.ts | 143 + .../tests/unit/fetcher/RawResponse.test.ts | 34 + .../unit/fetcher/createRequestUrl.test.ts | 163 + .../tests/unit/fetcher/getRequestBody.test.ts | 129 + .../unit/fetcher/getResponseBody.test.ts | 97 + .../tests/unit/fetcher/logging.test.ts | 517 ++ .../fetcher/makePassthroughRequest.test.ts | 398 ++ .../tests/unit/fetcher/makeRequest.test.ts | 158 + .../tests/unit/fetcher/redacting.test.ts | 1115 ++++ .../unit/fetcher/requestWithRetries.test.ts | 230 + .../tests/unit/fetcher/signals.test.ts | 69 + .../tests/unit/fetcher/test-file.txt | 1 + .../tests/unit/logging/logger.test.ts | 454 ++ .../allof-inline/tests/unit/url/join.test.ts | 284 + .../allof-inline/tests/unit/url/qs.test.ts | 278 + seed/ts-sdk/allof-inline/tests/wire/.gitkeep | 0 .../allof-inline/tests/wire/main.test.ts | 91 + seed/ts-sdk/allof-inline/tsconfig.base.json | 17 + seed/ts-sdk/allof-inline/tsconfig.cjs.json | 9 + seed/ts-sdk/allof-inline/tsconfig.esm.json | 10 + seed/ts-sdk/allof-inline/tsconfig.json | 3 + seed/ts-sdk/allof-inline/vitest.config.mts | 32 + seed/ts-sdk/allof/.fern/metadata.json | 7 + seed/ts-sdk/allof/.github/workflows/ci.yml | 46 + seed/ts-sdk/allof/.gitignore | 3 + seed/ts-sdk/allof/CONTRIBUTING.md | 133 + seed/ts-sdk/allof/README.md | 292 + seed/ts-sdk/allof/biome.json | 74 + seed/ts-sdk/allof/package.json | 69 + seed/ts-sdk/allof/pnpm-workspace.yaml | 1 + seed/ts-sdk/allof/reference.md | 225 + .../allof/scripts/rename-to-esm-files.js | 123 + seed/ts-sdk/allof/snippet.json | 60 + seed/ts-sdk/allof/src/BaseClient.ts | 60 + seed/ts-sdk/allof/src/Client.ts | 303 ++ seed/ts-sdk/allof/src/api/client/index.ts | 1 + .../api/client/requests/RuleCreateRequest.ts | 15 + .../client/requests/SearchRuleTypesRequest.ts | 9 + .../allof/src/api/client/requests/index.ts | 2 + seed/ts-sdk/allof/src/api/index.ts | 2 + seed/ts-sdk/allof/src/api/types/AuditInfo.ts | 15 + seed/ts-sdk/allof/src/api/types/BaseOrg.ts | 15 + .../allof/src/api/types/CombinedEntity.ts | 19 + .../ts-sdk/allof/src/api/types/Describable.ts | 8 + .../ts-sdk/allof/src/api/types/DetailedOrg.ts | 14 + .../allof/src/api/types/Identifiable.ts | 8 + .../allof/src/api/types/Organization.ts | 16 + .../allof/src/api/types/PaginatedResult.ts | 9 + .../allof/src/api/types/PagingCursors.ts | 8 + .../src/api/types/RuleExecutionContext.ts | 9 + .../allof/src/api/types/RuleResponse.ts | 19 + seed/ts-sdk/allof/src/api/types/RuleType.ts | 7 + .../src/api/types/RuleTypeSearchResponse.ts | 9 + seed/ts-sdk/allof/src/api/types/User.ts | 6 + .../allof/src/api/types/UserSearchResponse.ts | 9 + seed/ts-sdk/allof/src/api/types/index.ts | 15 + seed/ts-sdk/allof/src/core/exports.ts | 1 + .../allof/src/core/fetcher/APIResponse.ts | 23 + .../allof/src/core/fetcher/BinaryResponse.ts | 34 + .../src/core/fetcher/EndpointMetadata.ts | 13 + .../src/core/fetcher/EndpointSupplier.ts | 14 + seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts | 404 ++ seed/ts-sdk/allof/src/core/fetcher/Headers.ts | 93 + .../src/core/fetcher/HttpResponsePromise.ts | 116 + .../allof/src/core/fetcher/RawResponse.ts | 61 + .../ts-sdk/allof/src/core/fetcher/Supplier.ts | 11 + .../src/core/fetcher/createRequestUrl.ts | 6 + .../src/core/fetcher/getErrorResponseBody.ts | 33 + .../allof/src/core/fetcher/getFetchFn.ts | 3 + .../allof/src/core/fetcher/getHeader.ts | 8 + .../allof/src/core/fetcher/getRequestBody.ts | 20 + .../allof/src/core/fetcher/getResponseBody.ts | 58 + seed/ts-sdk/allof/src/core/fetcher/index.ts | 13 + .../core/fetcher/makePassthroughRequest.ts | 189 + .../allof/src/core/fetcher/makeRequest.ts | 70 + .../src/core/fetcher/requestWithRetries.ts | 64 + seed/ts-sdk/allof/src/core/fetcher/signals.ts | 26 + seed/ts-sdk/allof/src/core/headers.ts | 33 + seed/ts-sdk/allof/src/core/index.ts | 4 + seed/ts-sdk/allof/src/core/json.ts | 27 + seed/ts-sdk/allof/src/core/logging/exports.ts | 19 + seed/ts-sdk/allof/src/core/logging/index.ts | 1 + seed/ts-sdk/allof/src/core/logging/logger.ts | 203 + seed/ts-sdk/allof/src/core/runtime/index.ts | 1 + seed/ts-sdk/allof/src/core/runtime/runtime.ts | 134 + .../allof/src/core/url/encodePathParam.ts | 18 + seed/ts-sdk/allof/src/core/url/index.ts | 3 + seed/ts-sdk/allof/src/core/url/join.ts | 79 + seed/ts-sdk/allof/src/core/url/qs.ts | 74 + seed/ts-sdk/allof/src/environments.ts | 7 + seed/ts-sdk/allof/src/errors/SeedApiError.ts | 64 + .../allof/src/errors/SeedApiTimeoutError.ts | 18 + .../src/errors/handleNonStatusCodeError.ts | 40 + seed/ts-sdk/allof/src/errors/index.ts | 2 + seed/ts-sdk/allof/src/exports.ts | 1 + seed/ts-sdk/allof/src/index.ts | 6 + seed/ts-sdk/allof/src/version.ts | 1 + seed/ts-sdk/allof/tests/custom.test.ts | 13 + .../allof/tests/mock-server/MockServer.ts | 29 + .../allof/tests/mock-server/MockServerPool.ts | 106 + .../tests/mock-server/mockEndpointBuilder.ts | 234 + .../allof/tests/mock-server/randomBaseUrl.ts | 4 + seed/ts-sdk/allof/tests/mock-server/setup.ts | 10 + .../tests/mock-server/withFormUrlEncoded.ts | 104 + .../allof/tests/mock-server/withHeaders.ts | 70 + .../allof/tests/mock-server/withJson.ts | 173 + seed/ts-sdk/allof/tests/setup.ts | 80 + seed/ts-sdk/allof/tests/tsconfig.json | 10 + .../allof/tests/unit/fetcher/Fetcher.test.ts | 262 + .../unit/fetcher/HttpResponsePromise.test.ts | 143 + .../tests/unit/fetcher/RawResponse.test.ts | 34 + .../unit/fetcher/createRequestUrl.test.ts | 163 + .../tests/unit/fetcher/getRequestBody.test.ts | 129 + .../unit/fetcher/getResponseBody.test.ts | 97 + .../allof/tests/unit/fetcher/logging.test.ts | 517 ++ .../fetcher/makePassthroughRequest.test.ts | 398 ++ .../tests/unit/fetcher/makeRequest.test.ts | 158 + .../tests/unit/fetcher/redacting.test.ts | 1115 ++++ .../unit/fetcher/requestWithRetries.test.ts | 230 + .../allof/tests/unit/fetcher/signals.test.ts | 69 + .../allof/tests/unit/fetcher/test-file.txt | 1 + .../allof/tests/unit/logging/logger.test.ts | 454 ++ seed/ts-sdk/allof/tests/unit/url/join.test.ts | 284 + seed/ts-sdk/allof/tests/unit/url/qs.test.ts | 278 + seed/ts-sdk/allof/tests/wire/.gitkeep | 0 seed/ts-sdk/allof/tests/wire/main.test.ts | 91 + seed/ts-sdk/allof/tsconfig.base.json | 17 + seed/ts-sdk/allof/tsconfig.cjs.json | 9 + seed/ts-sdk/allof/tsconfig.esm.json | 10 + seed/ts-sdk/allof/tsconfig.json | 3 + seed/ts-sdk/allof/vitest.config.mts | 32 + 1445 files changed, 139240 insertions(+) create mode 100644 seed/csharp-sdk/allof-inline/.editorconfig create mode 100644 seed/csharp-sdk/allof-inline/.fern/metadata.json create mode 100644 seed/csharp-sdk/allof-inline/.github/workflows/ci.yml create mode 100644 seed/csharp-sdk/allof-inline/.gitignore create mode 100644 seed/csharp-sdk/allof-inline/README.md create mode 100644 seed/csharp-sdk/allof-inline/SeedApi.slnx create mode 100644 seed/csharp-sdk/allof-inline/reference.md create mode 100644 seed/csharp-sdk/allof-inline/snippet.json create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs create mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs create mode 100644 seed/csharp-sdk/allof/.editorconfig create mode 100644 seed/csharp-sdk/allof/.fern/metadata.json create mode 100644 seed/csharp-sdk/allof/.github/workflows/ci.yml create mode 100644 seed/csharp-sdk/allof/.gitignore create mode 100644 seed/csharp-sdk/allof/README.md create mode 100644 seed/csharp-sdk/allof/SeedApi.slnx create mode 100644 seed/csharp-sdk/allof/reference.md create mode 100644 seed/csharp-sdk/allof/snippet.json create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props create mode 100644 seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj create mode 100644 seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/User.cs create mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs create mode 100644 seed/go-sdk/allof-inline/.fern/metadata.json create mode 100644 seed/go-sdk/allof-inline/.github/workflows/ci.yml create mode 100644 seed/go-sdk/allof-inline/README.md create mode 100644 seed/go-sdk/allof-inline/client/client.go create mode 100644 seed/go-sdk/allof-inline/client/client_test.go create mode 100644 seed/go-sdk/allof-inline/client/raw_client.go create mode 100644 seed/go-sdk/allof-inline/core/api_error.go create mode 100644 seed/go-sdk/allof-inline/core/http.go create mode 100644 seed/go-sdk/allof-inline/core/request_option.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go create mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go create mode 100644 seed/go-sdk/allof-inline/environments.go create mode 100644 seed/go-sdk/allof-inline/error_codes.go create mode 100644 seed/go-sdk/allof-inline/file_param.go create mode 100644 seed/go-sdk/allof-inline/go.mod create mode 100644 seed/go-sdk/allof-inline/go.sum create mode 100644 seed/go-sdk/allof-inline/internal/caller.go create mode 100644 seed/go-sdk/allof-inline/internal/caller_test.go create mode 100644 seed/go-sdk/allof-inline/internal/error_decoder.go create mode 100644 seed/go-sdk/allof-inline/internal/error_decoder_test.go create mode 100644 seed/go-sdk/allof-inline/internal/explicit_fields.go create mode 100644 seed/go-sdk/allof-inline/internal/explicit_fields_test.go create mode 100644 seed/go-sdk/allof-inline/internal/extra_properties.go create mode 100644 seed/go-sdk/allof-inline/internal/extra_properties_test.go create mode 100644 seed/go-sdk/allof-inline/internal/http.go create mode 100644 seed/go-sdk/allof-inline/internal/query.go create mode 100644 seed/go-sdk/allof-inline/internal/query_test.go create mode 100644 seed/go-sdk/allof-inline/internal/retrier.go create mode 100644 seed/go-sdk/allof-inline/internal/retrier_test.go create mode 100644 seed/go-sdk/allof-inline/internal/stringer.go create mode 100644 seed/go-sdk/allof-inline/internal/time.go create mode 100644 seed/go-sdk/allof-inline/option/request_option.go create mode 100644 seed/go-sdk/allof-inline/pointer.go create mode 100644 seed/go-sdk/allof-inline/pointer_test.go create mode 100644 seed/go-sdk/allof-inline/reference.md create mode 100644 seed/go-sdk/allof-inline/snippet.json create mode 100644 seed/go-sdk/allof-inline/types.go create mode 100644 seed/go-sdk/allof-inline/types_test.go create mode 100644 seed/go-sdk/allof/.fern/metadata.json create mode 100644 seed/go-sdk/allof/.github/workflows/ci.yml create mode 100644 seed/go-sdk/allof/README.md create mode 100644 seed/go-sdk/allof/client/client.go create mode 100644 seed/go-sdk/allof/client/client_test.go create mode 100644 seed/go-sdk/allof/client/raw_client.go create mode 100644 seed/go-sdk/allof/core/api_error.go create mode 100644 seed/go-sdk/allof/core/http.go create mode 100644 seed/go-sdk/allof/core/request_option.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example0/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example1/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example2/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example3/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example4/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example5/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example6/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example7/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example8/snippet.go create mode 100644 seed/go-sdk/allof/dynamic-snippets/example9/snippet.go create mode 100644 seed/go-sdk/allof/environments.go create mode 100644 seed/go-sdk/allof/error_codes.go create mode 100644 seed/go-sdk/allof/file_param.go create mode 100644 seed/go-sdk/allof/go.mod create mode 100644 seed/go-sdk/allof/go.sum create mode 100644 seed/go-sdk/allof/internal/caller.go create mode 100644 seed/go-sdk/allof/internal/caller_test.go create mode 100644 seed/go-sdk/allof/internal/error_decoder.go create mode 100644 seed/go-sdk/allof/internal/error_decoder_test.go create mode 100644 seed/go-sdk/allof/internal/explicit_fields.go create mode 100644 seed/go-sdk/allof/internal/explicit_fields_test.go create mode 100644 seed/go-sdk/allof/internal/extra_properties.go create mode 100644 seed/go-sdk/allof/internal/extra_properties_test.go create mode 100644 seed/go-sdk/allof/internal/http.go create mode 100644 seed/go-sdk/allof/internal/query.go create mode 100644 seed/go-sdk/allof/internal/query_test.go create mode 100644 seed/go-sdk/allof/internal/retrier.go create mode 100644 seed/go-sdk/allof/internal/retrier_test.go create mode 100644 seed/go-sdk/allof/internal/stringer.go create mode 100644 seed/go-sdk/allof/internal/time.go create mode 100644 seed/go-sdk/allof/option/request_option.go create mode 100644 seed/go-sdk/allof/pointer.go create mode 100644 seed/go-sdk/allof/pointer_test.go create mode 100644 seed/go-sdk/allof/reference.md create mode 100644 seed/go-sdk/allof/snippet.json create mode 100644 seed/go-sdk/allof/types.go create mode 100644 seed/go-sdk/allof/types_test.go create mode 100644 seed/java-sdk/allof-inline/.fern/metadata.json create mode 100644 seed/java-sdk/allof-inline/.github/workflows/ci.yml create mode 100644 seed/java-sdk/allof-inline/.gitignore create mode 100644 seed/java-sdk/allof-inline/README.md create mode 100644 seed/java-sdk/allof-inline/build.gradle create mode 100644 seed/java-sdk/allof-inline/reference.md create mode 100644 seed/java-sdk/allof-inline/sample-app/build.gradle create mode 100644 seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java create mode 100644 seed/java-sdk/allof-inline/settings.gradle create mode 100644 seed/java-sdk/allof-inline/snippet.json create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java create mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java create mode 100644 seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java create mode 100644 seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java create mode 100644 seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java create mode 100644 seed/java-sdk/allof/.fern/metadata.json create mode 100644 seed/java-sdk/allof/.github/workflows/ci.yml create mode 100644 seed/java-sdk/allof/.gitignore create mode 100644 seed/java-sdk/allof/README.md create mode 100644 seed/java-sdk/allof/build.gradle create mode 100644 seed/java-sdk/allof/reference.md create mode 100644 seed/java-sdk/allof/sample-app/build.gradle create mode 100644 seed/java-sdk/allof/sample-app/src/main/java/sample/App.java create mode 100644 seed/java-sdk/allof/settings.gradle create mode 100644 seed/java-sdk/allof/snippet.json create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java create mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example0.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example1.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example2.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example3.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example4.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example5.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example6.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example7.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example8.java create mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example9.java create mode 100644 seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java create mode 100644 seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java create mode 100644 seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java create mode 100644 seed/php-sdk/allof-inline/.fern/metadata.json create mode 100644 seed/php-sdk/allof-inline/.github/workflows/ci.yml create mode 100644 seed/php-sdk/allof-inline/.gitignore create mode 100644 seed/php-sdk/allof-inline/README.md create mode 100644 seed/php-sdk/allof-inline/composer.json create mode 100644 seed/php-sdk/allof-inline/phpstan.neon create mode 100644 seed/php-sdk/allof-inline/phpunit.xml create mode 100644 seed/php-sdk/allof-inline/reference.md create mode 100644 seed/php-sdk/allof-inline/snippet.json create mode 100644 seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Client/MockHttpClient.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Client/RawClient.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonSerializableType.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Json/Utils.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Types/Date.php create mode 100644 seed/php-sdk/allof-inline/src/Core/Types/Union.php create mode 100644 seed/php-sdk/allof-inline/src/Environments.php create mode 100644 seed/php-sdk/allof-inline/src/Exceptions/SeedApiException.php create mode 100644 seed/php-sdk/allof-inline/src/Exceptions/SeedException.php create mode 100644 seed/php-sdk/allof-inline/src/Requests/RuleCreateRequest.php create mode 100644 seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php create mode 100644 seed/php-sdk/allof-inline/src/SeedClient.php create mode 100644 seed/php-sdk/allof-inline/src/Types/AuditInfo.php create mode 100644 seed/php-sdk/allof-inline/src/Types/BaseOrg.php create mode 100644 seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php create mode 100644 seed/php-sdk/allof-inline/src/Types/CombinedEntity.php create mode 100644 seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php create mode 100644 seed/php-sdk/allof-inline/src/Types/Describable.php create mode 100644 seed/php-sdk/allof-inline/src/Types/DetailedOrg.php create mode 100644 seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php create mode 100644 seed/php-sdk/allof-inline/src/Types/Identifiable.php create mode 100644 seed/php-sdk/allof-inline/src/Types/Organization.php create mode 100644 seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php create mode 100644 seed/php-sdk/allof-inline/src/Types/PaginatedResult.php create mode 100644 seed/php-sdk/allof-inline/src/Types/PagingCursors.php create mode 100644 seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php create mode 100644 seed/php-sdk/allof-inline/src/Types/RuleResponse.php create mode 100644 seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php create mode 100644 seed/php-sdk/allof-inline/src/Types/RuleType.php create mode 100644 seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php create mode 100644 seed/php-sdk/allof-inline/src/Types/User.php create mode 100644 seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php create mode 100644 seed/php-sdk/allof-inline/src/Utils/File.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php create mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php create mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php create mode 100644 seed/php-sdk/allof/.fern/metadata.json create mode 100644 seed/php-sdk/allof/.github/workflows/ci.yml create mode 100644 seed/php-sdk/allof/.gitignore create mode 100644 seed/php-sdk/allof/README.md create mode 100644 seed/php-sdk/allof/composer.json create mode 100644 seed/php-sdk/allof/phpstan.neon create mode 100644 seed/php-sdk/allof/phpunit.xml create mode 100644 seed/php-sdk/allof/reference.md create mode 100644 seed/php-sdk/allof/snippet.json create mode 100644 seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php create mode 100644 seed/php-sdk/allof/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/allof/src/Core/Client/MockHttpClient.php create mode 100644 seed/php-sdk/allof/src/Core/Client/RawClient.php create mode 100644 seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php create mode 100644 seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/allof/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/allof/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/allof/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/allof/src/Core/Json/JsonSerializableType.php create mode 100644 seed/php-sdk/allof/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/allof/src/Core/Json/Utils.php create mode 100644 seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php create mode 100644 seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php create mode 100644 seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php create mode 100644 seed/php-sdk/allof/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/allof/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/allof/src/Core/Types/Date.php create mode 100644 seed/php-sdk/allof/src/Core/Types/Union.php create mode 100644 seed/php-sdk/allof/src/Environments.php create mode 100644 seed/php-sdk/allof/src/Exceptions/SeedApiException.php create mode 100644 seed/php-sdk/allof/src/Exceptions/SeedException.php create mode 100644 seed/php-sdk/allof/src/Requests/RuleCreateRequest.php create mode 100644 seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php create mode 100644 seed/php-sdk/allof/src/SeedClient.php create mode 100644 seed/php-sdk/allof/src/Traits/AuditInfo.php create mode 100644 seed/php-sdk/allof/src/Types/AuditInfo.php create mode 100644 seed/php-sdk/allof/src/Types/BaseOrg.php create mode 100644 seed/php-sdk/allof/src/Types/BaseOrgMetadata.php create mode 100644 seed/php-sdk/allof/src/Types/CombinedEntity.php create mode 100644 seed/php-sdk/allof/src/Types/CombinedEntityStatus.php create mode 100644 seed/php-sdk/allof/src/Types/Describable.php create mode 100644 seed/php-sdk/allof/src/Types/DetailedOrg.php create mode 100644 seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php create mode 100644 seed/php-sdk/allof/src/Types/Identifiable.php create mode 100644 seed/php-sdk/allof/src/Types/Organization.php create mode 100644 seed/php-sdk/allof/src/Types/PaginatedResult.php create mode 100644 seed/php-sdk/allof/src/Types/PagingCursors.php create mode 100644 seed/php-sdk/allof/src/Types/RuleExecutionContext.php create mode 100644 seed/php-sdk/allof/src/Types/RuleResponse.php create mode 100644 seed/php-sdk/allof/src/Types/RuleResponseStatus.php create mode 100644 seed/php-sdk/allof/src/Types/RuleType.php create mode 100644 seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php create mode 100644 seed/php-sdk/allof/src/Types/User.php create mode 100644 seed/php-sdk/allof/src/Types/UserSearchResponse.php create mode 100644 seed/php-sdk/allof/src/Utils/File.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php create mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php create mode 100644 seed/php-sdk/allof/tests/Core/Client/RawClientTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/InvalidTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/ScalarTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php create mode 100644 seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php create mode 100644 seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json create mode 100644 seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml create mode 100644 seed/python-sdk/allof-inline/no-custom-config/.gitignore create mode 100644 seed/python-sdk/allof-inline/no-custom-config/README.md create mode 100644 seed/python-sdk/allof-inline/no-custom-config/poetry.lock create mode 100644 seed/python-sdk/allof-inline/no-custom-config/pyproject.toml create mode 100644 seed/python-sdk/allof-inline/no-custom-config/reference.md create mode 100644 seed/python-sdk/allof-inline/no-custom-config/requirements.txt create mode 100644 seed/python-sdk/allof-inline/no-custom-config/snippet.json create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/py.typed create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/wire/__init__.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py create mode 100644 seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml create mode 100644 seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json create mode 100644 seed/python-sdk/allof/no-custom-config/.fern/metadata.json create mode 100644 seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml create mode 100644 seed/python-sdk/allof/no-custom-config/.gitignore create mode 100644 seed/python-sdk/allof/no-custom-config/README.md create mode 100644 seed/python-sdk/allof/no-custom-config/poetry.lock create mode 100644 seed/python-sdk/allof/no-custom-config/pyproject.toml create mode 100644 seed/python-sdk/allof/no-custom-config/reference.md create mode 100644 seed/python-sdk/allof/no-custom-config/requirements.txt create mode 100644 seed/python-sdk/allof/no-custom-config/snippet.json create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/__init__.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/client.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/file.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/environment.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/py.typed create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/user.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py create mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/version.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/conftest.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/wire/__init__.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py create mode 100644 seed/python-sdk/allof/no-custom-config/tests/wire/test_.py create mode 100644 seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml create mode 100644 seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json create mode 100644 seed/ruby-sdk-v2/allof-inline/.fern/metadata.json create mode 100644 seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml create mode 100644 seed/ruby-sdk-v2/allof-inline/.gitignore create mode 100644 seed/ruby-sdk-v2/allof-inline/.rubocop.yml create mode 100644 seed/ruby-sdk-v2/allof-inline/Gemfile create mode 100644 seed/ruby-sdk-v2/allof-inline/Gemfile.custom create mode 100644 seed/ruby-sdk-v2/allof-inline/README.md create mode 100644 seed/ruby-sdk-v2/allof-inline/Rakefile create mode 100644 seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/reference.md create mode 100644 seed/ruby-sdk-v2/allof-inline/seed.gemspec create mode 100644 seed/ruby-sdk-v2/allof-inline/snippet.json create mode 100644 seed/ruby-sdk-v2/allof-inline/test/custom.test.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/test_helper.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb create mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb create mode 100644 seed/ruby-sdk-v2/allof/.fern/metadata.json create mode 100644 seed/ruby-sdk-v2/allof/.github/workflows/ci.yml create mode 100644 seed/ruby-sdk-v2/allof/.gitignore create mode 100644 seed/ruby-sdk-v2/allof/.rubocop.yml create mode 100644 seed/ruby-sdk-v2/allof/Gemfile create mode 100644 seed/ruby-sdk-v2/allof/Gemfile.custom create mode 100644 seed/ruby-sdk-v2/allof/README.md create mode 100644 seed/ruby-sdk-v2/allof/Rakefile create mode 100644 seed/ruby-sdk-v2/allof/custom.gemspec.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/environment.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/user.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb create mode 100644 seed/ruby-sdk-v2/allof/lib/seed/version.rb create mode 100644 seed/ruby-sdk-v2/allof/reference.md create mode 100644 seed/ruby-sdk-v2/allof/seed.gemspec create mode 100644 seed/ruby-sdk-v2/allof/snippet.json create mode 100644 seed/ruby-sdk-v2/allof/test/custom.test.rb create mode 100644 seed/ruby-sdk-v2/allof/test/test_helper.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb create mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb create mode 100644 seed/rust-sdk/allof-inline/.fern/metadata.json create mode 100644 seed/rust-sdk/allof-inline/.github/workflows/ci.yml create mode 100644 seed/rust-sdk/allof-inline/.gitignore create mode 100644 seed/rust-sdk/allof-inline/Cargo.toml create mode 100644 seed/rust-sdk/allof-inline/README.md create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs create mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs create mode 100644 seed/rust-sdk/allof-inline/reference.md create mode 100644 seed/rust-sdk/allof-inline/rustfmt.toml create mode 100644 seed/rust-sdk/allof-inline/snippet.json create mode 100644 seed/rust-sdk/allof-inline/src/api/mod.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/resources/mod.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/audit_info.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/base_org.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/describable.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/identifiable.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/mod.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/organization.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_response.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_type.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/user.rs create mode 100644 seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs create mode 100644 seed/rust-sdk/allof-inline/src/client.rs create mode 100644 seed/rust-sdk/allof-inline/src/config.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/http_client.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/mod.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/pagination.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/request_options.rs create mode 100644 seed/rust-sdk/allof-inline/src/core/utils.rs create mode 100644 seed/rust-sdk/allof-inline/src/environment.rs create mode 100644 seed/rust-sdk/allof-inline/src/error.rs create mode 100644 seed/rust-sdk/allof-inline/src/lib.rs create mode 100644 seed/rust-sdk/allof-inline/src/prelude.rs create mode 100644 seed/rust-sdk/allof/.fern/metadata.json create mode 100644 seed/rust-sdk/allof/.github/workflows/ci.yml create mode 100644 seed/rust-sdk/allof/.gitignore create mode 100644 seed/rust-sdk/allof/Cargo.toml create mode 100644 seed/rust-sdk/allof/README.md create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example0.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example1.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example2.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example3.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example4.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example5.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example6.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example7.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example8.rs create mode 100644 seed/rust-sdk/allof/dynamic-snippets/example9.rs create mode 100644 seed/rust-sdk/allof/reference.md create mode 100644 seed/rust-sdk/allof/rustfmt.toml create mode 100644 seed/rust-sdk/allof/snippet.json create mode 100644 seed/rust-sdk/allof/src/api/mod.rs create mode 100644 seed/rust-sdk/allof/src/api/resources/mod.rs create mode 100644 seed/rust-sdk/allof/src/api/types/audit_info.rs create mode 100644 seed/rust-sdk/allof/src/api/types/base_org.rs create mode 100644 seed/rust-sdk/allof/src/api/types/base_org_metadata.rs create mode 100644 seed/rust-sdk/allof/src/api/types/combined_entity.rs create mode 100644 seed/rust-sdk/allof/src/api/types/combined_entity_status.rs create mode 100644 seed/rust-sdk/allof/src/api/types/describable.rs create mode 100644 seed/rust-sdk/allof/src/api/types/detailed_org.rs create mode 100644 seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs create mode 100644 seed/rust-sdk/allof/src/api/types/identifiable.rs create mode 100644 seed/rust-sdk/allof/src/api/types/mod.rs create mode 100644 seed/rust-sdk/allof/src/api/types/organization.rs create mode 100644 seed/rust-sdk/allof/src/api/types/paginated_result.rs create mode 100644 seed/rust-sdk/allof/src/api/types/paging_cursors.rs create mode 100644 seed/rust-sdk/allof/src/api/types/rule_create_request.rs create mode 100644 seed/rust-sdk/allof/src/api/types/rule_execution_context.rs create mode 100644 seed/rust-sdk/allof/src/api/types/rule_response.rs create mode 100644 seed/rust-sdk/allof/src/api/types/rule_response_status.rs create mode 100644 seed/rust-sdk/allof/src/api/types/rule_type.rs create mode 100644 seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs create mode 100644 seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs create mode 100644 seed/rust-sdk/allof/src/api/types/user.rs create mode 100644 seed/rust-sdk/allof/src/api/types/user_search_response.rs create mode 100644 seed/rust-sdk/allof/src/client.rs create mode 100644 seed/rust-sdk/allof/src/config.rs create mode 100644 seed/rust-sdk/allof/src/core/flexible_datetime.rs create mode 100644 seed/rust-sdk/allof/src/core/http_client.rs create mode 100644 seed/rust-sdk/allof/src/core/mod.rs create mode 100644 seed/rust-sdk/allof/src/core/oauth_token_provider.rs create mode 100644 seed/rust-sdk/allof/src/core/pagination.rs create mode 100644 seed/rust-sdk/allof/src/core/query_parameter_builder.rs create mode 100644 seed/rust-sdk/allof/src/core/request_options.rs create mode 100644 seed/rust-sdk/allof/src/core/utils.rs create mode 100644 seed/rust-sdk/allof/src/environment.rs create mode 100644 seed/rust-sdk/allof/src/error.rs create mode 100644 seed/rust-sdk/allof/src/lib.rs create mode 100644 seed/rust-sdk/allof/src/prelude.rs create mode 100644 seed/swift-sdk/allof-inline/.fern/metadata.json create mode 100644 seed/swift-sdk/allof-inline/Package.swift create mode 100644 seed/swift-sdk/allof-inline/README.md create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example0.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example1.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example2.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example3.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example4.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example5.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example6.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example7.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example8.swift create mode 100644 seed/swift-sdk/allof-inline/Snippets/Example9.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/ApiClient.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/ApiError.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/Networking.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/User.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift create mode 100644 seed/swift-sdk/allof-inline/Sources/Version.swift create mode 100644 seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift create mode 100644 seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift create mode 100644 seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift create mode 100644 seed/swift-sdk/allof-inline/reference.md create mode 100644 seed/swift-sdk/allof-inline/snippet.json create mode 100644 seed/swift-sdk/allof/.fern/metadata.json create mode 100644 seed/swift-sdk/allof/Package.swift create mode 100644 seed/swift-sdk/allof/README.md create mode 100644 seed/swift-sdk/allof/Snippets/Example0.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example1.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example2.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example3.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example4.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example5.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example6.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example7.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example8.swift create mode 100644 seed/swift-sdk/allof/Snippets/Example9.swift create mode 100644 seed/swift-sdk/allof/Sources/ApiClient.swift create mode 100644 seed/swift-sdk/allof/Sources/ApiEnvironment.swift create mode 100644 seed/swift-sdk/allof/Sources/ApiError.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Data+String.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift create mode 100644 seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/CalendarDate.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/ClientConfig.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/FormFile.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/HTTPError.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/Indirect.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/JSONValue.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/Networking.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/Nullable.swift create mode 100644 seed/swift-sdk/allof/Sources/Public/RequestOptions.swift create mode 100644 seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift create mode 100644 seed/swift-sdk/allof/Sources/Requests/Requests.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/Describable.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/Organization.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleType.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/User.swift create mode 100644 seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift create mode 100644 seed/swift-sdk/allof/Sources/Version.swift create mode 100644 seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift create mode 100644 seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift create mode 100644 seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift create mode 100644 seed/swift-sdk/allof/reference.md create mode 100644 seed/swift-sdk/allof/snippet.json create mode 100644 seed/ts-sdk/allof-inline/.fern/metadata.json create mode 100644 seed/ts-sdk/allof-inline/.github/workflows/ci.yml create mode 100644 seed/ts-sdk/allof-inline/.gitignore create mode 100644 seed/ts-sdk/allof-inline/CONTRIBUTING.md create mode 100644 seed/ts-sdk/allof-inline/README.md create mode 100644 seed/ts-sdk/allof-inline/biome.json create mode 100644 seed/ts-sdk/allof-inline/package.json create mode 100644 seed/ts-sdk/allof-inline/pnpm-workspace.yaml create mode 100644 seed/ts-sdk/allof-inline/reference.md create mode 100644 seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js create mode 100644 seed/ts-sdk/allof-inline/snippet.json create mode 100644 seed/ts-sdk/allof-inline/src/BaseClient.ts create mode 100644 seed/ts-sdk/allof-inline/src/Client.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/client/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/client/requests/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/Describable.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/Organization.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleType.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/User.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts create mode 100644 seed/ts-sdk/allof-inline/src/api/types/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/exports.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/headers.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/json.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/logging/exports.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/logging/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/logging/logger.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/runtime/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/url/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/url/join.ts create mode 100644 seed/ts-sdk/allof-inline/src/core/url/qs.ts create mode 100644 seed/ts-sdk/allof-inline/src/environments.ts create mode 100644 seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts create mode 100644 seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts create mode 100644 seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts create mode 100644 seed/ts-sdk/allof-inline/src/errors/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/exports.ts create mode 100644 seed/ts-sdk/allof-inline/src/index.ts create mode 100644 seed/ts-sdk/allof-inline/src/version.ts create mode 100644 seed/ts-sdk/allof-inline/tests/custom.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/setup.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts create mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts create mode 100644 seed/ts-sdk/allof-inline/tests/setup.ts create mode 100644 seed/ts-sdk/allof-inline/tests/tsconfig.json create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt create mode 100644 seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts create mode 100644 seed/ts-sdk/allof-inline/tests/wire/.gitkeep create mode 100644 seed/ts-sdk/allof-inline/tests/wire/main.test.ts create mode 100644 seed/ts-sdk/allof-inline/tsconfig.base.json create mode 100644 seed/ts-sdk/allof-inline/tsconfig.cjs.json create mode 100644 seed/ts-sdk/allof-inline/tsconfig.esm.json create mode 100644 seed/ts-sdk/allof-inline/tsconfig.json create mode 100644 seed/ts-sdk/allof-inline/vitest.config.mts create mode 100644 seed/ts-sdk/allof/.fern/metadata.json create mode 100644 seed/ts-sdk/allof/.github/workflows/ci.yml create mode 100644 seed/ts-sdk/allof/.gitignore create mode 100644 seed/ts-sdk/allof/CONTRIBUTING.md create mode 100644 seed/ts-sdk/allof/README.md create mode 100644 seed/ts-sdk/allof/biome.json create mode 100644 seed/ts-sdk/allof/package.json create mode 100644 seed/ts-sdk/allof/pnpm-workspace.yaml create mode 100644 seed/ts-sdk/allof/reference.md create mode 100644 seed/ts-sdk/allof/scripts/rename-to-esm-files.js create mode 100644 seed/ts-sdk/allof/snippet.json create mode 100644 seed/ts-sdk/allof/src/BaseClient.ts create mode 100644 seed/ts-sdk/allof/src/Client.ts create mode 100644 seed/ts-sdk/allof/src/api/client/index.ts create mode 100644 seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts create mode 100644 seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts create mode 100644 seed/ts-sdk/allof/src/api/client/requests/index.ts create mode 100644 seed/ts-sdk/allof/src/api/index.ts create mode 100644 seed/ts-sdk/allof/src/api/types/AuditInfo.ts create mode 100644 seed/ts-sdk/allof/src/api/types/BaseOrg.ts create mode 100644 seed/ts-sdk/allof/src/api/types/CombinedEntity.ts create mode 100644 seed/ts-sdk/allof/src/api/types/Describable.ts create mode 100644 seed/ts-sdk/allof/src/api/types/DetailedOrg.ts create mode 100644 seed/ts-sdk/allof/src/api/types/Identifiable.ts create mode 100644 seed/ts-sdk/allof/src/api/types/Organization.ts create mode 100644 seed/ts-sdk/allof/src/api/types/PaginatedResult.ts create mode 100644 seed/ts-sdk/allof/src/api/types/PagingCursors.ts create mode 100644 seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts create mode 100644 seed/ts-sdk/allof/src/api/types/RuleResponse.ts create mode 100644 seed/ts-sdk/allof/src/api/types/RuleType.ts create mode 100644 seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts create mode 100644 seed/ts-sdk/allof/src/api/types/User.ts create mode 100644 seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts create mode 100644 seed/ts-sdk/allof/src/api/types/index.ts create mode 100644 seed/ts-sdk/allof/src/core/exports.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/Headers.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/Supplier.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/getHeader.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/index.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts create mode 100644 seed/ts-sdk/allof/src/core/fetcher/signals.ts create mode 100644 seed/ts-sdk/allof/src/core/headers.ts create mode 100644 seed/ts-sdk/allof/src/core/index.ts create mode 100644 seed/ts-sdk/allof/src/core/json.ts create mode 100644 seed/ts-sdk/allof/src/core/logging/exports.ts create mode 100644 seed/ts-sdk/allof/src/core/logging/index.ts create mode 100644 seed/ts-sdk/allof/src/core/logging/logger.ts create mode 100644 seed/ts-sdk/allof/src/core/runtime/index.ts create mode 100644 seed/ts-sdk/allof/src/core/runtime/runtime.ts create mode 100644 seed/ts-sdk/allof/src/core/url/encodePathParam.ts create mode 100644 seed/ts-sdk/allof/src/core/url/index.ts create mode 100644 seed/ts-sdk/allof/src/core/url/join.ts create mode 100644 seed/ts-sdk/allof/src/core/url/qs.ts create mode 100644 seed/ts-sdk/allof/src/environments.ts create mode 100644 seed/ts-sdk/allof/src/errors/SeedApiError.ts create mode 100644 seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts create mode 100644 seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts create mode 100644 seed/ts-sdk/allof/src/errors/index.ts create mode 100644 seed/ts-sdk/allof/src/exports.ts create mode 100644 seed/ts-sdk/allof/src/index.ts create mode 100644 seed/ts-sdk/allof/src/version.ts create mode 100644 seed/ts-sdk/allof/tests/custom.test.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/MockServer.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/setup.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/withHeaders.ts create mode 100644 seed/ts-sdk/allof/tests/mock-server/withJson.ts create mode 100644 seed/ts-sdk/allof/tests/setup.ts create mode 100644 seed/ts-sdk/allof/tests/tsconfig.json create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt create mode 100644 seed/ts-sdk/allof/tests/unit/logging/logger.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/url/join.test.ts create mode 100644 seed/ts-sdk/allof/tests/unit/url/qs.test.ts create mode 100644 seed/ts-sdk/allof/tests/wire/.gitkeep create mode 100644 seed/ts-sdk/allof/tests/wire/main.test.ts create mode 100644 seed/ts-sdk/allof/tsconfig.base.json create mode 100644 seed/ts-sdk/allof/tsconfig.cjs.json create mode 100644 seed/ts-sdk/allof/tsconfig.esm.json create mode 100644 seed/ts-sdk/allof/tsconfig.json create mode 100644 seed/ts-sdk/allof/vitest.config.mts diff --git a/seed/csharp-sdk/allof-inline/.editorconfig b/seed/csharp-sdk/allof-inline/.editorconfig new file mode 100644 index 000000000000..1e7a0adbac80 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/.editorconfig @@ -0,0 +1,35 @@ +root = true + +[*.cs] +resharper_arrange_object_creation_when_type_evident_highlighting = hint +resharper_auto_property_can_be_made_get_only_global_highlighting = hint +resharper_check_namespace_highlighting = hint +resharper_class_never_instantiated_global_highlighting = hint +resharper_class_never_instantiated_local_highlighting = hint +resharper_collection_never_updated_global_highlighting = hint +resharper_convert_type_check_pattern_to_null_check_highlighting = hint +resharper_inconsistent_naming_highlighting = hint +resharper_member_can_be_private_global_highlighting = hint +resharper_member_hides_static_from_outer_class_highlighting = hint +resharper_not_accessed_field_local_highlighting = hint +resharper_nullable_warning_suppression_is_used_highlighting = suggestion +resharper_partial_type_with_single_part_highlighting = hint +resharper_prefer_concrete_value_over_default_highlighting = none +resharper_private_field_can_be_converted_to_local_variable_highlighting = hint +resharper_property_can_be_made_init_only_global_highlighting = hint +resharper_property_can_be_made_init_only_local_highlighting = hint +resharper_redundant_name_qualifier_highlighting = none +resharper_redundant_using_directive_highlighting = hint +resharper_replace_slice_with_range_indexer_highlighting = none +resharper_unused_auto_property_accessor_global_highlighting = hint +resharper_unused_auto_property_accessor_local_highlighting = hint +resharper_unused_member_global_highlighting = hint +resharper_unused_type_global_highlighting = hint +resharper_use_string_interpolation_highlighting = hint +dotnet_diagnostic.CS1591.severity = suggestion + +[src/**/Types/*.cs] +resharper_check_namespace_highlighting = none + +[src/**/Core/Public/*.cs] +resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/.fern/metadata.json b/seed/csharp-sdk/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..91d0855bee07 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/.fern/metadata.json @@ -0,0 +1,8 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-csharp-sdk", + "generatorVersion": "latest", + "generatorConfig": {}, + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/.github/workflows/ci.yml b/seed/csharp-sdk/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..87068349b616 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + DOTNET_NOLOGO: true + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Install tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore src/SeedApi/SeedApi.csproj + + - name: Build + run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release + + - name: Restore test dependencies + run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj + + - name: Build tests + run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release + + - name: Test + run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release + + - name: Pack + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release + + - name: Publish to NuGet.org + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" + diff --git a/seed/csharp-sdk/allof-inline/.gitignore b/seed/csharp-sdk/allof-inline/.gitignore new file mode 100644 index 000000000000..11014f2b33d7 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +## This is based on `dotnet new gitignore` and customized by Fern + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +# [Rr]elease/ (Ignored by Fern) +# [Rr]eleases/ (Ignored by Fern) +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +# [Ll]og/ (Ignored by Fern) +# [Ll]ogs/ (Ignored by Fern) + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/seed/csharp-sdk/allof-inline/README.md b/seed/csharp-sdk/allof-inline/README.md new file mode 100644 index 000000000000..939b438133db --- /dev/null +++ b/seed/csharp-sdk/allof-inline/README.md @@ -0,0 +1,216 @@ +# Seed C# Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FC%23) +[![nuget shield](https://img.shields.io/nuget/v/Fernallof-inline)](https://nuget.org/packages/Fernallof-inline) + +The Seed C# library provides convenient access to the Seed APIs from C#. + +## Table of Contents + +- [Requirements](#requirements) +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Raw Response](#raw-response) + - [Additional Headers](#additional-headers) + - [Additional Query Parameters](#additional-query-parameters) + - [Forward Compatible Enums](#forward-compatible-enums) +- [Contributing](#contributing) + +## Requirements + +This SDK requires: + +## Installation + +```sh +dotnet add package Fernallof-inline +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```csharp +using SeedApi; + +var client = new SeedApiClient(); +await client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } +); +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```csharp +using SeedApi; + +var client = new SeedApiClient(new ClientOptions +{ + BaseUrl = SeedApiEnvironment.Default +}); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```csharp +using SeedApi; + +try { + var response = await client.CreateRuleAsync(...); +} catch (SeedApiApiException e) { + System.Console.WriteLine(e.Body); + System.Console.WriteLine(e.StatusCode); +} +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `MaxRetries` request option to configure this behavior. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + MaxRetries: 0 // Override MaxRetries at the request level + } +); +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s + } +); +``` + +### Raw Response + +Access raw HTTP response data (status code, headers, URL) alongside parsed response data using the `.WithRawResponse()` method. + +```csharp +using SeedApi; + +// Access raw response data (status code, headers, etc.) alongside the parsed response +var result = await client.CreateRuleAsync(...).WithRawResponse(); + +// Access the parsed data +var data = result.Data; + +// Access raw response metadata +var statusCode = result.RawResponse.StatusCode; +var headers = result.RawResponse.Headers; +var url = result.RawResponse.Url; + +// Access specific headers (case-insensitive) +if (headers.TryGetValue("X-Request-Id", out var requestId)) +{ + System.Console.WriteLine($"Request ID: {requestId}"); +} + +// For the default behavior, simply await without .WithRawResponse() +var data = await client.CreateRuleAsync(...); +``` + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + AdditionalHeaders = new Dictionary + { + { "X-Custom-Header", "custom-value" } + } + } +); +``` + +### Additional Query Parameters + +If you would like to send additional query parameters as part of the request, use the `AdditionalQueryParameters` request option. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + AdditionalQueryParameters = new Dictionary + { + { "custom_param", "custom-value" } + } + } +); +``` + +### Forward Compatible Enums + +This SDK uses forward-compatible enums that can handle unknown values gracefully. + +```csharp +using SeedApi; + +// Using a built-in value +var ruleExecutionContext = RuleExecutionContext.Prod; + +// Using a custom value +var customRuleExecutionContext = RuleExecutionContext.FromCustom("custom-value"); + +// Using in a switch statement +switch (ruleExecutionContext.Value) +{ + case RuleExecutionContext.Values.Prod: + Console.WriteLine("Prod"); + break; + default: + Console.WriteLine($"Unknown value: {ruleExecutionContext.Value}"); + break; +} + +// Explicit casting +string ruleExecutionContextString = (string)RuleExecutionContext.Prod; +RuleExecutionContext ruleExecutionContextFromString = (RuleExecutionContext)"prod"; +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/csharp-sdk/allof-inline/SeedApi.slnx b/seed/csharp-sdk/allof-inline/SeedApi.slnx new file mode 100644 index 000000000000..d4c63c241aad --- /dev/null +++ b/seed/csharp-sdk/allof-inline/SeedApi.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/seed/csharp-sdk/allof-inline/reference.md b/seed/csharp-sdk/allof-inline/reference.md new file mode 100644 index 000000000000..09ba75269a0f --- /dev/null +++ b/seed/csharp-sdk/allof-inline/reference.md @@ -0,0 +1,158 @@ +# Reference +
client.SearchRuleTypesAsync(SearchRuleTypesRequest { ... }) -> WithRawResponseTask<RuleTypeSearchResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SearchRuleTypesRequest` + +
+
+
+
+ + +
+
+
+ +
client.CreateRuleAsync(RuleCreateRequest { ... }) -> WithRawResponseTask<RuleResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `RuleCreateRequest` + +
+
+
+
+ + +
+
+
+ +
client.ListUsersAsync() -> WithRawResponseTask<UserSearchResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.ListUsersAsync(); +``` +
+
+
+
+ + +
+
+
+ +
client.GetEntityAsync() -> WithRawResponseTask<CombinedEntity> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.GetEntityAsync(); +``` +
+
+
+
+ + +
+
+
+ +
client.GetOrganizationAsync() -> WithRawResponseTask<Organization> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.GetOrganizationAsync(); +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/csharp-sdk/allof-inline/snippet.json b/seed/csharp-sdk/allof-inline/snippet.json new file mode 100644 index 000000000000..df8ab96f5bc6 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/snippet.json @@ -0,0 +1,65 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.SearchRuleTypesAsync(new SearchRuleTypesRequest());\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.CreateRuleAsync(\n new RuleCreateRequest { Name = \"name\", ExecutionContext = RuleExecutionContext.Prod }\n);\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.ListUsersAsync();\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetEntityAsync();\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetOrganizationAsync();\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs new file mode 100644 index 000000000000..0c6994903107 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs @@ -0,0 +1,19 @@ +using SeedApi; + +namespace Usage; + +public class Example0 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.SearchRuleTypesAsync( + new SearchRuleTypesRequest() + ); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs new file mode 100644 index 000000000000..44ebc3965c0d --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs @@ -0,0 +1,21 @@ +using SeedApi; + +namespace Usage; + +public class Example1 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.SearchRuleTypesAsync( + new SearchRuleTypesRequest { + Query = "query" + } + ); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs new file mode 100644 index 000000000000..b63878a26a91 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs @@ -0,0 +1,22 @@ +using SeedApi; + +namespace Usage; + +public class Example2 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.CreateRuleAsync( + new RuleCreateRequest { + Name = "name", + ExecutionContext = RuleExecutionContext.Prod + } + ); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs new file mode 100644 index 000000000000..eb0371508e02 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs @@ -0,0 +1,22 @@ +using SeedApi; + +namespace Usage; + +public class Example3 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.CreateRuleAsync( + new RuleCreateRequest { + Name = "name", + ExecutionContext = RuleExecutionContext.Prod + } + ); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs new file mode 100644 index 000000000000..440f17e55522 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example4 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.ListUsersAsync(); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs new file mode 100644 index 000000000000..c1d081509932 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example5 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.ListUsersAsync(); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs new file mode 100644 index 000000000000..8694c56b98a0 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example6 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetEntityAsync(); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs new file mode 100644 index 000000000000..bc47bce361fb --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example7 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetEntityAsync(); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs new file mode 100644 index 000000000000..897c0c509b2e --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example8 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetOrganizationAsync(); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs new file mode 100644 index 000000000000..2f7b06bd2b7b --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example9 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetOrganizationAsync(); + } + +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj new file mode 100644 index 000000000000..3417db2e58e2 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + 12 + enable + enable + + + + + + \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs new file mode 100644 index 000000000000..7ad11f298637 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs @@ -0,0 +1,326 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class HeadersBuilderTests +{ + [Test] + public async global::System.Threading.Tasks.Task Add_SimpleHeaders() + { + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") + .Add("Authorization", "Bearer token123") + .Add("X-API-Key", "key456") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); + Assert.That(headers["Authorization"], Is.EqualTo("Bearer token123")); + Assert.That(headers["X-API-Key"], Is.EqualTo("key456")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_NullValuesIgnored() + { + var headers = await new HeadersBuilder.Builder() + .Add("Header1", "value1") + .Add("Header2", null) + .Add("Header3", "value3") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(2)); + Assert.That(headers.ContainsKey("Header1"), Is.True); + Assert.That(headers.ContainsKey("Header2"), Is.False); + Assert.That(headers.ContainsKey("Header3"), Is.True); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_OverwritesExistingHeader() + { + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") + .Add("Content-Type", "application/xml") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_MergesExistingHeaders() + { + var existingHeaders = new Headers( + new Dictionary { { "Header1", "value1" }, { "Header2", "value2" } } + ); + + var result = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result["Header1"], Is.EqualTo("value1")); + Assert.That(result["Header2"], Is.EqualTo("value2")); + Assert.That(result["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_OverwritesExistingHeaders() + { + var existingHeaders = new Headers( + new Dictionary { { "Header1", "override" } } + ); + + var result = await new HeadersBuilder.Builder() + .Add("Header1", "original") + .Add("Header2", "keep") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result["Header1"], Is.EqualTo("override")); + Assert.That(result["Header2"], Is.EqualTo("keep")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_NullHeadersIgnored() + { + var result = await new HeadersBuilder.Builder() + .Add("Header1", "value1") + .Add((Headers?)null) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result["Header1"], Is.EqualTo("value1")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_AddsHeaders() + { + var additionalHeaders = new List> + { + new("Header1", "value1"), + new("Header2", "value2"), + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(additionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + Assert.That(headers["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_IgnoresNullValues() + { + var additionalHeaders = new List> + { + new("Header1", "value1"), + new("Header2", null), // Should be ignored + }; + + var headers = await new HeadersBuilder.Builder() + .Add(additionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers.ContainsKey("Header2"), Is.False); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_DictionaryOverload_AddsHeaders() + { + var dict = new Dictionary + { + { "Header1", "value1" }, + { "Header2", "value2" }, + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(dict) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + Assert.That(headers["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task EmptyBuilder_ReturnsEmptyHeaders() + { + var headers = await new HeadersBuilder.Builder().BuildAsync().ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task OnlyNullValues_ReturnsEmptyHeaders() + { + var headers = await new HeadersBuilder.Builder() + .Add("Header1", null) + .Add("Header2", null) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task ComplexMergingScenario() + { + // Simulates real SDK usage: endpoint headers + client headers + request options + var clientHeaders = new Headers( + new Dictionary + { + { "X-Client-Version", "1.0.0" }, + { "User-Agent", "MyClient/1.0" }, + } + ); + + var clientAdditionalHeaders = new List> + { + new("X-Custom-Header", "custom-value"), + }; + + var requestOptionsHeaders = new Headers( + new Dictionary + { + { "Authorization", "Bearer user-token" }, + { "User-Agent", "MyClient/2.0" }, // Override + } + ); + + var requestAdditionalHeaders = new List> + { + new("X-Request-ID", "req-123"), + new("X-Custom-Header", "overridden-value"), // Override + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") // Endpoint header + .Add("X-Endpoint-ID", "endpoint-1") + .Add(clientHeaders) + .Add(clientAdditionalHeaders) + .Add(requestOptionsHeaders) + .Add(requestAdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + // Verify precedence + Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); + Assert.That(headers["X-Endpoint-ID"], Is.EqualTo("endpoint-1")); + Assert.That(headers["X-Client-Version"], Is.EqualTo("1.0.0")); + Assert.That(headers["User-Agent"], Is.EqualTo("MyClient/2.0")); // Overridden + Assert.That(headers["Authorization"], Is.EqualTo("Bearer user-token")); + Assert.That(headers["X-Request-ID"], Is.EqualTo("req-123")); + Assert.That(headers["X-Custom-Header"], Is.EqualTo("overridden-value")); // Overridden + } + + [Test] + public async global::System.Threading.Tasks.Task Builder_WithCapacity() + { + // Test that capacity constructor works without errors + var headers = await new HeadersBuilder.Builder(capacity: 10) + .Add("Header1", "value1") + .Add("Header2", "value2") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(2)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_ResolvesDynamicHeaderValues() + { + // Test that BuildAsync properly resolves HeaderValue instances + var existingHeaders = new Headers(); + existingHeaders["DynamicHeader"] = + (Func>)( + () => global::System.Threading.Tasks.Task.FromResult("dynamic-value") + ); + + var result = await new HeadersBuilder.Builder() + .Add("StaticHeader", "static-value") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result["StaticHeader"], Is.EqualTo("static-value")); + Assert.That(result["DynamicHeader"], Is.EqualTo("dynamic-value")); + } + + [Test] + public async global::System.Threading.Tasks.Task MultipleSyncAdds() + { + var headers1 = new Headers(new Dictionary { { "H1", "v1" } }); + var headers2 = new Headers(new Dictionary { { "H2", "v2" } }); + var headers3 = new Headers(new Dictionary { { "H3", "v3" } }); + + var result = await new HeadersBuilder.Builder() + .Add(headers1) + .Add(headers2) + .Add(headers3) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result["H1"], Is.EqualTo("v1")); + Assert.That(result["H2"], Is.EqualTo("v2")); + Assert.That(result["H3"], Is.EqualTo("v3")); + } + + [Test] + public async global::System.Threading.Tasks.Task PrecedenceOrder_LatestWins() + { + // Test that later operations override earlier ones + var headers1 = new Headers(new Dictionary { { "Key", "value1" } }); + var headers2 = new Headers(new Dictionary { { "Key", "value2" } }); + var additional = new List> { new("Key", "value3") }; + + var result = await new HeadersBuilder.Builder() + .Add("Key", "value0") + .Add(headers1) + .Add(headers2) + .Add(additional) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result["Key"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task CaseInsensitiveKeys() + { + // Test that header keys are case-insensitive + var headers = await new HeadersBuilder.Builder() + .Add("content-type", "application/json") + .Add("Content-Type", "application/xml") // Should overwrite + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers["content-type"], Is.EqualTo("application/xml")); + Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); + Assert.That(headers["CONTENT-TYPE"], Is.EqualTo("application/xml")); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs new file mode 100644 index 000000000000..a12183113312 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs @@ -0,0 +1,365 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class AdditionalPropertiesTests +{ + [Test] + public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); + Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); + }); + } + + [Test] + public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecord + { + Id = "1", + AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.Id, Is.EqualTo("1")); + Assert.That( + deserializedRecord.AdditionalProperties["category"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), + Is.EqualTo("fiction") + ); + Assert.That( + deserializedRecord.AdditionalProperties["title"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() + { + // Arrange + var extensionData = new Dictionary + { + ["key1"] = JsonUtils.SerializeToElement("value1"), + ["key2"] = JsonUtils.SerializeToElement(123), + }; + var readOnlyProps = new ReadOnlyAdditionalProperties(); + readOnlyProps.CopyFromExtensionData(extensionData); + + // Act & Assert + Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); + Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); + } + + [Test] + public void AdditionalProperties_ShouldBehaveAsDictionary() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + additionalProps["key3"] = true; + + // Assert + Assert.Multiple(() => + { + Assert.That(additionalProps["key1"], Is.EqualTo("value1")); + Assert.That(additionalProps["key2"], Is.EqualTo(123)); + Assert.That((bool)additionalProps["key3"]!, Is.True); + Assert.That(additionalProps.Count, Is.EqualTo(3)); + }); + } + + [Test] + public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + var jsonObject = additionalProps.ToJsonObject(); + + Assert.Multiple(() => + { + // Assert + Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); + Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); + }); + } + + [Test] + public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + var record = JsonUtils.Deserialize(json); + + // Act + record.AdditionalProperties["category"] = "non-fiction"; + + // Assert + Assert.Multiple(() => + { + Assert.That(record, Is.Not.Null); + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); + Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); + Assert.That( + ((JsonElement)record.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": 42, + "extra2": 99 + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithInts + { + AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": { "key1": true, "key2": false }, + "extra2": { "key3": true } + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithDictionaries + { + AdditionalProperties = + { + ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, + ["extra2"] = new Dictionary { { "key3", true } }, + }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + private record Record : IJsonOnDeserialized + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithInts : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithDictionaries : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties< + Dictionary + > AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties> AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs new file mode 100644 index 000000000000..c0f258680b78 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs @@ -0,0 +1,100 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateOnlyJsonTests +{ + [Test] + public void SerializeDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly? dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly? expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateOnlyKey() + { + var key = new DateOnly(2023, 10, 5); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateOnlyKey() + { + var json = """ + { + "2023-10-05": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateOnly(2023, 10, 5); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs new file mode 100644 index 000000000000..1dde45a8e939 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs @@ -0,0 +1,134 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateTimeJsonTests +{ + [Test] + public void SerializeDateTime_ShouldMatchExpectedFormat() + { + (DateTime dateTime, string expected)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + foreach (var (dateTime, expected) in testCases) + { + var json = JsonUtils.Serialize(dateTime); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateTime_ShouldMatchExpectedDateTime() + { + (DateTime expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateTime_ShouldMatchExpectedFormat() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateTimeKey() + { + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateTimeKey() + { + var json = """ + { + "2023-10-05T14:30:00.000Z": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs new file mode 100644 index 000000000000..969acd620998 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs @@ -0,0 +1,160 @@ +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class JsonAccessAttributeTests +{ + private class MyClass + { + [JsonPropertyName("read_only_prop")] + [JsonAccess(JsonAccessType.ReadOnly)] + public string? ReadOnlyProp { get; set; } + + [JsonPropertyName("write_only_prop")] + [JsonAccess(JsonAccessType.WriteOnly)] + public string? WriteOnlyProp { get; set; } + + [JsonPropertyName("normal_prop")] + public string? NormalProp { get; set; } + + [JsonPropertyName("read_only_nullable_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable? ReadOnlyNullableList { get; set; } + + [JsonPropertyName("read_only_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable ReadOnlyList { get; set; } = []; + + [JsonPropertyName("write_only_nullable_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable? WriteOnlyNullableList { get; set; } + + [JsonPropertyName("write_only_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable WriteOnlyList { get; set; } = []; + + [JsonPropertyName("normal_list")] + public IEnumerable NormalList { get; set; } = []; + + [JsonPropertyName("normal_nullable_list")] + public IEnumerable? NullableNormalList { get; set; } + } + + [Test] + public void JsonAccessAttribute_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "write_only_prop": "write", + "normal_prop": "normal_prop", + "read_only_nullable_list": ["item1", "item2"], + "read_only_list": ["item3", "item4"], + "write_only_nullable_list": ["item5", "item6"], + "write_only_list": ["item7", "item8"], + "normal_list": ["normal1", "normal2"], + "normal_nullable_list": ["normal1", "normal2"] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // String properties + Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); + Assert.That(obj.WriteOnlyProp, Is.Null); + Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); + + // List properties - read only + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Not.Null); + Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); + Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); + Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); + + var readOnlyList = obj.ReadOnlyList.ToArray(); + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Has.Length.EqualTo(2)); + Assert.That(readOnlyList[0], Is.EqualTo("item3")); + Assert.That(readOnlyList[1], Is.EqualTo("item4")); + + // List properties - write only + Assert.That(obj.WriteOnlyNullableList, Is.Null); + Assert.That(obj.WriteOnlyList, Is.Not.Null); + Assert.That(obj.WriteOnlyList, Is.Empty); + + // Normal list property + var normalList = obj.NormalList.ToArray(); + Assert.That(normalList, Is.Not.Null); + Assert.That(normalList, Has.Length.EqualTo(2)); + Assert.That(normalList[0], Is.EqualTo("normal1")); + Assert.That(normalList[1], Is.EqualTo("normal2")); + }); + + // Set up values for serialization + obj.WriteOnlyProp = "write"; + obj.NormalProp = "new_value"; + obj.WriteOnlyNullableList = new List { "write1", "write2" }; + obj.WriteOnlyList = new List { "write3", "write4" }; + obj.NormalList = new List { "new_normal" }; + obj.NullableNormalList = new List { "new_normal" }; + + var serializedJson = JsonUtils.Serialize(obj); + const string expectedJson = """ + { + "write_only_prop": "write", + "normal_prop": "new_value", + "write_only_nullable_list": [ + "write1", + "write2" + ], + "write_only_list": [ + "write3", + "write4" + ], + "normal_list": [ + "new_normal" + ], + "normal_nullable_list": [ + "new_normal" + ] + } + """; + Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); + } + + [Test] + public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "normal_prop": "normal_prop", + "read_only_nullable_list": null, + "read_only_list": [] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // Read-only nullable list should be null when JSON contains null + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Null); + + // Read-only non-nullable list should never be null, but empty when JSON contains null + var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Is.Empty); + }); + + // Serialize and verify read-only lists are not included + var serializedJson = JsonUtils.Serialize(obj); + Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); + Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); + Assert.That(serializedJson, Does.Not.Contain("read_only_list")); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs new file mode 100644 index 000000000000..493d8e99c329 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs @@ -0,0 +1,658 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class QueryStringBuilderTests +{ + [Test] + public void Build_SimpleParameters() + { + var parameters = new List> + { + new("name", "John Doe"), + new("age", "30"), + new("city", "New York"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?name=John%20Doe&age=30&city=New%20York")); + } + + [Test] + public void Build_EmptyList_ReturnsEmptyString() + { + var parameters = new List>(); + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Build_SpecialCharacters() + { + var parameters = new List> + { + new("email", "test@example.com"), + new("url", "https://example.com/path?query=value"), + new("special", "a+b=c&d"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That( + result, + Is.EqualTo( + "?email=test@example.com&url=https://example.com/path?query=value&special=a%2Bb=c%26d" + ) + ); + } + + [Test] + public void Build_UnicodeCharacters() + { + var parameters = new List> { new("greeting", "Hello 世界") }; + + var result = QueryStringBuilder.Build(parameters); + + // Verify the Chinese characters are properly UTF-8 encoded + Assert.That(result, Does.StartWith("?greeting=Hello%20")); + Assert.That(result, Does.Contain("%E4%B8%96%E7%95%8C")); // 世界 + } + + [Test] + public void Build_SessionSettings_DeepObject() + { + // Simulate session settings with nested properties + var sessionSettings = new + { + custom_session_id = "my-custom-session-id", + system_prompt = "You are a helpful assistant", + variables = new Dictionary + { + { "userName", "John" }, + { "userAge", 30 }, + { "isPremium", true }, + }, + }; + + // Build query parameters list + var queryParams = new List> { new("api_key", "test_key_123") }; + + // Add session_settings with prefix using the new overload + queryParams.AddRange( + QueryStringConverter.ToDeepObject("session_settings", sessionSettings) + ); + + var result = QueryStringBuilder.Build(queryParams); + + // Verify the result contains properly formatted deep object notation + // Note: Square brackets are URL-encoded as %5B and %5D + Assert.That(result, Does.StartWith("?api_key=test_key_123")); + Assert.That( + result, + Does.Contain("session_settings%5Bcustom_session_id%5D=my-custom-session-id") + ); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20a%20helpful%20assistant") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BisPremium%5D=true")); + + // Verify it's NOT JSON encoded (no braces or quotes in the original format) + Assert.That(result, Does.Not.Contain("%7B%22")); // Not {" sequence + } + + [Test] + public void Build_ChatApiLikeParameters() + { + // Simulate what ChatApi constructor does + var sessionSettings = new + { + system_prompt = "You are helpful", + variables = new Dictionary { { "name", "Alice" } }, + }; + + var queryParams = new List>(); + + // Simple parameters + var simpleParams = new Dictionary + { + { "access_token", "token123" }, + { "config_id", "config456" }, + { "api_key", "key789" }, + }; + queryParams.AddRange(QueryStringConverter.ToExplodedForm(simpleParams)); + + // Session settings as deep object with prefix + queryParams.AddRange( + QueryStringConverter.ToDeepObject("session_settings", sessionSettings) + ); + + var result = QueryStringBuilder.Build(queryParams); + + // Verify structure (square brackets are URL-encoded) + Assert.That(result, Does.StartWith("?")); + Assert.That(result, Does.Contain("access_token=token123")); + Assert.That(result, Does.Contain("config_id=config456")); + Assert.That(result, Does.Contain("api_key=key789")); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); + } + + [Test] + public void Build_ReservedCharacters_NotEncoded() + { + var parameters = new List> + { + new("path", "some-path"), + new("id", "123-456_789.test~value"), + }; + + var result = QueryStringBuilder.Build(parameters); + + // Safe query characters include RFC 3986 unreserved + sub-delimiters (except & = +) + : @ / + Assert.That(result, Is.EqualTo("?path=some-path&id=123-456_789.test~value")); + } + + [Test] + public void Builder_Add_SimpleParameters() + { + var result = new QueryStringBuilder.Builder() + .Add("name", "John Doe") + .Add("age", 30) + .Add("active", true) + .Build(); + + Assert.That(result, Does.Contain("name=John%20Doe")); + Assert.That(result, Does.Contain("age=30")); + Assert.That(result, Does.Contain("active=true")); + } + + [Test] + public void Builder_Add_NullValuesIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("name", "John") + .Add("middle", null) + .Add("age", 30) + .Build(); + + Assert.That(result, Does.Contain("name=John")); + Assert.That(result, Does.Contain("age=30")); + Assert.That(result, Does.Not.Contain("middle")); + } + + [Test] + public void Builder_AddDeepObject_WithPrefix() + { + var settings = new + { + custom_session_id = "id-123", + system_prompt = "You are helpful", + variables = new { name = "Alice", age = 25 }, + }; + + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddDeepObject("session_settings", settings) + .Build(); + + Assert.That(result, Does.Contain("api_key=key123")); + Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=id-123")); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bage%5D=25")); + } + + [Test] + public void Builder_AddDeepObject_NullIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddDeepObject("settings", null) + .Build(); + + Assert.That(result, Is.EqualTo("?api_key=key123")); + Assert.That(result, Does.Not.Contain("settings")); + } + + [Test] + public void Builder_AddExploded_WithPrefix() + { + var filter = new { status = "active", type = "user" }; + + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddExploded("filter", filter) + .Build(); + + Assert.That(result, Does.Contain("api_key=key123")); + Assert.That(result, Does.Contain("filter%5Bstatus%5D=active")); + Assert.That(result, Does.Contain("filter%5Btype%5D=user")); + } + + [Test] + public void Builder_AddExploded_NullIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddExploded("filter", null) + .Build(); + + Assert.That(result, Is.EqualTo("?api_key=key123")); + Assert.That(result, Does.Not.Contain("filter")); + } + + [Test] + public void Builder_WithCapacity() + { + // Test that capacity constructor works without errors + var result = new QueryStringBuilder.Builder(capacity: 10) + .Add("param1", "value1") + .Add("param2", "value2") + .Build(); + + Assert.That(result, Does.Contain("param1=value1")); + Assert.That(result, Does.Contain("param2=value2")); + } + + [Test] + public void Builder_ChatApiLikeUsage() + { + // Simulate real usage from ChatApi + var sessionSettings = new + { + custom_session_id = "session-123", + variables = new Dictionary + { + { "userName", "John" }, + { "userAge", 30 }, + }, + }; + + var result = new QueryStringBuilder.Builder(capacity: 16) + .Add("access_token", "token123") + .Add("allow_connection", true) + .Add("config_id", "config456") + .Add("api_key", "key789") + .AddDeepObject("session_settings", sessionSettings) + .Build(); + + Assert.That(result, Does.StartWith("?")); + Assert.That(result, Does.Contain("access_token=token123")); + Assert.That(result, Does.Contain("allow_connection=true")); + Assert.That(result, Does.Contain("config_id=config456")); + Assert.That(result, Does.Contain("api_key=key789")); + Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=session-123")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); + } + + [Test] + public void Builder_EmptyBuilder_ReturnsEmptyString() + { + var result = new QueryStringBuilder.Builder().Build(); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Builder_OnlyNullValues_ReturnsEmptyString() + { + var result = new QueryStringBuilder.Builder() + .Add("param1", null) + .Add("param2", null) + .AddDeepObject("settings", null) + .Build(); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Builder_Set_OverridesSingleValue() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Set("foo", "override") + .Build(); + + Assert.That(result, Is.EqualTo("?foo=override")); + } + + [Test] + public void Builder_Set_OverridesMultipleValues() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "value1") + .Add("foo", "value2") + .Set("foo", "override") + .Build(); + + Assert.That(result, Is.EqualTo("?foo=override")); + } + + [Test] + public void Builder_Set_WithArray_CreatesMultipleParameters() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Set("foo", new[] { "value1", "value2" }) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value1&foo=value2")); + } + + [Test] + public void Builder_Set_WithNull_RemovesParameter() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Add("bar", "keep") + .Set("foo", null) + .Build(); + + Assert.That(result, Is.EqualTo("?bar=keep")); + } + + [Test] + public void Builder_MergeAdditional_WithSingleValues() + { + var additional = new List> + { + new("foo", "bar"), + new("baz", "qux"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("existing", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("existing=value")); + Assert.That(result, Does.Contain("foo=bar")); + Assert.That(result, Does.Contain("baz=qux")); + } + + [Test] + public void Builder_MergeAdditional_WithDuplicateKeys_CreatesList() + { + var additional = new List> + { + new("foo", "bar1"), + new("foo", "bar2"), + new("baz", "qux"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("existing", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("existing=value")); + Assert.That(result, Does.Contain("foo=bar1")); + Assert.That(result, Does.Contain("foo=bar2")); + Assert.That(result, Does.Contain("baz=qux")); + } + + [Test] + public void Builder_MergeAdditional_OverridesExistingParameters() + { + var additional = new List> { new("foo", "override") }; + + var result = new QueryStringBuilder.Builder() + .Add("foo", "original1") + .Add("foo", "original2") + .Add("bar", "keep") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("bar=keep")); + Assert.That(result, Does.Contain("foo=override")); + Assert.That(result, Does.Not.Contain("original1")); + Assert.That(result, Does.Not.Contain("original2")); + } + + [Test] + public void Builder_MergeAdditional_WithDuplicates_OverridesExisting() + { + var additional = new List> + { + new("foo", "new1"), + new("foo", "new2"), + new("foo", "new3"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("foo", "original1") + .Add("foo", "original2") + .Add("bar", "keep") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("bar=keep")); + Assert.That(result, Does.Contain("foo=new1")); + Assert.That(result, Does.Contain("foo=new2")); + Assert.That(result, Does.Contain("foo=new3")); + Assert.That(result, Does.Not.Contain("original1")); + Assert.That(result, Does.Not.Contain("original2")); + } + + [Test] + public void Builder_MergeAdditional_WithNull_NoOp() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "value") + .MergeAdditional(null) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value")); + } + + [Test] + public void Builder_MergeAdditional_WithEmptyList_NoOp() + { + var additional = new List>(); + + var result = new QueryStringBuilder.Builder() + .Add("foo", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value")); + } + + [Test] + public void Builder_MergeAdditional_RealWorldScenario() + { + // SDK generates foo=foo1&foo=foo2 + var builder = new QueryStringBuilder.Builder() + .Add("foo", "foo1") + .Add("foo", "foo2") + .Add("bar", "baz"); + + // User provides foo=override in AdditionalQueryParameters + var additional = new List> { new("foo", "override") }; + + var result = builder.MergeAdditional(additional).Build(); + + // Result should be foo=override&bar=baz (user overrides SDK) + Assert.That(result, Does.Contain("bar=baz")); + Assert.That(result, Does.Contain("foo=override")); + Assert.That(result, Does.Not.Contain("foo1")); + Assert.That(result, Does.Not.Contain("foo2")); + } + + [Test] + public void Builder_MergeAdditional_UserProvidesMultipleValues() + { + // SDK generates no foo parameter + var builder = new QueryStringBuilder.Builder().Add("bar", "baz"); + + // User provides foo=bar1&foo=bar2 in AdditionalQueryParameters + var additional = new List> + { + new("foo", "bar1"), + new("foo", "bar2"), + }; + + var result = builder.MergeAdditional(additional).Build(); + + // Result should be bar=baz&foo=bar1&foo=bar2 + Assert.That(result, Does.Contain("bar=baz")); + Assert.That(result, Does.Contain("foo=bar1")); + Assert.That(result, Does.Contain("foo=bar2")); + } + + [Test] + public void Builder_Add_WithCollection_CreatesMultipleParameters() + { + var tags = new[] { "tag1", "tag2", "tag3" }; + var result = new QueryStringBuilder.Builder().Add("tag", tags).Build(); + + Assert.That(result, Does.Contain("tag=tag1")); + Assert.That(result, Does.Contain("tag=tag2")); + Assert.That(result, Does.Contain("tag=tag3")); + } + + [Test] + public void Builder_Add_WithList_CreatesMultipleParameters() + { + var ids = new List { 1, 2, 3 }; + var result = new QueryStringBuilder.Builder().Add("id", ids).Build(); + + Assert.That(result, Does.Contain("id=1")); + Assert.That(result, Does.Contain("id=2")); + Assert.That(result, Does.Contain("id=3")); + } + + [Test] + public void Builder_Set_WithCollection_ReplacesAllPreviousValues() + { + var result = new QueryStringBuilder.Builder() + .Add("id", 1) + .Add("id", 2) + .Set("id", new[] { 10, 20, 30 }) + .Build(); + + Assert.That(result, Does.Contain("id=10")); + Assert.That(result, Does.Contain("id=20")); + Assert.That(result, Does.Contain("id=30")); + // Check that old values are not present (use word boundaries to avoid false positives with id=10) + Assert.That(result, Does.Not.Contain("id=1&")); + Assert.That(result, Does.Not.Contain("id=2&")); + Assert.That(result, Does.Not.Contain("id=1?")); + Assert.That(result, Does.Not.Contain("id=2?")); + Assert.That(result, Does.Not.EndWith("id=1")); + Assert.That(result, Does.Not.EndWith("id=2")); + } + + [Test] + public void EncodePathSegment_UnreservedChars_NotEncoded() + { + var result = QueryStringBuilder.EncodePathSegment("hello-world_test.value~123"); + Assert.That(result, Is.EqualTo("hello-world_test.value~123")); + } + + [Test] + public void EncodePathSegment_SubDelimiters_NotEncoded() + { + // All sub-delimiters are safe in path segments per RFC 3986 + var result = QueryStringBuilder.EncodePathSegment("a!b$c&d'e(f)g*h+i,j;k=l"); + Assert.That(result, Is.EqualTo("a!b$c&d'e(f)g*h+i,j;k=l")); + } + + [Test] + public void EncodePathSegment_ColonAndAt_NotEncoded() + { + var result = QueryStringBuilder.EncodePathSegment("user@host:8080"); + Assert.That(result, Is.EqualTo("user@host:8080")); + } + + [Test] + public void EncodePathSegment_SlashAndQuestion_Encoded() + { + // "/" and "?" are NOT part of pchar, so they must be encoded in path segments + var result = QueryStringBuilder.EncodePathSegment("path/with?query"); + Assert.That(result, Is.EqualTo("path%2Fwith%3Fquery")); + } + + [Test] + public void EncodePathSegment_Space_Encoded() + { + var result = QueryStringBuilder.EncodePathSegment("hello world"); + Assert.That(result, Is.EqualTo("hello%20world")); + } + + [Test] + public void EncodePathSegment_EmptyAndNull() + { + Assert.That(QueryStringBuilder.EncodePathSegment(""), Is.EqualTo("")); + Assert.That(QueryStringBuilder.EncodePathSegment(null!), Is.Null); + } + + [Test] + public void Build_QueryKeyVsValue_DifferentEncoding() + { + // "=" is safe in query values but NOT in query keys + var parameters = new List> + { + new("key=with=equals", "value=with=equals"), + }; + + var result = QueryStringBuilder.Build(parameters); + + // Key: "=" must be encoded + // Value: "=" is safe (part of query value safe chars) + Assert.That(result, Is.EqualTo("?key%3Dwith%3Dequals=value=with=equals")); + } + + [Test] + public void Build_QueryValue_QuestionMarkNotEncoded() + { + // "?" is safe in both query keys and query values per RFC 3986 + var parameters = new List> { new("q?key", "is this?") }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?q?key=is%20this?")); + } + + [Test] + public void Build_QueryKey_PlusEncoded() + { + // "+" must be encoded in both query keys and query values + var parameters = new List> { new("a+b", "c+d") }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?a%2Bb=c%2Bd")); + } + + [Test] + public void Build_ODataFilter_DollarPreserved() + { + // "$" is safe in query keys (sub-delimiter), verifies OData-style parameters work + var parameters = new List> + { + new("$filter", "status eq 'active'"), + new("$top", "10"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Does.Contain("$filter=status%20eq%20'active'")); + Assert.That(result, Does.Contain("$top=10")); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs new file mode 100644 index 000000000000..06293e022863 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs @@ -0,0 +1,158 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class QueryStringConverterTests +{ + [Test] + public void ToQueryStringCollection_Form() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToForm(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates]", "39.78172,-89.65015"), + new("Tags", "Developer,Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_ExplodedForm() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToExplodedForm(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates]", "39.78172"), + new("Address[Coordinates]", "-89.65015"), + new("Tags", "Developer"), + new("Tags", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_DeepObject() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToDeepObject(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates][0]", "39.78172"), + new("Address[Coordinates][1]", "-89.65015"), + new("Tags[0]", "Developer"), + new("Tags[1]", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_OnString_ThrowsException() + { + var exception = Assert.Throws(() => + QueryStringConverter.ToForm("invalid") + ); + Assert.That( + exception.Message, + Is.EqualTo( + "Only objects can be converted to query string collections. Given type is String." + ) + ); + } + + [Test] + public void ToQueryStringCollection_OnArray_ThrowsException() + { + var exception = Assert.Throws(() => + QueryStringConverter.ToForm(Array.Empty()) + ); + Assert.That( + exception.Message, + Is.EqualTo( + "Only objects can be converted to query string collections. Given type is Array." + ) + ); + } + + [Test] + public void ToQueryStringCollection_DeepObject_WithPrefix() + { + var obj = new + { + custom_session_id = "my-id", + system_prompt = "You are helpful", + variables = new { name = "Alice", age = 25 }, + }; + var result = QueryStringConverter.ToDeepObject("session_settings", obj); + var expected = new List> + { + new("session_settings[custom_session_id]", "my-id"), + new("session_settings[system_prompt]", "You are helpful"), + new("session_settings[variables][name]", "Alice"), + new("session_settings[variables][age]", "25"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_ExplodedForm_WithPrefix() + { + var obj = new { Name = "John", Tags = new[] { "Developer", "Blogger" } }; + var result = QueryStringConverter.ToExplodedForm("user", obj); + var expected = new List> + { + new("user[Name]", "John"), + new("user[Tags]", "Developer"), + new("user[Tags]", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs new file mode 100644 index 000000000000..39ac12fc18b2 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs @@ -0,0 +1,1120 @@ +using global::System.Net.Http; +using global::System.Text; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; +using SystemTask = global::System.Threading.Tasks.Task; + +namespace SeedApi.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class MultipartFormTests +{ + private static SimpleObject _simpleObject = new(); + + private static string _simpleFormEncoded = + "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data,2023-10-01,12:00:00,01:00:00,1a1bb98f-47c6-407b-9481-78476affe52a,true,42,A"; + + private static string _simpleExplodedFormEncoded = + "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data&Values=2023-10-01&Values=12:00:00&Values=01:00:00&Values=1a1bb98f-47c6-407b-9481-78476affe52a&Values=true&Values=42&Values=A"; + + private static ComplexObject _complexObject = new(); + + private static string _complexJson = """ + { + "meta": "data", + "Nested": { + "foo": "value" + }, + "NestedDictionary": { + "key": { + "foo": "value" + } + }, + "ListOfObjects": [ + { + "foo": "value" + }, + { + "foo": "value2" + } + ], + "Date": "2023-10-01", + "Time": "12:00:00", + "Duration": "01:00:00", + "Id": "1a1bb98f-47c6-407b-9481-78476affe52a", + "IsActive": true, + "Count": 42, + "Initial": "A" + } + """; + + [Test] + public async SystemTask ShouldAddStringPart() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, partInput]); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddStringPart() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", null); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithNullsInList() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, null, partInput]); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringPart_WithContentType() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput, "text/xml"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringPart_WithContentTypeAndCharset() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput, "text/xml; charset=utf-8"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithContentType() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, partInput], "text/xml"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithContentTypeAndCharset() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts( + "strings", + [partInput, partInput], + "text/xml; charset=utf-8" + ); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFileName() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithoutFileName() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", partInput); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithContentType() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter + { + Stream = partInput, + FileName = "test.txt", + ContentType = "text/plain", + }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithContentTypeAndCharset() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter + { + Stream = partInput, + FileName = "test.txt", + ContentType = "text/plain; charset=utf-8", + }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain; charset=utf-8 + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFallbackContentType() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "text/plain"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFallbackContentTypeAndCharset() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "text/plain; charset=utf-8"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain; charset=utf-8 + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameters() + { + var (partInput1, partExpectedString1) = GetFileParameterTestData(); + var (partInput2, partExpectedString2) = GetFileParameterTestData(); + var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; + var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterParts("file", [file1, file2]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt + + {partExpectedString1} + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt + + {partExpectedString2} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameters_WithNullsInList() + { + var (partInput1, partExpectedString1) = GetFileParameterTestData(); + var (partInput2, partExpectedString2) = GetFileParameterTestData(); + var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; + var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterParts("file", [file1, null, file2]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt + + {partExpectedString1} + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt + + {partExpectedString2} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddFileParameter() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonPart_WithComplexObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonPart("object", _complexObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=object + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonPart_WithComplexObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [_complexObject, _complexObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddJsonPart() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonPart("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [_complexObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [new { }], "application/json-patch+json"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $$""" + --{{boundary}} + Content-Type: application/json-patch+json + Content-Disposition: form-data; name=objects + + {} + --{{boundary}}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithSimpleObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart("object", _simpleObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=object + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithSimpleObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, _simpleObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddFormEncodedParts_WithNull() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddFormEncodedParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedPart_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedPart_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart("object", _simpleObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=object + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, _simpleObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNull() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + private static string EscapeFormEncodedString(string input) + { + return string.Join( + "&", + input + .Split('&') + .Select(x => x.Split('=')) + .Select(x => $"{Uri.EscapeDataString(x[0])}={Uri.EscapeDataString(x[1])}") + ); + } + + private static string GetBoundary(MultipartFormDataContent content) + { + return content + .Headers.ContentType?.Parameters.Single(p => + p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) + ) + .Value?.Trim('"') ?? throw new global::System.Exception("Boundary not found"); + } + + private static SeedApi.Core.MultipartFormRequest CreateMultipartFormRequest() + { + return new SeedApi.Core.MultipartFormRequest + { + BaseUrl = "https://localhost", + Method = HttpMethod.Post, + Path = "", + }; + } + + private static (Stream partInput, string partExpectedString) GetFileParameterTestData() + { + const string partExpectedString = "file content"; + var partInput = new MemoryStream(Encoding.Default.GetBytes(partExpectedString)); + return (partInput, partExpectedString); + } + + private class SimpleObject + { + [JsonPropertyName("meta")] + public string Meta { get; set; } = "data"; + public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); + public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); + public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); + public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); + public bool IsActive { get; set; } = true; + public int Count { get; set; } = 42; + public char Initial { get; set; } = 'A'; + public IEnumerable Values { get; set; } = + [ + "data", + DateOnly.Parse("2023-10-01"), + TimeOnly.Parse("12:00:00"), + TimeSpan.FromHours(1), + Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), + true, + 42, + 'A', + ]; + } + + private class ComplexObject + { + [JsonPropertyName("meta")] + public string Meta { get; set; } = "data"; + + public object Nested { get; set; } = new { foo = "value" }; + + public Dictionary NestedDictionary { get; set; } = + new() { { "key", new { foo = "value" } } }; + + public IEnumerable ListOfObjects { get; set; } = + new List { new { foo = "value" }, new { foo = "value2" } }; + + public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); + public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); + public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); + public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); + public bool IsActive { get; set; } = true; + public int Count { get; set; } = 42; + public char Initial { get; set; } = 'A'; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs new file mode 100644 index 000000000000..f4edf9ef52a6 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs @@ -0,0 +1,108 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class QueryParameterTests +{ + [Test] + public void QueryParameters_BasicParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .Add("baz", "qux") + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar&baz=qux")); + } + + [Test] + public void QueryParameters_SpecialCharacterEscaping() + { + var queryString = new QueryStringBuilder.Builder() + .Add("email", "bob+test@example.com") + .Add("%Complete", "100") + .Add("space test", "hello world") + .Build(); + + Assert.That(queryString, Does.Contain("email=bob%2Btest@example.com")); + Assert.That(queryString, Does.Contain("%25Complete=100")); + Assert.That(queryString, Does.Contain("space%20test=hello%20world")); + } + + [Test] + public void QueryParameters_MergeAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("sdk", "param") + .MergeAdditional(new List> { new("user", "value") }) + .Build(); + + Assert.That(queryString, Does.Contain("sdk=param")); + Assert.That(queryString, Does.Contain("user=value")); + } + + [Test] + public void QueryParameters_AdditionalOverridesSdk() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "sdk_value") + .MergeAdditional(new List> { new("foo", "user_override") }) + .Build(); + + Assert.That(queryString, Does.Contain("foo=user_override")); + Assert.That(queryString, Does.Not.Contain("sdk_value")); + } + + [Test] + public void QueryParameters_AdditionalMultipleValues() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "sdk_value") + .MergeAdditional( + new List> { new("foo", "user1"), new("foo", "user2") } + ) + .Build(); + + Assert.That(queryString, Does.Contain("foo=user1")); + Assert.That(queryString, Does.Contain("foo=user2")); + Assert.That(queryString, Does.Not.Contain("sdk_value")); + } + + [Test] + public void QueryParameters_OnlyAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .MergeAdditional( + new List> { new("foo", "bar"), new("baz", "qux") } + ) + .Build(); + + Assert.That(queryString, Does.Contain("foo=bar")); + Assert.That(queryString, Does.Contain("baz=qux")); + } + + [Test] + public void QueryParameters_EmptyAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .MergeAdditional(new List>()) + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar")); + } + + [Test] + public void QueryParameters_NullAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .MergeAdditional(null) + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar")); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs new file mode 100644 index 000000000000..22e8103847cb --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs @@ -0,0 +1,406 @@ +using global::System.Net.Http; +using global::System.Text.Json; +using NUnit.Framework; +using SeedApi.Core; +using WireMock.Server; +using SystemTask = global::System.Threading.Tasks.Task; +using WireMockRequest = WireMock.RequestBuilders.Request; +using WireMockResponse = WireMock.ResponseBuilders.Response; + +namespace SeedApi.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class RetriesTests +{ + private const int MaxRetries = 3; + private WireMockServer _server; + private HttpClient _httpClient; + private RawClient _rawClient; + private string _baseUrl; + + [SetUp] + public void SetUp() + { + _server = WireMockServer.Start(); + _baseUrl = _server.Url ?? ""; + _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) }; + _rawClient = new RawClient( + new ClientOptions { HttpClient = _httpClient, MaxRetries = MaxRetries } + ) + { + BaseRetryDelay = 0, + }; + } + + [Test] + [TestCase(408)] + [TestCase(429)] + [TestCase(500)] + [TestCase(504)] + public async SystemTask SendRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Server Error") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + + Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); + } + } + + [Test] + [TestCase(400)] + [TestCase(409)] + public async SystemTask SendRequestAsync_ShouldRetry_OnNonRetryableStatusCodes(int statusCode) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure")); + + var request = new JsonRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + Body = new { }, + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(statusCode)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldNotRetry_WithStreamRequest() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); + + var request = new StreamRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + Body = new MemoryStream(), + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(429)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest_WithStream() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); + + var request = new SeedApi.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddFileParameterPart("file", new MemoryStream()); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(429)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_WithoutStream() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WhenStateIs("Server Error") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(429)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new SeedApi.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddJsonPart("object", new { }); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithSecondsValue() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfter") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse.Create().WithStatusCode(429).WithHeader("Retry-After", "1") + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfter") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithHttpDateValue() + { + var retryAfterDate = DateTimeOffset.UtcNow.AddSeconds(1).ToString("R"); + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfterDate") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse + .Create() + .WithStatusCode(429) + .WithHeader("Retry-After", retryAfterDate) + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfterDate") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectXRateLimitResetHeader() + { + var resetTime = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds().ToString(); + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RateLimitReset") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse + .Create() + .WithStatusCode(429) + .WithHeader("X-RateLimit-Reset", resetTime) + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RateLimitReset") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldPreserveJsonBody_OnRetry() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryWithBody") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(500)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryWithBody") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new JsonRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + Body = new { key = "value" }, + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + + // Verify the retried request preserved the JSON body (compare parsed to ignore formatting differences) + var retriedEntry = _server.LogEntries.ElementAt(1); + using var actualJson = JsonDocument.Parse(retriedEntry.RequestMessage.Body!); + Assert.That(actualJson.RootElement.GetProperty("key").GetString(), Is.EqualTo("value")); + } + } + + [Test] + public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryMultipart") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(500)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryMultipart") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new SeedApi.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddJsonPart("object", new { key = "value" }); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + + // Verify the retried request preserved the multipart body (check key/value presence to ignore formatting differences) + var retriedEntry = _server.LogEntries.ElementAt(1); + Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"key\"")); + Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"value\"")); + } + } + + [TearDown] + public void TearDown() + { + _server.Dispose(); + _httpClient.Dispose(); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs new file mode 100644 index 000000000000..27337ed343e8 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs @@ -0,0 +1,269 @@ +using global::System.Net; +using global::System.Net.Http.Headers; +using NUnit.Framework; +using SeedApi; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class WithRawResponseTests +{ + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_DirectAwait_ReturnsData() + { + // Arrange + var expectedData = "test-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act + var result = await task; + + // Assert + Assert.That(result, Is.EqualTo(expectedData)); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_WithRawResponse_ReturnsDataAndMetadata() + { + // Arrange + var expectedData = "test-data"; + var expectedStatusCode = HttpStatusCode.Created; + var task = CreateWithRawResponseTask(expectedData, expectedStatusCode); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.Data, Is.EqualTo(expectedData)); + Assert.That(result.RawResponse.StatusCode, Is.EqualTo(expectedStatusCode)); + Assert.That(result.RawResponse.Url, Is.Not.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_CaseInsensitive() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Request-Id", "12345"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act & Assert + Assert.That(headers.TryGetValue("X-Request-Id", out var value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + + Assert.That(headers.TryGetValue("x-request-id", out value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + + Assert.That(headers.TryGetValue("X-REQUEST-ID", out value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_ReturnsMultipleValues() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("Set-Cookie", new[] { "cookie1=value1", "cookie2=value2" }); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValues("Set-Cookie", out var values); + + // Assert + Assert.That(success, Is.True); + Assert.That(values, Is.Not.Null); + Assert.That(values!.Count(), Is.EqualTo(2)); + Assert.That(values, Does.Contain("cookie1=value1")); + Assert.That(values, Does.Contain("cookie2=value2")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_ContentType_ReturnsValue() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Content = new StringContent( + "{}", + global::System.Text.Encoding.UTF8, + "application/json" + ); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var contentType = headers.ContentType; + + // Assert + Assert.That(contentType, Is.Not.Null); + Assert.That(contentType, Does.Contain("application/json")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_ContentLength_ReturnsValue() + { + // Arrange + var content = "test content"; + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Content = new StringContent(content); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var contentLength = headers.ContentLength; + + // Assert + Assert.That(contentLength, Is.Not.Null); + Assert.That(contentLength, Is.GreaterThan(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_Contains_ReturnsTrueForExistingHeader() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Custom-Header", "value"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act & Assert + Assert.That(headers.Contains("X-Custom-Header"), Is.True); + Assert.That(headers.Contains("x-custom-header"), Is.True); + Assert.That(headers.Contains("NonExistent"), Is.False); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_Enumeration_IncludesAllHeaders() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Header-1", "value1"); + response.Headers.Add("X-Header-2", "value2"); + response.Content = new StringContent("test"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var allHeaders = headers.ToList(); + + // Assert + Assert.That(allHeaders.Count, Is.GreaterThan(0)); + Assert.That(allHeaders.Any(h => h.Name == "X-Header-1"), Is.True); + Assert.That(allHeaders.Any(h => h.Name == "X-Header-2"), Is.True); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_ErrorStatusCode_StillReturnsMetadata() + { + // Arrange + var expectedData = "error-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.BadRequest); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.Data, Is.EqualTo(expectedData)); + Assert.That(result.RawResponse.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_Url_IsPreserved() + { + // Arrange + var expectedUrl = new Uri("https://api.example.com/users/123"); + var task = CreateWithRawResponseTask("data", HttpStatusCode.OK, expectedUrl); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.RawResponse.Url, Is.EqualTo(expectedUrl)); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_NonExistentHeader_ReturnsFalse() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValue("X-NonExistent", out var value); + + // Assert + Assert.That(success, Is.False); + Assert.That(value, Is.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_NonExistentHeader_ReturnsFalse() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValues("X-NonExistent", out var values); + + // Assert + Assert.That(success, Is.False); + Assert.That(values, Is.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_ImplicitConversion_ToTask() + { + // Arrange + var expectedData = "test-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act - implicitly convert to Task + global::System.Threading.Tasks.Task regularTask = task; + var result = await regularTask; + + // Assert + Assert.That(result, Is.EqualTo(expectedData)); + } + + [Test] + public void WithRawResponseTask_ImplicitConversion_AssignToTaskVariable() + { + // Arrange + var expectedData = "test-data"; + var wrappedTask = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act - assign to Task variable + global::System.Threading.Tasks.Task regularTask = wrappedTask; + + // Assert + Assert.That(regularTask, Is.Not.Null); + Assert.That(regularTask, Is.InstanceOf>()); + } + + // Helper methods + + private static WithRawResponseTask CreateWithRawResponseTask( + T data, + HttpStatusCode statusCode, + Uri? url = null + ) + { + url ??= new Uri("https://api.example.com/test"); + using var httpResponse = CreateHttpResponse(statusCode); + httpResponse.RequestMessage = new HttpRequestMessage(HttpMethod.Get, url); + + var rawResponse = new RawResponse + { + StatusCode = statusCode, + Url = url, + Headers = ResponseHeaders.FromHttpResponseMessage(httpResponse), + }; + + var withRawResponse = new WithRawResponse { Data = data, RawResponse = rawResponse }; + + var task = global::System.Threading.Tasks.Task.FromResult(withRawResponse); + return new WithRawResponseTask(task); + } + + private static HttpResponseMessage CreateHttpResponse(HttpStatusCode statusCode) + { + return new HttpResponseMessage(statusCode) { Content = new StringContent("") }; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props new file mode 100644 index 000000000000..aac9b5020d80 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props @@ -0,0 +1,6 @@ + + diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj new file mode 100644 index 000000000000..a4dfe85227f6 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj @@ -0,0 +1,33 @@ + + + net9.0 + 12 + enable + enable + false + true + true + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs new file mode 100644 index 000000000000..18aaa67904d8 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs @@ -0,0 +1,6 @@ +using NUnit.Framework; + +namespace SeedApi.Test; + +[TestFixture] +public class TestClient; diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs new file mode 100644 index 000000000000..3f4ed8503ec1 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs @@ -0,0 +1,37 @@ +using NUnit.Framework; +using SeedApi; +using WireMock.Logging; +using WireMock.Server; +using WireMock.Settings; + +namespace SeedApi.Test.Unit.MockServer; + +public class BaseMockServerTest +{ + protected WireMockServer Server { get; set; } = null!; + + protected SeedApiClient Client { get; set; } = null!; + + protected RequestOptions RequestOptions { get; set; } = new(); + + [OneTimeSetUp] + public void GlobalSetup() + { + // Start the WireMock server + Server = WireMockServer.Start( + new WireMockServerSettings { Logger = new WireMockConsoleLogger() } + ); + + // Initialize the Client + Client = new SeedApiClient( + clientOptions: new ClientOptions { BaseUrl = Server.Urls[0], MaxRetries = 0 } + ); + } + + [OneTimeTearDown] + public void GlobalTeardown() + { + Server.Stop(); + Server.Dispose(); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs new file mode 100644 index 000000000000..053c0fd711a1 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs @@ -0,0 +1,92 @@ +using NUnit.Framework; +using SeedApi; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class CreateRuleTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string requestJson = """ + { + "name": "name", + "executionContext": "prod" + } + """; + + const string mockResponse = """ + { + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/rules") + .WithHeader("Content-Type", "application/json") + .UsingPost() + .WithBodyAsJson(requestJson) + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } + ); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string requestJson = """ + { + "name": "name", + "executionContext": "prod" + } + """; + + const string mockResponse = """ + { + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/rules") + .WithHeader("Content-Type", "application/json") + .UsingPost() + .WithBodyAsJson(requestJson) + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } + ); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs new file mode 100644 index 000000000000..cec77fca473f --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs @@ -0,0 +1,59 @@ +using NUnit.Framework; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class GetEntityTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "id": "id", + "name": "name", + "summary": "summary", + "status": "active" + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetEntityAsync(); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "id": "id", + "name": "name", + "summary": "summary", + "status": "active" + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetEntityAsync(); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs new file mode 100644 index 000000000000..384bd8be8464 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs @@ -0,0 +1,63 @@ +using NUnit.Framework; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class GetOrganizationTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "id": "id", + "metadata": { + "region": "region", + "domain": "domain" + }, + "name": "name" + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetOrganizationAsync(); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "id": "id", + "metadata": { + "region": "region", + "domain": "domain" + }, + "name": "name" + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetOrganizationAsync(); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs new file mode 100644 index 000000000000..631e8034f03a --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs @@ -0,0 +1,75 @@ +using NUnit.Framework; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class ListUsersTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ] + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.ListUsersAsync(); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "email": "email" + } + ] + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.ListUsersAsync(); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs new file mode 100644 index 000000000000..afc1fceaf710 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs @@ -0,0 +1,87 @@ +using NUnit.Framework; +using SeedApi; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class SearchRuleTypesTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/rule-types") + .WithParam("query", "query") + .UsingGet() + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.SearchRuleTypesAsync( + new SearchRuleTypesRequest { Query = "query" } + ); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/rule-types").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs new file mode 100644 index 000000000000..3ac7e5310f95 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs @@ -0,0 +1,219 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; +using SeedApi; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle AdditionalProperties values. +/// +public static class AdditionalPropertiesComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their + /// serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) + { + constraint.Using( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + /// + /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing + /// their serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) + { + constraint.Using>( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => + JsonElementsAreEqual(x, y); + + private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) + { + if (x.ValueKind != y.ValueKind) + { + return false; + } + + return x.ValueKind switch + { + JsonValueKind.Object => CompareJsonObjects(x, y), + JsonValueKind.Array => CompareJsonArrays(x, y), + JsonValueKind.String => x.GetString() == y.GetString(), + JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), + JsonValueKind.True => true, + JsonValueKind.False => true, + JsonValueKind.Null => true, + _ => false, + }; + } + + private static bool CompareJsonObjects(JsonElement x, JsonElement y) + { + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + if (xProps.Count != yProps.Count) + { + return false; + } + + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + return false; + } + + if (!JsonElementsAreEqual(xProps[key], yProps[key])) + { + return false; + } + } + + return true; + } + + private static bool CompareJsonArrays(JsonElement x, JsonElement y) + { + var xArray = x.EnumerateArray().ToList(); + var yArray = y.EnumerateArray().ToList(); + + if (xArray.Count != yArray.Count) + { + return false; + } + + for (var i = 0; i < xArray.Count; i++) + { + if (!JsonElementsAreEqual(xArray[i], yArray[i])) + { + return false; + } + } + + return true; + } + + /// + /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. + /// When UsingPropertiesComparer() walks object properties and encounters a property typed as + /// 'object', the expected side may be a Dictionary<object, object?> while the actual + /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing + /// the non-JsonElement side and comparing JSON representations. + /// + /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic + /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only + /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all + /// same-type comparisons normally. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) + { + // Handle: expected is non-JsonElement, actual is JsonElement + constraint.Using( + (actualJsonElement, expectedObj) => + { + try + { + var expectedElement = JsonUtils.SerializeToElement(expectedObj); + return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); + } + catch + { + return false; + } + } + ); + // Handle reverse: expected is JsonElement, actual is non-JsonElement + constraint.Using( + (actualObj, expectedJsonElement) => + { + try + { + var actualElement = JsonUtils.SerializeToElement(actualObj); + return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); + } + catch + { + return false; + } + } + ); + return constraint; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs new file mode 100644 index 000000000000..3f4b5eb602b2 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs @@ -0,0 +1,29 @@ +using global::System.Text.Json; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Utils; + +internal static class JsonAssert +{ + /// + /// Asserts that the serialized JSON of an object equals the expected JSON string. + /// Uses JsonElement comparison for reliable deep equality of collections and union types. + /// + internal static void AreEqual(object actual, string expectedJson) + { + var actualElement = JsonUtils.SerializeToElement(actual); + var expectedElement = JsonUtils.Deserialize(expectedJson); + Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); + } + + /// + /// Asserts that the given JSON string survives a deserialization/serialization round-trip + /// intact: deserializes to T then re-serializes and compares to the original JSON. + /// + internal static void Roundtrips(string json) + { + var deserialized = JsonUtils.Deserialize(json); + AreEqual(deserialized!, json); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs new file mode 100644 index 000000000000..a37ef402c1ac --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs @@ -0,0 +1,236 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle JsonElement objects. +/// +public static class JsonElementComparerExtensions +{ + /// + /// Extension method for comparing JsonElement objects in NUnit tests. + /// Property order doesn't matter, but array order does matter. + /// Includes special handling for DateTime string formats. + /// + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare JsonElements with detailed diffs. + public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) + { + return constraint.Using(new JsonElementComparer()); + } +} + +/// +/// Equality comparer for JsonElement with detailed reporting. +/// Property order doesn't matter, but array order does matter. +/// Now includes special handling for DateTime string formats with improved null handling. +/// +public class JsonElementComparer : IEqualityComparer +{ + private string _failurePath = string.Empty; + + /// + public bool Equals(JsonElement x, JsonElement y) + { + _failurePath = string.Empty; + return CompareJsonElements(x, y, string.Empty); + } + + /// + public int GetHashCode(JsonElement obj) + { + return JsonSerializer.Serialize(obj).GetHashCode(); + } + + private bool CompareJsonElements(JsonElement x, JsonElement y, string path) + { + // If value kinds don't match, they're not equivalent + if (x.ValueKind != y.ValueKind) + { + _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; + return false; + } + + switch (x.ValueKind) + { + case JsonValueKind.Object: + return CompareJsonObjects(x, y, path); + + case JsonValueKind.Array: + return CompareJsonArraysInOrder(x, y, path); + + case JsonValueKind.String: + string? xStr = x.GetString(); + string? yStr = y.GetString(); + + // Handle null strings + if (xStr is null && yStr is null) + return true; + + if (xStr is null || yStr is null) + { + _failurePath = + $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; + return false; + } + + // Check if they are identical strings + if (xStr == yStr) + return true; + + // Try to handle DateTime strings + if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) + { + if (AreEquivalentDateTimeStrings(xStr, yStr)) + return true; + } + + _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; + return false; + + case JsonValueKind.Number: + if (x.GetDecimal() != y.GetDecimal()) + { + _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; + return false; + } + + return true; + + case JsonValueKind.True: + case JsonValueKind.False: + if (x.GetBoolean() != y.GetBoolean()) + { + _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; + return false; + } + + return true; + + case JsonValueKind.Null: + return true; + + default: + _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; + return false; + } + } + + private bool IsLikelyDateTimeString(string? str) + { + // Simple heuristic to identify likely ISO date time strings + return str is not null + && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); + } + + private bool AreEquivalentDateTimeStrings(string str1, string str2) + { + // Try to parse both as DateTime + if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) + { + return dt1 == dt2; + } + + return false; + } + + private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) + { + // Create dictionaries for both JSON objects + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + // Check if all properties in x exist in y + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + _failurePath = $"{path}: Missing property '{key}'"; + return false; + } + } + + // Check if y has extra properties + foreach (var key in yProps.Keys) + { + if (!xProps.ContainsKey(key)) + { + _failurePath = $"{path}: Unexpected property '{key}'"; + return false; + } + } + + // Compare each property value + foreach (var key in xProps.Keys) + { + var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; + if (!CompareJsonElements(xProps[key], yProps[key], propPath)) + { + return false; + } + } + + return true; + } + + private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) + { + var xArray = x.EnumerateArray(); + var yArray = y.EnumerateArray(); + + // Count x elements + var xCount = 0; + var xElements = new List(); + foreach (var item in xArray) + { + xElements.Add(item); + xCount++; + } + + // Count y elements + var yCount = 0; + var yElements = new List(); + foreach (var item in yArray) + { + yElements.Add(item); + yCount++; + } + + // Check if counts match + if (xCount != yCount) + { + _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; + return false; + } + + // Compare elements in order + for (var i = 0; i < xCount; i++) + { + var itemPath = $"{path}[{i}]"; + if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) + { + return false; + } + } + + return true; + } + + /// + public override string ToString() + { + if (!string.IsNullOrEmpty(_failurePath)) + { + return $"JSON comparison failed at {_failurePath}"; + } + + return "JsonElementEqualityComparer"; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs new file mode 100644 index 000000000000..816f4c010e6e --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs @@ -0,0 +1,32 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class NUnitExtensions +{ + /// + /// Modifies the EqualConstraint to use our own set of default comparers. + /// + /// + /// + public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => + constraint + .UsingPropertiesComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingOneOfComparer() + .UsingJsonElementComparer() + .UsingOptionalComparer() + .UsingObjectDictionaryComparer() + .UsingAdditionalPropertiesComparer() + .UsingJsonSerializationComparer(); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs new file mode 100644 index 000000000000..767439174363 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs @@ -0,0 +1,86 @@ +using NUnit.Framework.Constraints; +using OneOf; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle OneOf values. +/// +public static class EqualConstraintExtensions +{ + /// + /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOneOf types + constraint.Using( + (x, y) => + { + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (x.Value is null && y.Value is null) + { + return true; + } + + if (x.Value is null) + { + return false; + } + + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs new file mode 100644 index 000000000000..98bfcac477b8 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs @@ -0,0 +1,104 @@ +using NUnit.Framework.Constraints; +using OneOf; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle Optional values. +/// +public static class OptionalComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOptional types + constraint.Using( + (x, y) => + { + // Both must have the same IsDefined state + if (x.IsDefined != y.IsDefined) + { + return false; + } + + // If both are undefined, they're equal + if (!x.IsDefined) + { + return true; + } + + // Both are defined, compare their boxed values + var xValue = x.GetBoxedValue(); + var yValue = y.GetBoxedValue(); + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (xValue is null && yValue is null) + { + return true; + } + + if (xValue is null || yValue is null) + { + return false; + } + + // Use NUnit's property comparer for the inner values + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values within Optional types. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs new file mode 100644 index 000000000000..fc0b595a5e54 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs @@ -0,0 +1,87 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class ReadOnlyMemoryComparerExtensions +{ + /// + /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. + /// + /// The type of elements in the ReadOnlyMemory. + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare ReadOnlyMemory<T>. + public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) + where T : IComparable + { + return constraint.Using(new ReadOnlyMemoryComparer()); + } +} + +/// +/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. +/// +/// +/// The type of elements in the ReadOnlyMemory. +/// +public class ReadOnlyMemoryComparer : IComparer> + where T : IComparable +{ + /// + public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) + { + // Check if sequences are equal + var xSpan = x.Span; + var ySpan = y.Span; + + // Optimized case for IEquatable implementations + if (typeof(IEquatable).IsAssignableFrom(typeof(T))) + { + var areEqual = xSpan.SequenceEqual(ySpan); + if (areEqual) + { + return 0; // Sequences are equal + } + } + else + { + // Manual equality check for non-IEquatable types + if (xSpan.Length == ySpan.Length) + { + var areEqual = true; + for (var i = 0; i < xSpan.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + areEqual = false; + break; + } + } + + if (areEqual) + { + return 0; // Sequences are equal + } + } + } + + // For non-equal sequences, we need to return a consistent ordering + // First compare lengths + if (x.Length != y.Length) + return x.Length.CompareTo(y.Length); + + // Same length but different content - compare first differing element + for (var i = 0; i < x.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + return xSpan[i].CompareTo(ySpan[i]); + } + } + + // Should never reach here if not equal + return 0; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs new file mode 100644 index 000000000000..838d0c00b960 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs @@ -0,0 +1,13 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// The response object returned from the API. +/// +internal record ApiResponse +{ + internal required int StatusCode { get; init; } + + internal required HttpResponseMessage Raw { get; init; } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs new file mode 100644 index 000000000000..9a3cabb806a3 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs @@ -0,0 +1,67 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; +using global::System.Text; + +namespace SeedApi.Core; + +internal abstract record BaseRequest +{ + internal string? BaseUrl { get; init; } + + internal required HttpMethod Method { get; init; } + + internal required string Path { get; init; } + + internal string? ContentType { get; init; } + + /// + /// The query string for this request (including the leading '?' if non-empty). + /// + internal string? QueryString { get; init; } + + internal Dictionary Headers { get; init; } = + new(StringComparer.OrdinalIgnoreCase); + + internal IRequestOptions? Options { get; init; } + + internal abstract HttpContent? CreateContent(); + + protected static ( + Encoding encoding, + string? charset, + string mediaType + ) ParseContentTypeOrDefault( + string? contentType, + Encoding encodingFallback, + string mediaTypeFallback + ) + { + var encoding = encodingFallback; + var mediaType = mediaTypeFallback; + string? charset = null; + if (string.IsNullOrEmpty(contentType)) + { + return (encoding, charset, mediaType); + } + + if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) + { + return (encoding, charset, mediaType); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) + { + charset = mediaTypeHeaderValue.CharSet; + encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) + { + mediaType = mediaTypeHeaderValue.MediaType; + } + + return (encoding, charset, mediaType); + } + + protected static Encoding Utf8NoBom => EncodingCache.Utf8NoBom; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs new file mode 100644 index 000000000000..b684f33d750e --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +internal class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter, new() +{ + private static readonly TConverterType _converter = new TConverterType(); + + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new global::System.Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs new file mode 100644 index 000000000000..ccf4e963cc89 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace SeedApi.Core; + +internal static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; + public const string DateFormat = "yyyy-MM-dd"; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs new file mode 100644 index 000000000000..af61cc061ae5 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs @@ -0,0 +1,747 @@ +// ReSharper disable All +#pragma warning disable + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using global::System.Diagnostics; +using global::System.Diagnostics.CodeAnalysis; +using global::System.Globalization; +using global::System.Runtime.CompilerServices; +using global::System.Runtime.InteropServices; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +// ReSharper disable SuggestVarOrType_SimpleTypes +// ReSharper disable SuggestVarOrType_BuiltInTypes + +namespace SeedApi.Core +{ + /// + /// Custom converter for handling the data type with the System.Text.Json library. + /// + /// + /// This class backported from: + /// + /// System.Text.Json.Serialization.Converters.DateOnlyConverter + /// + public sealed class DateOnlyConverter : JsonConverter + { + private const int FormatLength = 10; // YYYY-MM-DD + + private const int MaxEscapedFormatLength = + FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; + + /// + public override DateOnly Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType != JsonTokenType.String) + { + ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); + } + + return ReadCore(ref reader); + } + + /// + public override DateOnly ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + return ReadCore(ref reader); + } + + private static DateOnly ReadCore(ref Utf8JsonReader reader) + { + if ( + !JsonHelpers.IsInRangeInclusive( + reader.ValueLength(), + FormatLength, + MaxEscapedFormatLength + ) + ) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + scoped ReadOnlySpan source; + if (!reader.HasValueSequence && !reader.ValueIsEscaped) + { + source = reader.ValueSpan; + } + else + { + Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; + int bytesWritten = reader.CopyString(stackSpan); + source = stackSpan.Slice(0, bytesWritten); + } + + if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + return value; + } + + /// + public override void Write( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WriteStringValue(buffer); + } + + /// + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WritePropertyName(buffer); + } + } + + internal static class JsonConstants + { + // The maximum number of fraction digits the Json DateTime parser allows + public const int DateTimeParseNumFractionDigits = 16; + + // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. + public const int MaxExpansionFactorWhileEscaping = 6; + + // The largest fraction expressible by TimeSpan and DateTime formats + public const int MaxDateTimeFraction = 9_999_999; + + // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. + public const int DateTimeNumFractionDigits = 7; + + public const byte UtcOffsetToken = (byte)'Z'; + + public const byte TimePrefix = (byte)'T'; + + public const byte Period = (byte)'.'; + + public const byte Hyphen = (byte)'-'; + + public const byte Colon = (byte)':'; + + public const byte Plus = (byte)'+'; + } + + // ReSharper disable SuggestVarOrType_Elsewhere + // ReSharper disable SuggestVarOrType_SimpleTypes + // ReSharper disable SuggestVarOrType_BuiltInTypes + + internal static class JsonHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => + (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); + + public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; + + [StructLayout(LayoutKind.Auto)] + private struct DateTimeParseData + { + public int Year; + public int Month; + public int Day; + public bool IsCalendarDateOnly; + public int Hour; + public int Minute; + public int Second; + public int Fraction; // This value should never be greater than 9_999_999. + public int OffsetHours; + public int OffsetMinutes; + + // ReSharper disable once NotAccessedField.Local + public byte OffsetToken; + } + + public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) + { + if ( + TryParseDateTimeOffset(source, out DateTimeParseData parseData) + && parseData.IsCalendarDateOnly + && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) + ) + { + value = DateOnly.FromDateTime(dateTime); + return true; + } + + value = default; + return false; + } + + /// + /// ISO 8601 date time parser (ISO 8601-1:2019). + /// + /// The date/time to parse in UTF-8 format. + /// The parsed for the given . + /// + /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day + /// representations with optional specification of seconds and fractional seconds. + /// + /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). + /// If unspecified they are considered to be local per spec. + /// + /// Examples: (TZD is either "Z" or hh:mm offset from UTC) + /// + /// YYYY-MM-DD (e.g. 1997-07-16) + /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) + /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) + /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) + /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) + /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) + /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) + /// + /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). + /// The extended variants have separator characters between components ('-', ':', '.', etc.). + /// Spaces are not permitted. + /// + /// "true" if successfully parsed. + private static bool TryParseDateTimeOffset( + ReadOnlySpan source, + out DateTimeParseData parseData + ) + { + parseData = default; + + // too short datetime + Debug.Assert(source.Length >= 10); + + // Parse the calendar date + // ----------------------- + // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" + // [dateX] = [year]["-"][month]["-"][day] + // [year] = [YYYY] [0000 - 9999] (4.3.2) + // [month] = [MM] [01 - 12] (4.3.3) + // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) + // + // Note: 5.2.2.2 "Representations with reduced precision" allows for + // just [year]["-"][month] (a) and just [year] (b), but we currently + // don't permit it. + + { + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + uint digit3 = source[2] - (uint)'0'; + uint digit4 = source[3] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) + { + return false; + } + + parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); + } + + if ( + source[4] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) + || source[7] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) + ) + { + return false; + } + + // We now have YYYY-MM-DD [dateX] + // ReSharper disable once ConvertIfStatementToSwitchStatement + if (source.Length == 10) + { + parseData.IsCalendarDateOnly = true; + return true; + } + + // Parse the time of day + // --------------------- + // + // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" + // [timeX] = ["T"][hour][":"][min][":"][sec] + // [hour] = [hh] [00 - 23] (4.3.8a) + // [minute] = [mm] [00 - 59] (4.3.9a) + // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) + // + // ISO 8601-1:2019 5.3.3 "UTC of day" + // [timeX]["Z"] + // + // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between + // local timescale and UTC" (Extended format) + // + // [shiftX] = ["+"|"-"][hour][":"][min] + // + // Notes: + // + // "T" is optional per spec, but _only_ when times are used alone. In our + // case, we're reading out a complete date & time and as such require "T". + // (5.4.2.1b). + // + // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations + // with reduced precision". 5.3.1.3b allows just specifying the hour, but + // we currently don't permit this. + // + // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). + // We only allow fractions for seconds currently. Lower order components + // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be + // one digit, but the max number of digits is implementation defined. We + // currently allow up to 16 digits of fractional seconds only. While we + // support 16 fractional digits we only parse the first seven, anything + // past that is considered a zero. This is to stay compatible with the + // DateTime implementation which is limited to this resolution. + + if (source.Length < 16) + { + // Source does not have enough characters for YYYY-MM-DDThh:mm + return false; + } + + // Parse THH:MM (e.g. "T10:32") + if ( + source[10] != JsonConstants.TimePrefix + || source[13] != JsonConstants.Colon + || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) + || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm + Debug.Assert(source.Length >= 16); + if (source.Length == 16) + { + return true; + } + + byte curByte = source[16]; + int sourceIndex = 17; + + // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Colon: + break; + default: + return false; + } + + // Try reading the seconds + if ( + source.Length < 19 + || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss + Debug.Assert(source.Length >= 19); + if (source.Length == 19) + { + return true; + } + + curByte = source[19]; + sourceIndex = 20; + + // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Period: + break; + default: + return false; + } + + // Source does not have enough characters for second fractions (i.e. ".s") + // YYYY-MM-DDThh:mm:ss.s + if (source.Length < 21) + { + return false; + } + + // Parse fraction. This value should never be greater than 9_999_999 + int numDigitsRead = 0; + int fractionEnd = Math.Min( + sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, + source.Length + ); + + while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) + { + if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); + numDigitsRead++; + } + + sourceIndex++; + } + + if (parseData.Fraction != 0) + { + while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction *= 10; + numDigitsRead++; + } + } + + // We now have YYYY-MM-DDThh:mm:ss.s + Debug.Assert(sourceIndex <= source.Length); + if (sourceIndex == source.Length) + { + return true; + } + + curByte = source[sourceIndex++]; + + // TZD ['Z'|'+'|'-'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + default: + return false; + } + + static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) + { + // Parse the hours for the offset + if ( + offsetData.Length < 2 + || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss.s+|-hh + + if (offsetData.Length == 2) + { + // Just hours offset specified + return true; + } + + // Ensure we have enough for ":mm" + return offsetData.Length == 5 + && offsetData[2] == JsonConstants.Colon + && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantAssignment + private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) + { + Debug.Assert(source.Length == 2); + + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9) + { + value = 0; + return false; + } + + value = (int)(digit1 * 10 + digit2); + return true; + } + + // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs + + /// + /// Overflow-safe DateTime factory. + /// + private static bool TryCreateDateTime( + DateTimeParseData parseData, + DateTimeKind kind, + out DateTime value + ) + { + if (parseData.Year == 0) + { + value = default; + return false; + } + + Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. + + if ((uint)parseData.Month - 1 >= 12) + { + value = default; + return false; + } + + uint dayMinusOne = (uint)parseData.Day - 1; + if ( + dayMinusOne >= 28 + && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) + ) + { + value = default; + return false; + } + + if ((uint)parseData.Hour > 23) + { + value = default; + return false; + } + + if ((uint)parseData.Minute > 59) + { + value = default; + return false; + } + + // This needs to allow leap seconds when appropriate. + // See https://github.com/dotnet/runtime/issues/30135. + if ((uint)parseData.Second > 59) + { + value = default; + return false; + } + + Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. + + ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) + ? DaysToMonth366 + : DaysToMonth365; + int yearMinusOne = parseData.Year - 1; + int totalDays = + yearMinusOne * 365 + + yearMinusOne / 4 + - yearMinusOne / 100 + + yearMinusOne / 400 + + days[parseData.Month - 1] + + parseData.Day + - 1; + long ticks = totalDays * TimeSpan.TicksPerDay; + int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; + ticks += totalSeconds * TimeSpan.TicksPerSecond; + ticks += parseData.Fraction; + value = new DateTime(ticks: ticks, kind: kind); + return true; + } + + private static ReadOnlySpan DaysToMonth365 => + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + private static ReadOnlySpan DaysToMonth366 => + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + } + + internal static class ThrowHelper + { + private const string ExceptionSourceValueToRethrowAsJsonException = + "System.Text.Json.Rethrowable"; + + [DoesNotReturn] + public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) + { + throw GetInvalidOperationException("string", tokenType); + } + + public static void ThrowFormatException(DataType dataType) + { + throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + + private static global::System.Exception GetInvalidOperationException( + string message, + JsonTokenType tokenType + ) + { + return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); + } + + private static InvalidOperationException GetInvalidOperationException(string message) + { + return new InvalidOperationException(message) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + } + + internal static class Utf8JsonReaderExtensions + { + internal static int ValueLength(this Utf8JsonReader reader) => + reader.HasValueSequence + ? checked((int)reader.ValueSequence.Length) + : reader.ValueSpan.Length; + } + + internal enum DataType + { + TimeOnly, + DateOnly, + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal static class SR + { + private static readonly bool s_usingResourceKeys = + AppContext.TryGetSwitch( + "System.Resources.UseSystemResourceKeys", + out bool usingResourceKeys + ) && usingResourceKeys; + + public static string UnsupportedFormat => Strings.UnsupportedFormat; + + public static string InvalidCast => Strings.InvalidCast; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1) + : string.Format(resourceFormat, p1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1, object? p2) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1, p2) + : string.Format(resourceFormat, p1, p2); + } + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( + "System.Resources.Tools.StronglyTypedResourceBuilder", + "17.0.0.0" + )] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings + { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode" + )] + internal Strings() { } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager( + "System.Text.Json.Resources.Strings", + typeof(Strings).Assembly + ); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Globalization.CultureInfo Culture + { + get { return resourceCulture; } + set { resourceCulture = value; } + } + + /// + /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. + /// + internal static string InvalidCast + { + get { return ResourceManager.GetString("InvalidCast", resourceCulture); } + } + + /// + /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. + /// + internal static string UnsupportedFormat + { + get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } + } + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs new file mode 100644 index 000000000000..d7dedc7f165b --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs @@ -0,0 +1,40 @@ +using global::System.Globalization; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +internal class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } + + public override DateTime ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateTime value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs new file mode 100644 index 000000000000..d14fc3bfa37e --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs @@ -0,0 +1,11 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// The request object to send without a request body. +/// +internal record EmptyRequest : BaseRequest +{ + internal override HttpContent? CreateContent() => null; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs new file mode 100644 index 000000000000..2dae8b535a18 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs @@ -0,0 +1,11 @@ +using global::System.Text; + +namespace SeedApi.Core; + +internal static class EncodingCache +{ + internal static readonly Encoding Utf8NoBom = new UTF8Encoding( + encoderShouldEmitUTF8Identifier: false, + throwOnInvalidBytes: true + ); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs new file mode 100644 index 000000000000..7338b20e748c --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs @@ -0,0 +1,55 @@ +using global::System.Diagnostics.CodeAnalysis; +using global::System.Runtime.Serialization; + +namespace SeedApi.Core; + +internal static class Extensions +{ + public static string Stringify(this Enum value) + { + var field = value.GetType().GetField(value.ToString()); + if (field is not null) + { + var attribute = (EnumMemberAttribute?) + global::System.Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); + return attribute?.Value ?? value.ToString(); + } + return value.ToString(); + } + + /// + /// Asserts that a condition is true, throwing an exception with the specified message if it is false. + /// + /// The condition to assert. + /// The exception message if the assertion fails. + /// Thrown when the condition is false. + internal static void Assert(this object value, bool condition, string message) + { + if (!condition) + { + throw new global::System.Exception(message); + } + } + + /// + /// Asserts that a value is not null, throwing an exception with the specified message if it is null. + /// + /// The type of the value to assert. + /// The value to assert is not null. + /// The exception message if the assertion fails. + /// The non-null value. + /// Thrown when the value is null. + internal static TValue Assert( + this object _unused, + [NotNull] TValue? value, + string message + ) + where TValue : class + { + if (value is null) + { + throw new global::System.Exception(message); + } + return value; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs new file mode 100644 index 000000000000..343c13716c24 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs @@ -0,0 +1,33 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// Encodes an object into a form URL-encoded content. +/// +public static class FormUrlEncoder +{ + /// + /// Encodes an object into a form URL-encoded content using Deep Object notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsDeepObject(object value) => + new(QueryStringConverter.ToDeepObject(value)); + + /// + /// Encodes an object into a form URL-encoded content using Exploded Form notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsExplodedForm(object value) => + new(QueryStringConverter.ToExplodedForm(value)); + + /// + /// Encodes an object into a form URL-encoded content using Form notation without exploding parameters. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsForm(object value) => + new(QueryStringConverter.ToForm(value)); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs new file mode 100644 index 000000000000..e908825e31f1 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs @@ -0,0 +1,52 @@ +namespace SeedApi.Core; + +internal sealed class HeaderValue +{ + private readonly Func> _resolver; + + public HeaderValue(string value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value); + } + + public HeaderValue(Func value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); + } + + public HeaderValue(Func> value) + { + _resolver = value; + } + + public HeaderValue(Func> value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); + } + + public static implicit operator HeaderValue(string value) => new(value); + + public static implicit operator HeaderValue(Func value) => new(value); + + public static implicit operator HeaderValue( + Func> value + ) => new(value); + + public static implicit operator HeaderValue( + Func> value + ) => new(value); + + public static HeaderValue FromString(string value) => new(value); + + public static HeaderValue FromFunc(Func value) => new(value); + + public static HeaderValue FromValueTaskFunc( + Func> value + ) => new(value); + + public static HeaderValue FromTaskFunc( + Func> value + ) => new(value); + + internal global::System.Threading.Tasks.ValueTask ResolveAsync() => _resolver(); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs new file mode 100644 index 000000000000..5b2bfc62f423 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs @@ -0,0 +1,28 @@ +namespace SeedApi.Core; + +/// +/// Represents the headers sent with the request. +/// +internal sealed class Headers : Dictionary +{ + internal Headers() { } + + /// + /// Initializes a new instance of the Headers class with the specified value. + /// + /// + internal Headers(Dictionary value) + { + foreach (var kvp in value) + { + this[kvp.Key] = kvp.Value; + } + } + + /// + /// Initializes a new instance of the Headers class with the specified value. + /// + /// + internal Headers(IEnumerable> value) + : base(value.ToDictionary(e => e.Key, e => e.Value)) { } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs new file mode 100644 index 000000000000..734b7ff41065 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs @@ -0,0 +1,197 @@ +namespace SeedApi.Core; + +/// +/// Fluent builder for constructing HTTP headers with support for merging from multiple sources. +/// Provides a clean API for building headers with proper precedence handling. +/// +internal static class HeadersBuilder +{ + /// + /// Fluent builder for constructing HTTP headers. + /// + public sealed class Builder + { + private readonly Dictionary _headers; + + /// + /// Initializes a new instance with default capacity. + /// Uses case-insensitive header name comparison. + /// + public Builder() + { + _headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Initializes a new instance with the specified initial capacity. + /// Uses case-insensitive header name comparison. + /// + public Builder(int capacity) + { + _headers = new Dictionary( + capacity, + StringComparer.OrdinalIgnoreCase + ); + } + + /// + /// Adds a header with the specified key and value. + /// If a header with the same key already exists, it will be overwritten. + /// Null values are ignored. + /// + /// The header name. + /// The header value. Null values are ignored. + /// This builder instance for method chaining. + public Builder Add(string key, string? value) + { + if (value is not null) + { + _headers[key] = (value); + } + return this; + } + + /// + /// Adds a header with the specified key and object value. + /// The value will be converted to string using ValueConvert for consistent serialization. + /// If a header with the same key already exists, it will be overwritten. + /// Null values are ignored. + /// + /// The header name. + /// The header value. Null values are ignored. + /// This builder instance for method chaining. + public Builder Add(string key, object? value) + { + if (value is null) + { + return this; + } + + // Use ValueConvert for consistent serialization across headers, query params, and path params + var stringValue = ValueConvert.ToString(value); + if (stringValue is not null) + { + _headers[key] = (stringValue); + } + return this; + } + + /// + /// Adds multiple headers from a Headers dictionary. + /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. + /// Overwrites any existing headers with the same key. + /// Null entries are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(Headers? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + _headers[header.Key] = header.Value; + } + + return this; + } + + /// + /// Adds multiple headers from a Headers dictionary, excluding the Authorization header. + /// This is useful for endpoints that don't require authentication, to avoid triggering + /// lazy auth token resolution. + /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. + /// Overwrites any existing headers with the same key. + /// Null entries are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder AddWithoutAuth(Headers? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + _headers[header.Key] = header.Value; + } + + return this; + } + + /// + /// Adds multiple headers from a key-value pair collection. + /// Overwrites any existing headers with the same key. + /// Null values are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(IEnumerable>? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + if (header.Value is not null) + { + _headers[header.Key] = (header.Value); + } + } + + return this; + } + + /// + /// Adds multiple headers from a dictionary. + /// Overwrites any existing headers with the same key. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(Dictionary? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + _headers[header.Key] = (header.Value); + } + + return this; + } + + /// + /// Asynchronously builds the final headers dictionary containing all merged headers. + /// Resolves all HeaderValue instances that may contain async operations. + /// Returns a case-insensitive dictionary. + /// + /// A task that represents the asynchronous operation, containing a case-insensitive dictionary of headers. + public async global::System.Threading.Tasks.Task> BuildAsync() + { + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in _headers) + { + var value = await kvp.Value.ResolveAsync().ConfigureAwait(false); + if (value is not null) + { + headers[kvp.Key] = value; + } + } + return headers; + } + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs new file mode 100644 index 000000000000..4295da097dc9 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs @@ -0,0 +1,20 @@ +#if !NET5_0_OR_GREATER +namespace SeedApi.Core; + +/// +/// Polyfill extension providing a ReadAsStringAsync(CancellationToken) overload +/// for target frameworks older than .NET 5, where only the parameterless +/// ReadAsStringAsync() is available. +/// +internal static class HttpContentExtensions +{ + internal static Task ReadAsStringAsync( + this HttpContent httpContent, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return httpContent.ReadAsStringAsync(); + } +} +#endif diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs new file mode 100644 index 000000000000..cedb977973d1 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs @@ -0,0 +1,8 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +internal static class HttpMethodExtensions +{ + public static readonly HttpMethod Patch = new("PATCH"); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs new file mode 100644 index 000000000000..1a5d48064427 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +public interface IIsRetryableContent +{ + public bool IsRetryable { get; } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs new file mode 100644 index 000000000000..4562c1723cf9 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs @@ -0,0 +1,83 @@ +namespace SeedApi.Core; + +internal interface IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional headers to be sent with the request. + /// Headers previously set with matching keys will be overwritten. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The max number of retries to attempt. + /// + public int? MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional query parameters sent with the request. + /// + public IEnumerable> AdditionalQueryParameters { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional body properties sent with the request. + /// This is only applied to JSON requests. + /// + public object? AdditionalBodyProperties { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs new file mode 100644 index 000000000000..93dcc6dd6bca --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs @@ -0,0 +1,15 @@ +namespace SeedApi.Core; + +[global::System.AttributeUsage( + global::System.AttributeTargets.Property | global::System.AttributeTargets.Field +)] +internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute +{ + internal JsonAccessType AccessType { get; init; } = accessType; +} + +internal enum JsonAccessType +{ + ReadOnly, + WriteOnly, +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs new file mode 100644 index 000000000000..2fa8cfb6ad8c --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs @@ -0,0 +1,275 @@ +using global::System.Reflection; +using global::System.Text.Encodings.Web; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using global::System.Text.Json.Serialization; +using global::System.Text.Json.Serialization.Metadata; + +namespace SeedApi.Core; + +internal static partial class JsonOptions +{ + internal static readonly JsonSerializerOptions JsonSerializerOptions; + internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; + + static JsonOptions() + { + var options = new JsonSerializerOptions + { + Converters = + { + new DateTimeSerializer(), +#if USE_PORTABLE_DATE_ONLY + new DateOnlyConverter(), +#endif + new OneOfSerializer(), + new OptionalJsonConverterFactory(), + }, +#if DEBUG + WriteIndented = true, +#endif + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + NullableOptionalModifier, + JsonAccessAndIgnoreModifier, + HandleExtensionDataFields, + }, + }, + }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; + + var relaxedOptions = new JsonSerializerOptions(options) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + JsonSerializerOptionsRelaxedEscaping = relaxedOptions; + } + + private static void NullableOptionalModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var property in typeInfo.Properties) + { + var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; + + if (propertyInfo is null) + continue; + + // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior + var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); + if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) + { + // ReadOnly means "never serialize", which completely overrides Optional/Nullable. + // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier + // will set ShouldSerialize = false anyway. + continue; + } + // Note: WriteOnly doesn't conflict with Optional/Nullable since it only + // affects deserialization (Set), not serialization (ShouldSerialize) + + var isOptionalType = + property.PropertyType.IsGenericType + && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); + + var hasOptionalAttribute = + propertyInfo.GetCustomAttribute() is not null; + var hasNullableAttribute = + propertyInfo.GetCustomAttribute() is not null; + + if (isOptionalType && hasOptionalAttribute) + { + var originalGetter = property.Get; + if (originalGetter is not null) + { + var capturedIsNullable = hasNullableAttribute; + + property.ShouldSerialize = (obj, value) => + { + var optionalValue = originalGetter(obj); + if (optionalValue is not IOptional optional) + return false; + + if (!optional.IsDefined) + return false; + + if (!capturedIsNullable) + { + var innerValue = optional.GetBoxedValue(); + if (innerValue is null) + return false; + } + + return true; + }; + } + } + else if (hasNullableAttribute) + { + // Force serialization of nullable properties even when null + property.ShouldSerialize = (obj, value) => true; + } + } + } + + private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var propertyInfo in typeInfo.Properties) + { + var jsonAccessAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonAccessAttribute is not null) + { + propertyInfo.IsRequired = false; + switch (jsonAccessAttribute.AccessType) + { + case JsonAccessType.ReadOnly: + propertyInfo.ShouldSerialize = (_, _) => false; + break; + case JsonAccessType.WriteOnly: + propertyInfo.Set = null; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + var jsonIgnoreAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonIgnoreAttribute is not null) + { + propertyInfo.IsRequired = false; + } + } + } + + private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) + { + if ( + typeInfo.Kind == JsonTypeInfoKind.Object + && typeInfo.Properties.All(prop => !prop.IsExtensionData) + ) + { + var extensionProp = typeInfo + .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) + .FirstOrDefault(prop => + prop.GetCustomAttribute() is not null + ); + + if (extensionProp is not null) + { + var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( + extensionProp.FieldType, + extensionProp.Name + ); + jsonPropertyInfo.Get = extensionProp.GetValue; + jsonPropertyInfo.Set = extensionProp.SetValue; + jsonPropertyInfo.IsExtensionData = true; + typeInfo.Properties.Add(jsonPropertyInfo); + } + } + } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); +} + +internal static class JsonUtils +{ + internal static string Serialize(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + + internal static string Serialize(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); + + internal static string SerializeRelaxedEscaping(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static JsonElement SerializeToElement(T obj) => + JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonElement SerializeToElement(object obj, global::System.Type type) => + JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); + + internal static JsonDocument SerializeToDocument(T obj) => + JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonNode? SerializeToNode(T obj) => + JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); + + internal static byte[] SerializeToUtf8Bytes(T obj) => + JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); + + internal static string SerializeWithAdditionalProperties( + T obj, + object? additionalProperties = null + ) + { + if (additionalProperties is null) + { + return Serialize(obj); + } + var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); + if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) + { + throw new InvalidOperationException( + "The additional properties must serialize to a JSON object." + ); + } + var jsonNode = SerializeToNode(obj); + if (jsonNode is not JsonObject jsonObject) + { + throw new InvalidOperationException( + "The serialized object must be a JSON object to add properties." + ); + } + MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); + return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); + } + + private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) + { + foreach (var property in overrideObject) + { + if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) + { + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + continue; + } + if ( + existingValue is JsonObject nestedBaseObject + && property.Value is JsonObject nestedOverrideObject + ) + { + // If both values are objects, recursively merge them. + MergeJsonObjects(nestedBaseObject, nestedOverrideObject); + continue; + } + // Otherwise, the overrideObject takes precedence. + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + } + } + + internal static T Deserialize(string json) => + JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs new file mode 100644 index 000000000000..0a85891304f1 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs @@ -0,0 +1,36 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// The request object to be sent for JSON APIs. +/// +internal record JsonRequest : BaseRequest +{ + internal object? Body { get; init; } + + internal override HttpContent? CreateContent() + { + if (Body is null && Options?.AdditionalBodyProperties is null) + { + return null; + } + + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + ContentType, + Utf8NoBom, + "application/json" + ); + var content = new StringContent( + JsonUtils.SerializeWithAdditionalProperties(Body, Options?.AdditionalBodyProperties), + encoding, + mediaType + ); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + return content; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs new file mode 100644 index 000000000000..bf72225d461b --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs @@ -0,0 +1,294 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; + +namespace SeedApi.Core; + +/// +/// The request object to be sent for multipart form data. +/// +internal record MultipartFormRequest : BaseRequest +{ + private readonly List> _partAdders = []; + + internal void AddJsonPart(string name, object? value) => AddJsonPart(name, value, null); + + internal void AddJsonPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + contentType, + Utf8NoBom, + "application/json" + ); + var content = new StringContent(JsonUtils.Serialize(value), encoding, mediaType); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + form.Add(content, name); + }); + } + + internal void AddJsonParts(string name, IEnumerable? value) => + AddJsonParts(name, value, null); + + internal void AddJsonParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddJsonPart(name, item, contentType); + } + } + + internal void AddJsonParts(string name, IEnumerable? value) => + AddJsonParts(name, value, null); + + internal void AddJsonParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddJsonPart(name, item, contentType); + } + } + + internal void AddStringPart(string name, object? value) => AddStringPart(name, value, null); + + internal void AddStringPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + AddStringPart(name, ValueConvert.ToString(value), contentType); + } + + internal void AddStringPart(string name, string? value) => AddStringPart(name, value, null); + + internal void AddStringPart(string name, string? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + contentType, + Utf8NoBom, + "text/plain" + ); + var content = new StringContent(value, encoding, mediaType); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + form.Add(content, name); + }); + } + + internal void AddStringParts(string name, IEnumerable? value) => + AddStringParts(name, value, null); + + internal void AddStringParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + AddStringPart(name, ValueConvert.ToString(value), contentType); + } + + internal void AddStringParts(string name, IEnumerable? value) => + AddStringParts(name, value, null); + + internal void AddStringParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddStringPart(name, item, contentType); + } + } + + internal void AddStreamPart(string name, Stream? stream, string? fileName) => + AddStreamPart(name, stream, fileName, null); + + internal void AddStreamPart(string name, Stream? stream, string? fileName, string? contentType) + { + if (stream is null) + { + return; + } + + _partAdders.Add(form => + { + var content = new StreamContent(stream) + { + Headers = + { + ContentType = MediaTypeHeaderValue.Parse( + contentType ?? "application/octet-stream" + ), + }, + }; + + if (fileName is not null) + { + form.Add(content, name, fileName); + } + else + { + form.Add(content, name); + } + }); + } + + internal void AddFileParameterPart(string name, Stream? stream) => + AddStreamPart(name, stream, null, null); + + internal void AddFileParameterPart(string name, FileParameter? file) => + AddFileParameterPart(name, file, null); + + internal void AddFileParameterPart( + string name, + FileParameter? file, + string? fallbackContentType + ) => + AddStreamPart(name, file?.Stream, file?.FileName, file?.ContentType ?? fallbackContentType); + + internal void AddFileParameterParts(string name, IEnumerable? files) => + AddFileParameterParts(name, files, null); + + internal void AddFileParameterParts( + string name, + IEnumerable? files, + string? fallbackContentType + ) + { + if (files is null) + { + return; + } + + foreach (var file in files) + { + AddFileParameterPart(name, file, fallbackContentType); + } + } + + internal void AddFormEncodedPart(string name, object? value) => + AddFormEncodedPart(name, value, null); + + internal void AddFormEncodedPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var content = FormUrlEncoder.EncodeAsForm(value); + if (!string.IsNullOrEmpty(contentType)) + { + content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + + form.Add(content, name); + }); + } + + internal void AddFormEncodedParts(string name, IEnumerable? value) => + AddFormEncodedParts(name, value, null); + + internal void AddFormEncodedParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddFormEncodedPart(name, item, contentType); + } + } + + internal void AddExplodedFormEncodedPart(string name, object? value) => + AddExplodedFormEncodedPart(name, value, null); + + internal void AddExplodedFormEncodedPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var content = FormUrlEncoder.EncodeAsExplodedForm(value); + if (!string.IsNullOrEmpty(contentType)) + { + content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + + form.Add(content, name); + }); + } + + internal void AddExplodedFormEncodedParts(string name, IEnumerable? value) => + AddExplodedFormEncodedParts(name, value, null); + + internal void AddExplodedFormEncodedParts( + string name, + IEnumerable? value, + string? contentType + ) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddExplodedFormEncodedPart(name, item, contentType); + } + } + + internal override HttpContent CreateContent() + { + var form = new MultipartFormDataContent(); + foreach (var adder in _partAdders) + { + adder(form); + } + + return form; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs new file mode 100644 index 000000000000..a1d30328bf9a --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs @@ -0,0 +1,18 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as nullable in the OpenAPI specification. +/// When applied to Optional properties, this indicates that null values should be +/// written to JSON when the optional is defined with null. +/// +/// +/// For regular (required) properties: +/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) +/// - With [Nullable]: null values are written to JSON +/// +/// For Optional properties (also marked with [Optional]): +/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) +/// - With [Nullable]: Optional.Of(null) → write null to JSON +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs new file mode 100644 index 000000000000..6eeb68fcba46 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs @@ -0,0 +1,145 @@ +using global::System.Reflection; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using OneOf; + +namespace SeedApi.Core; + +internal class OneOfSerializer : JsonConverter +{ + public override IOneOf? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (IOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + public override IOneOf ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = reader.GetString(); + if (stringValue == null) + throw new JsonException("Cannot deserialize null property name into OneOf type"); + + // Try to deserialize the string value into one of the supported types + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + // For primitive types, try direct conversion + if (type == typeof(string)) + { + return (IOneOf)cast.Invoke(null, [stringValue])!; + } + + // For other types, try to deserialize from JSON string + var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); + if (result != null) + { + return (IOneOf)cast.Invoke(null, [result])!; + } + } + catch { } + } + + // If no type-specific deserialization worked, default to string if available + var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); + if (stringType != default) + { + return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; + } + + throw new JsonException( + $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" + ); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + IOneOf value, + JsonSerializerOptions options + ) + { + // Serialize the underlying value to a string suitable for use as a dictionary key + var stringValue = value.Value?.ToString() ?? "null"; + writer.WritePropertyName(stringValue); + } + + private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( + global::System.Type typeToConvert + ) + { + var type = typeToConvert; + if (Nullable.GetUnderlyingType(type) is { } underlyingType) + { + type = underlyingType; + } + + var casts = type.GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + while (type is not null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + var genericArguments = type.GetGenericArguments(); + if (genericArguments.Length == 1) + { + return [(genericArguments[0], casts[0])]; + } + + // if object type is present, make sure it is last + var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); + if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) + { + genericArguments = genericArguments + .OrderBy(t => t == typeof(object) ? 1 : 0) + .ToArray(); + } + + return genericArguments + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(global::System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs new file mode 100644 index 000000000000..d174943cb2cf --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs @@ -0,0 +1,474 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Non-generic interface for Optional types to enable reflection-free checks. +/// +public interface IOptional +{ + /// + /// Returns true if the value is defined (set), even if the value is null. + /// + bool IsDefined { get; } + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + object? GetBoxedValue(); +} + +/// +/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). +/// Use this for HTTP PATCH requests where you need to distinguish between: +/// +/// Undefined: Don't send this field (leave it unchanged on the server) +/// Defined with null: Send null (clear the field on the server) +/// Defined with value: Send the value (update the field on the server) +/// +/// +/// The type of the value. Use nullable types (T?) for fields that can be null. +/// +/// For nullable string fields, use Optional<string?>: +/// +/// public class UpdateUserRequest +/// { +/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; +/// } +/// +/// var request = new UpdateUserRequest +/// { +/// Name = "John" // Will send: { "name": "John" } +/// }; +/// +/// var request2 = new UpdateUserRequest +/// { +/// Name = Optional<string?>.Of(null) // Will send: { "name": null } +/// }; +/// +/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) +/// +/// +public readonly struct Optional : IOptional, IEquatable> +{ + private readonly T _value; + private readonly bool _isDefined; + + private Optional(T value, bool isDefined) + { + _value = value; + _isDefined = isDefined; + } + + /// + /// Creates an undefined value - the field will not be included in the HTTP request. + /// Use this as the default value for optional fields. + /// + /// + /// + /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; + /// + /// + public static Optional Undefined => new(default!, false); + + /// + /// Creates a defined value - the field will be included in the HTTP request. + /// The value can be null if T is a nullable type. + /// + /// The value to set. Can be null if T is nullable (e.g., string?, int?). + /// + /// + /// // Set to a value + /// request.Name = Optional<string?>.Of("John"); + /// + /// // Set to null (clears the field) + /// request.Email = Optional<string?>.Of(null); + /// + /// // Or use implicit conversion + /// request.Name = "John"; // Same as Of("John") + /// request.Email = null; // Same as Of(null) + /// + /// + public static Optional Of(T value) => new(value, true); + + /// + /// Returns true if the field is defined (set), even if the value is null. + /// Use this to determine if the field should be included in the HTTP request. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// requestBody["name"] = request.Name.Value; // Include in request (can be null) + /// } + /// + /// + public bool IsDefined => _isDefined; + + /// + /// Returns true if the field is undefined (not set). + /// Use this to check if the field should be excluded from the HTTP request. + /// + /// + /// + /// if (request.Email.IsUndefined) + /// { + /// // Don't include email in the request - leave it unchanged + /// } + /// + /// + public bool IsUndefined => !_isDefined; + + /// + /// Gets the value. The value may be null if T is a nullable type. + /// + /// Thrown if the value is undefined. + /// + /// Always check before accessing Value, or use instead. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> + /// } + /// + /// // Or check for null explicitly + /// if (request.Email.IsDefined && request.Email.Value is null) + /// { + /// // Email is explicitly set to null (clear it) + /// } + /// + /// + public T Value + { + get + { + if (!_isDefined) + throw new InvalidOperationException("Optional value is undefined"); + return _value; + } + } + + /// + /// Gets the value if defined, otherwise returns the specified default value. + /// Note: If the value is defined as null, this returns null (not the default). + /// + /// The value to return if undefined. + /// The actual value if defined (can be null), otherwise the default value. + /// + /// + /// string name = request.Name.GetValueOrDefault("Anonymous"); + /// // If Name is undefined: returns "Anonymous" + /// // If Name is Of(null): returns null + /// // If Name is Of("John"): returns "John" + /// + /// + public T GetValueOrDefault(T defaultValue = default!) + { + return _isDefined ? _value : defaultValue; + } + + /// + /// Tries to get the value. Returns true if the value is defined (even if null). + /// + /// + /// When this method returns, contains the value if defined, or default(T) if undefined. + /// The value may be null if T is nullable. + /// + /// True if the value is defined; otherwise, false. + /// + /// + /// if (request.Email.TryGetValue(out var email)) + /// { + /// requestBody["email"] = email; // email can be null + /// } + /// else + /// { + /// // Email is undefined - don't include in request + /// } + /// + /// + public bool TryGetValue(out T value) + { + if (_isDefined) + { + value = _value; + return true; + } + value = default!; + return false; + } + + /// + /// Implicitly converts a value to Optional<T>.Of(value). + /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). + /// + /// The value to convert (can be null if T is nullable). + public static implicit operator Optional(T value) => Of(value); + + /// + /// Returns a string representation of this Optional value. + /// + /// "Undefined" if not set, or "Defined(value)" if set. + public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + public object? GetBoxedValue() + { + if (!_isDefined) + return null; + return _value; + } + + /// + public bool Equals(Optional other) => + _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); + + /// + public override bool Equals(object? obj) => obj is Optional other && Equals(other); + + /// + public override int GetHashCode() + { + if (!_isDefined) + return 0; + unchecked + { + int hash = 17; + hash = hash * 31 + 1; // _isDefined = true + hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); + return hash; + } + } + + /// + /// Determines whether two Optional values are equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are equal; otherwise, false. + public static bool operator ==(Optional left, Optional right) => left.Equals(right); + + /// + /// Determines whether two Optional values are not equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are not equal; otherwise, false. + public static bool operator !=(Optional left, Optional right) => !left.Equals(right); +} + +/// +/// Extension methods for Optional to simplify common operations. +/// +public static class OptionalExtensions +{ + /// + /// Adds the value to a dictionary if the optional is defined (even if the value is null). + /// This is useful for building JSON request payloads where null values should be included. + /// + /// The type of the optional value. + /// The optional value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined + /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined + /// + /// + public static void AddTo( + this Optional optional, + Dictionary dictionary, + string key + ) + { + if (optional.IsDefined) + { + dictionary[key] = optional.Value; + } + } + + /// + /// Executes an action if the optional is defined. + /// + /// The type of the optional value. + /// The optional value. + /// The action to execute with the value. + /// + /// + /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); + /// + /// + public static void IfDefined(this Optional optional, Action action) + { + if (optional.IsDefined) + { + action(optional.Value); + } + } + + /// + /// Maps the value to a new type if the optional is defined, otherwise returns undefined. + /// + /// The type of the original value. + /// The type to map to. + /// The optional value to map. + /// The mapping function. + /// An optional containing the mapped value if defined, otherwise undefined. + /// + /// + /// Optional<string?> name = Optional<string?>.Of("John"); + /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) + /// + /// + public static Optional Map( + this Optional optional, + Func mapper + ) + { + return optional.IsDefined + ? Optional.Of(mapper(optional.Value)) + : Optional.Undefined; + } + + /// + /// Adds a nullable value to a dictionary only if it is not null. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The type of the value (must be a reference type or Nullable). + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : class + { + if (value is not null) + { + dictionary[key] = value; + } + } + + /// + /// Adds a nullable value type to a dictionary only if it has a value. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The underlying value type. + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : struct + { + if (value.HasValue) + { + dictionary[key] = value.Value; + } + } +} + +/// +/// JSON converter factory for Optional that handles undefined vs null correctly. +/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. +/// +public class OptionalJsonConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(global::System.Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + return false; + + return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); + } + + public override JsonConverter? CreateConverter( + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var valueType = typeToConvert.GetGenericArguments()[0]; + var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); + return (JsonConverter?)global::System.Activator.CreateInstance(converterType); + } +} + +/// +/// JSON converter for Optional that unwraps the value during serialization. +/// The actual property skipping is handled by the OptionalTypeInfoResolver. +/// +public class OptionalJsonConverter : JsonConverter> +{ + public override Optional Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return Optional.Of(default!); + } + + var value = JsonSerializer.Deserialize(ref reader, options); + return Optional.Of(value!); + } + + public override void Write( + Utf8JsonWriter writer, + Optional value, + JsonSerializerOptions options + ) + { + // This will be called by the serializer + // We need to unwrap and serialize the inner value + // The TypeInfoResolver will handle skipping undefined values + + if (value.IsUndefined) + { + // This shouldn't be called for undefined values due to ShouldSerialize + // But if it is, write null and let the resolver filter it + writer.WriteNullValue(); + return; + } + + // Get the inner value + var innerValue = value.Value; + + // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) + if (innerValue is null) + { + writer.WriteNullValue(); + return; + } + + // Serialize the unwrapped value + JsonSerializer.Serialize(writer, innerValue, options); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs new file mode 100644 index 000000000000..4c4c4073a0ae --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs @@ -0,0 +1,17 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as optional in the OpenAPI specification. +/// Optional properties use the Optional type and can be undefined (not present in JSON). +/// +/// +/// Properties marked with [Optional] should use the Optional type: +/// - Undefined: Optional.Undefined → omitted from JSON +/// - Defined: Optional.Of(value) → written to JSON +/// +/// Combine with [Nullable] to allow null values: +/// - [Optional, Nullable] Optional → can be undefined, null, or a value +/// - [Optional] Optional → can be undefined or a value (null is invalid) +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs new file mode 100644 index 000000000000..8b43322350bd --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs @@ -0,0 +1,353 @@ +using global::System.Collections; +using global::System.Collections.ObjectModel; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using SeedApi.Core; + +namespace SeedApi; + +public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties +{ + internal ReadOnlyAdditionalProperties() { } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record ReadOnlyAdditionalProperties : IReadOnlyDictionary +{ + private readonly Dictionary _extensionData = new(); + private readonly Dictionary _convertedCache = new(); + + internal ReadOnlyAdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + if (kvp.Value is JsonElement element) + { + _extensionData.Add(kvp.Key, element); + } + else + { + _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); + } + + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(JsonElement value) + { + if (typeof(T) == typeof(JsonElement)) + { + return (T)(object)value; + } + + return value.Deserialize(JsonOptions.JsonSerializerOptions)!; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var cached)) + { + return cached; + } + + var value = ConvertToT(_extensionData[key]); + _convertedCache[key] = value; + return value; + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _extensionData.Count; + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var element)) + { + value = ConvertToT(element); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public T this[string key] => GetCached(key); + + public IEnumerable Keys => _extensionData.Keys; + + public IEnumerable Values => Keys.Select(GetCached); +} + +public record AdditionalProperties : AdditionalProperties +{ + public AdditionalProperties() { } + + public AdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record AdditionalProperties : IDictionary +{ + private readonly Dictionary _extensionData; + private readonly Dictionary _convertedCache; + + public AdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + public AdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + _extensionData[kvp.Key] = kvp.Value; + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(object? extensionDataValue) + { + return extensionDataValue switch + { + T value => value, + JsonElement jsonElement => jsonElement.Deserialize( + JsonOptions.JsonSerializerOptions + )!, + JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, + _ => JsonUtils + .SerializeToElement(extensionDataValue) + .Deserialize(JsonOptions.JsonSerializerOptions)!, + }; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + internal void CopyToExtensionData(IDictionary extensionData) + { + extensionData.Clear(); + foreach (var kvp in _extensionData) + { + extensionData[kvp.Key] = kvp.Value; + } + } + + public JsonObject ToJsonObject() => + ( + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ) + ).AsObject(); + + public JsonNode ToJsonNode() => + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ); + + public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); + + public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); + + public IReadOnlyDictionary ToJsonElementDictionary() + { + return new ReadOnlyDictionary( + _extensionData.ToDictionary( + kvp => kvp.Key, + kvp => + { + if (kvp.Value is JsonElement jsonElement) + { + return jsonElement; + } + + return JsonUtils.SerializeToElement(kvp.Value); + } + ) + ); + } + + public ICollection Keys => _extensionData.Keys; + + public ICollection Values + { + get + { + var values = new T[_extensionData.Count]; + var i = 0; + foreach (var key in Keys) + { + values[i++] = GetCached(key); + } + + return values; + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var value)) + { + return value; + } + + value = ConvertToT(_extensionData[key]); + _convertedCache.Add(key, value); + return value; + } + + private void SetCached(string key, T value) + { + _extensionData[key] = value; + _convertedCache[key] = value; + } + + private void AddCached(string key, T value) + { + _extensionData.Add(key, value); + _convertedCache.Add(key, value); + } + + private bool RemoveCached(string key) + { + var isRemoved = _extensionData.Remove(key); + _convertedCache.Remove(key); + return isRemoved; + } + + public int Count => _extensionData.Count; + public bool IsReadOnly => false; + + public T this[string key] + { + get => GetCached(key); + set => SetCached(key, value); + } + + public void Add(string key, T value) => AddCached(key, value); + + public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); + + public bool Remove(string key) => RemoveCached(key); + + public bool Remove(KeyValuePair item) => RemoveCached(item.Key); + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool Contains(KeyValuePair item) + { + return _extensionData.ContainsKey(item.Key) + && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); + } + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var extensionDataValue)) + { + value = ConvertToT(extensionDataValue); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public void Clear() + { + _extensionData.Clear(); + _convertedCache.Clear(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (array.Length - arrayIndex < _extensionData.Count) + { + throw new ArgumentException( + "The array does not have enough space to copy the elements." + ); + } + + foreach (var kvp in _extensionData) + { + array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); + } + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs new file mode 100644 index 000000000000..837716a987f2 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs @@ -0,0 +1,84 @@ +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public partial class ClientOptions +{ + /// + /// The http headers sent with the request. + /// + internal Headers Headers { get; init; } = new(); + + /// + /// The Base URL for the API. + /// + public string BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = SeedApiEnvironment.Default; + + /// + /// The http client used to make requests. + /// + public HttpClient HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = new HttpClient(); + + /// + /// Additional headers to be sent with HTTP requests. + /// Headers with matching keys will be overwritten by headers set on the request. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = []; + + /// + /// The max number of retries to attempt. + /// + public int MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = 2; + + /// + /// The timeout for the request. + /// + public TimeSpan Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = TimeSpan.FromSeconds(30); + + /// + /// Clones this and returns a new instance + /// + internal ClientOptions Clone() + { + return new ClientOptions + { + BaseUrl = BaseUrl, + HttpClient = HttpClient, + MaxRetries = MaxRetries, + Timeout = Timeout, + Headers = new Headers(new Dictionary(Headers)), + AdditionalHeaders = AdditionalHeaders, + }; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs new file mode 100644 index 000000000000..f33d49028884 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs @@ -0,0 +1,63 @@ +namespace SeedApi; + +/// +/// File parameter for uploading files. +/// +public record FileParameter : IDisposable +#if NET6_0_OR_GREATER + , IAsyncDisposable +#endif +{ + private bool _disposed; + + /// + /// The name of the file to be uploaded. + /// + public string? FileName { get; set; } + + /// + /// The content type of the file to be uploaded. + /// + public string? ContentType { get; set; } + + /// + /// The content of the file to be uploaded. + /// + public required Stream Stream { get; set; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + if (disposing) + { + Stream.Dispose(); + } + + _disposed = true; + } + +#if NET6_0_OR_GREATER + /// + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + await Stream.DisposeAsync().ConfigureAwait(false); + _disposed = true; + } + + GC.SuppressFinalize(this); + } +#endif + + public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs new file mode 100644 index 000000000000..f711c7f774d2 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs @@ -0,0 +1,24 @@ +using global::System.Net; + +namespace SeedApi; + +/// +/// Contains HTTP response metadata including status code, URL, and headers. +/// +public record RawResponse +{ + /// + /// The HTTP status code of the response. + /// + public required HttpStatusCode StatusCode { get; init; } + + /// + /// The request URL that generated this response. + /// + public required Uri Url { get; init; } + + /// + /// The HTTP response headers. + /// + public required Core.ResponseHeaders Headers { get; init; } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs new file mode 100644 index 000000000000..ea7db76b032f --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs @@ -0,0 +1,86 @@ +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public partial class RequestOptions : IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional headers to be sent with the request. + /// Headers previously set with matching keys will be overwritten. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = []; + + /// + /// The max number of retries to attempt. + /// + public int? MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional query parameters sent with the request. + /// + public IEnumerable> AdditionalQueryParameters { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = Enumerable.Empty>(); + + /// + /// Additional body properties sent with the request. + /// This is only applied to JSON requests. + /// + public object? AdditionalBodyProperties { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs new file mode 100644 index 000000000000..94afb43fa0a3 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs @@ -0,0 +1,22 @@ +namespace SeedApi; + +/// +/// This exception type will be thrown for any non-2XX API responses. +/// +public class SeedApiApiException( + string message, + int statusCode, + object body, + Exception? innerException = null +) : SeedApiException(message, innerException) +{ + /// + /// The error code of the response that triggered the exception. + /// + public int StatusCode => statusCode; + + /// + /// The body of the response that triggered the exception. + /// + public object Body => body; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs new file mode 100644 index 000000000000..d30f17ce0829 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +[Serializable] +public class SeedApiEnvironment +{ + public const string Default = "https://api.example.com"; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs new file mode 100644 index 000000000000..90e03e71e695 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +/// +/// Base exception class for all exceptions thrown by the SDK. +/// +public class SeedApiException(string message, Exception? innerException = null) + : Exception(message, innerException); diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs new file mode 100644 index 000000000000..3d210b7e0b4c --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +[Serializable] +internal class Version +{ + public const string Current = "0.0.1"; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs new file mode 100644 index 000000000000..4c173fb2c115 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs @@ -0,0 +1,18 @@ +namespace SeedApi; + +/// +/// Wraps a parsed response value with its raw HTTP response metadata. +/// +/// The type of the parsed response data. +public readonly struct WithRawResponse +{ + /// + /// The parsed response data. + /// + public required T Data { get; init; } + + /// + /// The raw HTTP response metadata. + /// + public required RawResponse RawResponse { get; init; } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs new file mode 100644 index 000000000000..7c939859cb9f --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs @@ -0,0 +1,144 @@ +using global::System.Runtime.CompilerServices; + +namespace SeedApi; + +/// +/// A task-like type that wraps Task<WithRawResponse<T>> and provides dual-mode awaiting: +/// - Direct await yields just T (zero-allocation path for common case) +/// - .WithRawResponse() yields WithRawResponse<T> (when raw response metadata is needed) +/// +/// The type of the parsed response data. +public readonly struct WithRawResponseTask +{ + private readonly global::System.Threading.Tasks.Task> _task; + + /// + /// Creates a new WithRawResponseTask wrapping the given task. + /// + public WithRawResponseTask(global::System.Threading.Tasks.Task> task) + { + _task = task; + } + + /// + /// Returns the underlying task that yields both the data and raw response metadata. + /// + public global::System.Threading.Tasks.Task> WithRawResponse() => _task; + + /// + /// Gets the custom awaiter that unwraps to just T when awaited. + /// + public Awaiter GetAwaiter() => new(_task.GetAwaiter()); + + /// + /// Configures the awaiter to continue on the captured context or not. + /// + public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => + new(_task.ConfigureAwait(continueOnCapturedContext)); + + /// + /// Implicitly converts WithRawResponseTask<T> to global::System.Threading.Tasks.Task<T> for backward compatibility. + /// The resulting task will yield just the data when awaited. + /// + public static implicit operator global::System.Threading.Tasks.Task( + WithRawResponseTask task + ) + { + return task._task.ContinueWith( + t => t.Result.Data, + TaskContinuationOptions.ExecuteSynchronously + ); + } + + /// + /// Custom awaiter that unwraps WithRawResponse<T> to just T. + /// + public readonly struct Awaiter : ICriticalNotifyCompletion + { + private readonly TaskAwaiter> _awaiter; + + internal Awaiter(TaskAwaiter> awaiter) + { + _awaiter = awaiter; + } + + /// + /// Gets whether the underlying task has completed. + /// + public bool IsCompleted => _awaiter.IsCompleted; + + /// + /// Gets the result, unwrapping to just the data. + /// + public T GetResult() => _awaiter.GetResult().Data; + + /// + /// Schedules the continuation action. + /// + public void OnCompleted(global::System.Action continuation) => + _awaiter.OnCompleted(continuation); + + /// + /// Schedules the continuation action without capturing the execution context. + /// + public void UnsafeOnCompleted(global::System.Action continuation) => + _awaiter.UnsafeOnCompleted(continuation); + } + + /// + /// Awaitable type returned by ConfigureAwait that unwraps to just T. + /// + public readonly struct ConfiguredTaskAwaitable + { + private readonly ConfiguredTaskAwaitable> _configuredTask; + + internal ConfiguredTaskAwaitable(ConfiguredTaskAwaitable> configuredTask) + { + _configuredTask = configuredTask; + } + + /// + /// Gets the configured awaiter that unwraps to just T. + /// + public ConfiguredAwaiter GetAwaiter() => new(_configuredTask.GetAwaiter()); + + /// + /// Custom configured awaiter that unwraps WithRawResponse<T> to just T. + /// + public readonly struct ConfiguredAwaiter : ICriticalNotifyCompletion + { + private readonly ConfiguredTaskAwaitable< + WithRawResponse + >.ConfiguredTaskAwaiter _awaiter; + + internal ConfiguredAwaiter( + ConfiguredTaskAwaitable>.ConfiguredTaskAwaiter awaiter + ) + { + _awaiter = awaiter; + } + + /// + /// Gets whether the underlying task has completed. + /// + public bool IsCompleted => _awaiter.IsCompleted; + + /// + /// Gets the result, unwrapping to just the data. + /// + public T GetResult() => _awaiter.GetResult().Data; + + /// + /// Schedules the continuation action. + /// + public void OnCompleted(global::System.Action continuation) => + _awaiter.OnCompleted(continuation); + + /// + /// Schedules the continuation action without capturing the execution context. + /// + public void UnsafeOnCompleted(global::System.Action continuation) => + _awaiter.UnsafeOnCompleted(continuation); + } + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs new file mode 100644 index 000000000000..9498488a311b --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs @@ -0,0 +1,654 @@ +using global::System.Buffers; +using global::System.Runtime.CompilerServices; +#if !NET6_0_OR_GREATER +using global::System.Text; +#endif + +namespace SeedApi.Core; + +/// +/// High-performance query string builder with RFC 3986 compliant percent-encoding. +/// Uses span-based APIs on .NET 6+ and StringBuilder fallback for older targets. +/// +/// RFC 3986 defines the following relevant productions: +/// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +/// query = *( pchar / "/" / "?" ) +/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +/// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +/// +/// Three encoding contexts are distinguished: +/// Path segment (pchar): unreserved + sub-delims + ":" + "@" +/// Query key: query chars minus "&", "=", "+", "#" +/// Query value: query chars minus "&", "+", "#" +/// +internal static class QueryStringBuilder +{ + // ────────────────────────────────────────────────────────────────────── + // RFC 3986 character sets + // + // Query key safe: unreserved + (sub-delims \ {& = +}) + : @ / ? + // Query value safe: unreserved + (sub-delims \ {& +}) + : @ / ? + // Path segment safe: unreserved + sub-delims + : @ + // ────────────────────────────────────────────────────────────────────── + +#if NET8_0_OR_GREATER + private static readonly SearchValues SafeQueryKeyChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?" + ); + + private static readonly SearchValues SafeQueryValueChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?" + ); + + private static readonly SearchValues SafePathChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@" + ); +#else + private const string SafeQueryKeyChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?"; + + private const string SafeQueryValueChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?"; + + private const string SafePathChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@"; +#endif + +#if NET7_0_OR_GREATER + private static ReadOnlySpan UpperHexChars => "0123456789ABCDEF"u8; +#else + private static readonly byte[] UpperHexChars = + { + (byte)'0', + (byte)'1', + (byte)'2', + (byte)'3', + (byte)'4', + (byte)'5', + (byte)'6', + (byte)'7', + (byte)'8', + (byte)'9', + (byte)'A', + (byte)'B', + (byte)'C', + (byte)'D', + (byte)'E', + (byte)'F', + }; +#endif + + private enum EncodingContext + { + QueryKey, + QueryValue, + Path, + } + + /// + /// Percent-encodes a path segment value per RFC 3986 section 3.3 (pchar). + /// Allowed unencoded: unreserved / sub-delims / ":" / "@" + /// + public static string EncodePathSegment(string value) + { + if (string.IsNullOrEmpty(value)) + return value; + +#if NET6_0_OR_GREATER + if (!NeedsEncoding(value.AsSpan(), EncodingContext.Path)) + return value; + + var buffer = ArrayPool.Shared.Rent(value.Length * 3); + try + { + var written = EncodeSlow(value.AsSpan(), buffer.AsSpan(), EncodingContext.Path); + return new string(buffer.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } +#else + var sb = new StringBuilder(value.Length); + AppendEncoded(sb, value, EncodingContext.Path); + return sb.ToString(); +#endif + } + + /// + /// Builds a query string from the provided parameters. + /// +#if NET6_0_OR_GREATER + public static string Build(ReadOnlySpan> parameters) + { + if (parameters.IsEmpty) + return string.Empty; + + var estimatedLength = EstimateLength(parameters); + if (estimatedLength == 0) + return string.Empty; + + var bufferSize = Math.Min(estimatedLength * 3, 8192); + var buffer = ArrayPool.Shared.Rent(bufferSize); + + try + { + var written = BuildCore(parameters, buffer); + return new string(buffer.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + private static int EstimateLength(ReadOnlySpan> parameters) + { + var estimatedLength = 0; + foreach (var kvp in parameters) + { + estimatedLength += kvp.Key.Length + kvp.Value.Length + 2; + } + return estimatedLength; + } +#endif + + /// + /// Builds a query string from the provided parameters. + /// + public static string Build(IEnumerable> parameters) + { +#if NET6_0_OR_GREATER + // Try to get span access for collections that support it + if (parameters is ICollection> collection) + { + if (collection.Count == 0) + return string.Empty; + + var array = ArrayPool>.Shared.Rent(collection.Count); + try + { + collection.CopyTo(array, 0); + return Build(array.AsSpan(0, collection.Count)); + } + finally + { + ArrayPool>.Shared.Return(array); + } + } + + // Fallback for non-collection enumerables + using var enumerator = parameters.GetEnumerator(); + if (!enumerator.MoveNext()) + return string.Empty; + + var buffer = ArrayPool.Shared.Rent(4096); + try + { + var position = 0; + var first = true; + + do + { + var kvp = enumerator.Current; + + // Ensure capacity (worst case: 3x for encoding + separators) + var required = (kvp.Key.Length + kvp.Value.Length + 2) * 3; + if (position + required > buffer.Length) + { + var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); + buffer.AsSpan(0, position).CopyTo(newBuffer); + ArrayPool.Shared.Return(buffer); + buffer = newBuffer; + } + + buffer[position++] = first ? '?' : '&'; + first = false; + + position += EncodeWithCharSet( + kvp.Key.AsSpan(), + buffer.AsSpan(position), + EncodingContext.QueryKey + ); + buffer[position++] = '='; + position += EncodeWithCharSet( + kvp.Value.AsSpan(), + buffer.AsSpan(position), + EncodingContext.QueryValue + ); + } while (enumerator.MoveNext()); + + return first ? string.Empty : new string(buffer.AsSpan(0, position)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } +#else + // netstandard2.0 / net462 fallback using StringBuilder + var sb = new StringBuilder(); + var first = true; + + foreach (var kvp in parameters) + { + sb.Append(first ? '?' : '&'); + first = false; + + AppendEncoded(sb, kvp.Key, EncodingContext.QueryKey); + sb.Append('='); + AppendEncoded(sb, kvp.Value, EncodingContext.QueryValue); + } + + return sb.ToString(); +#endif + } + +#if NET6_0_OR_GREATER + private static int BuildCore( + ReadOnlySpan> parameters, + Span buffer + ) + { + var position = 0; + var first = true; + + foreach (var kvp in parameters) + { + buffer[position++] = first ? '?' : '&'; + first = false; + + position += EncodeWithCharSet( + kvp.Key.AsSpan(), + buffer.Slice(position), + EncodingContext.QueryKey + ); + buffer[position++] = '='; + position += EncodeWithCharSet( + kvp.Value.AsSpan(), + buffer.Slice(position), + EncodingContext.QueryValue + ); + } + + return position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EncodeWithCharSet( + ReadOnlySpan input, + Span output, + EncodingContext context + ) + { + if (!NeedsEncoding(input, context)) + { + input.CopyTo(output); + return input.Length; + } + + return EncodeSlow(input, output, context); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool NeedsEncoding(ReadOnlySpan value, EncodingContext context) + { + return context switch + { + EncodingContext.QueryKey => value.ContainsAnyExcept(SafeQueryKeyChars), + EncodingContext.QueryValue => value.ContainsAnyExcept(SafeQueryValueChars), + EncodingContext.Path => value.ContainsAnyExcept(SafePathChars), + _ => true, + }; + } + + private static int EncodeSlow( + ReadOnlySpan input, + Span output, + EncodingContext context + ) + { + var position = 0; + + foreach (var c in input) + { + if (IsSafeChar(c, context)) + { + output[position++] = c; + } + else if (c == ' ') + { + output[position++] = '%'; + output[position++] = '2'; + output[position++] = '0'; + } + else if (char.IsAscii(c)) + { + position += EncodeAscii((byte)c, output.Slice(position)); + } + else + { + position += EncodeUtf8(c, output.Slice(position)); + } + } + + return position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EncodeAscii(byte value, Span output) + { + output[0] = '%'; + output[1] = (char)UpperHexChars[value >> 4]; + output[2] = (char)UpperHexChars[value & 0xF]; + return 3; + } + + private static int EncodeUtf8(char c, Span output) + { + Span utf8Bytes = stackalloc byte[4]; + Span singleChar = stackalloc char[1] { c }; + var byteCount = global::System.Text.Encoding.UTF8.GetBytes(singleChar, utf8Bytes); + + var position = 0; + for (var i = 0; i < byteCount; i++) + { + output[position++] = '%'; + output[position++] = (char)UpperHexChars[utf8Bytes[i] >> 4]; + output[position++] = (char)UpperHexChars[utf8Bytes[i] & 0xF]; + } + + return position; + } +#else + // netstandard2.0 / net462 StringBuilder-based encoding + private static void AppendEncoded(StringBuilder sb, string value, EncodingContext context) + { + foreach (var c in value) + { + if (IsSafeChar(c, context)) + { + sb.Append(c); + } + else if (c == ' ') + { + sb.Append("%20"); + } + else if (c <= 127) + { + AppendPercentEncoded(sb, (byte)c); + } + else + { + var bytes = Encoding.UTF8.GetBytes(new[] { c }); + foreach (var b in bytes) + { + AppendPercentEncoded(sb, b); + } + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendPercentEncoded(StringBuilder sb, byte value) + { + sb.Append('%'); + sb.Append((char)UpperHexChars[value >> 4]); + sb.Append((char)UpperHexChars[value & 0xF]); + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeChar(char c, EncodingContext context) + { + return context switch + { + EncodingContext.QueryKey => IsSafeQueryKeyChar(c), + EncodingContext.QueryValue => IsSafeQueryValueChar(c), + EncodingContext.Path => IsSafePathChar(c), + _ => false, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeQueryKeyChar(char c) + { +#if NET8_0_OR_GREATER + return SafeQueryKeyChars.Contains(c); +#else + // query = *( pchar / "/" / "?" ) minus "&", "=", "+", "#" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == ',' + || c == ';' + || c == ':' + || c == '@' + || c == '/' + || c == '?'; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeQueryValueChar(char c) + { +#if NET8_0_OR_GREATER + return SafeQueryValueChars.Contains(c); +#else + // query = *( pchar / "/" / "?" ) minus "&", "+", "#" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == ',' + || c == ';' + || c == '=' + || c == ':' + || c == '@' + || c == '/' + || c == '?'; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafePathChar(char c) + { +#if NET8_0_OR_GREATER + return SafePathChars.Contains(c); +#else + // pchar = unreserved / sub-delims / ":" / "@" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == '&' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == '+' + || c == ',' + || c == ';' + || c == '=' + || c == ':' + || c == '@'; +#endif + } + + /// + /// Fluent builder for constructing query strings with support for simple parameters and deep object notation. + /// + public sealed class Builder + { + private readonly List> _params; + + /// + /// Initializes a new instance with default capacity. + /// + public Builder() + { + _params = new List>(); + } + + /// + /// Initializes a new instance with the specified initial capacity. + /// + public Builder(int capacity) + { + _params = new List>(capacity); + } + + /// + /// Adds a simple parameter. For collections, adds multiple key-value pairs (one per element). + /// + public Builder Add(string key, object? value) + { + if (value is null) + { + return this; + } + + // Handle string separately since it implements IEnumerable + if (value is string stringValue) + { + _params.Add(new KeyValuePair(key, stringValue)); + return this; + } + + // Handle collections (arrays, lists, etc.) - add each element as a separate key-value pair + if ( + value + is global::System.Collections.IEnumerable enumerable + and not global::System.Collections.IDictionary + ) + { + foreach (var item in enumerable) + { + if (item is not null) + { + _params.Add( + new KeyValuePair( + key, + ValueConvert.ToQueryStringValue(item) + ) + ); + } + } + return this; + } + + // Handle scalar values + _params.Add( + new KeyValuePair(key, ValueConvert.ToQueryStringValue(value)) + ); + return this; + } + + /// + /// Sets a parameter, removing any existing parameters with the same key before adding the new value. + /// For collections, removes all existing parameters with the key, then adds multiple key-value pairs (one per element). + /// This allows overriding parameters set earlier in the builder. + /// + public Builder Set(string key, object? value) + { + // Remove all existing parameters with this key + _params.RemoveAll(kv => kv.Key == key); + + // Add the new value(s) + return Add(key, value); + } + + /// + /// Merges additional query parameters with override semantics. + /// Groups parameters by key and calls Set() once per unique key. + /// This ensures that parameters with the same key are properly merged: + /// - If a key appears once, it's added as a single value + /// - If a key appears multiple times, all values are added as an array + /// - All parameters override any existing parameters with the same key + /// + public Builder MergeAdditional( + global::System.Collections.Generic.IEnumerable>? additionalParameters + ) + { + if (additionalParameters is null) + { + return this; + } + + // Group by key to handle multiple values for the same key correctly + var grouped = additionalParameters + .GroupBy(kv => kv.Key) + .Select(g => new global::System.Collections.Generic.KeyValuePair( + g.Key, + g.Count() == 1 ? (object)g.First().Value : g.Select(kv => kv.Value).ToArray() + )); + + foreach (var param in grouped) + { + Set(param.Key, param.Value); + } + + return this; + } + + /// + /// Adds a complex object using deep object notation with a prefix. + /// Deep object notation nests properties with brackets: prefix[key][nested]=value + /// + public Builder AddDeepObject(string prefix, object? value) + { + if (value is not null) + { + _params.AddRange(QueryStringConverter.ToDeepObject(prefix, value)); + } + return this; + } + + /// + /// Adds a complex object using exploded form notation with an optional prefix. + /// Exploded form flattens properties: prefix[key]=value (no deep nesting). + /// + public Builder AddExploded(string prefix, object? value) + { + if (value is not null) + { + _params.AddRange(QueryStringConverter.ToExplodedForm(prefix, value)); + } + return this; + } + + /// + /// Builds the final query string. + /// + public string Build() + { + return QueryStringBuilder.Build(_params); + } + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs new file mode 100644 index 000000000000..be39e2f51aa4 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs @@ -0,0 +1,259 @@ +using global::System.Text.Json; + +namespace SeedApi.Core; + +/// +/// Converts an object into a query string collection. +/// +internal static class QueryStringConverter +{ + /// + /// Converts an object into a query string collection using Deep Object notation with a prefix. + /// + /// The prefix to prepend to all keys (e.g., "session_settings"). Pass empty string for no prefix. + /// Object to form URL-encode. Can be an object, array of objects, or dictionary. + /// Throws when passing in a string or primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToDeepObject( + string prefix, + object value + ) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + JsonToDeepObject(json, prefix, queryCollection); + return queryCollection; + } + + /// + /// Converts an object into a query string collection using Deep Object notation. + /// + /// Object to form URL-encode. Can be an object, array of objects, or dictionary. + /// Throws when passing in a string or primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToDeepObject(object value) + { + return ToDeepObject("", value); + } + + /// + /// Converts an object into a query string collection using Exploded Form notation with a prefix. + /// + /// The prefix to prepend to all keys. Pass empty string for no prefix. + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToExplodedForm( + string prefix, + object value + ) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + AssertRootJson(json); + JsonToFormExploded(json, prefix, queryCollection); + return queryCollection; + } + + /// + /// Converts an object into a query string collection using Exploded Form notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToExplodedForm(object value) + { + return ToExplodedForm("", value); + } + + /// + /// Converts an object into a query string collection using Form notation without exploding parameters. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToForm(object value) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + AssertRootJson(json); + JsonToForm(json, "", queryCollection); + return queryCollection; + } + + private static void AssertRootJson(JsonElement json) + { + switch (json.ValueKind) + { + case JsonValueKind.Object: + break; + case JsonValueKind.Array: + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + default: + throw new global::System.Exception( + $"Only objects can be converted to query string collections. Given type is {json.ValueKind}." + ); + } + } + + private static void JsonToForm( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToForm(property.Value, newPrefix, parameters); + } + break; + case JsonValueKind.Array: + var arrayValues = element.EnumerateArray().Select(ValueToString).ToArray(); + parameters.Add( + new KeyValuePair(prefix, string.Join(",", arrayValues)) + ); + break; + case JsonValueKind.Null: + break; + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static void JsonToFormExploded( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToFormExploded(property.Value, newPrefix, parameters); + } + + break; + case JsonValueKind.Array: + foreach (var item in element.EnumerateArray()) + { + if ( + item.ValueKind != JsonValueKind.Object + && item.ValueKind != JsonValueKind.Array + ) + { + parameters.Add( + new KeyValuePair(prefix, ValueToString(item)) + ); + } + else + { + JsonToFormExploded(item, prefix, parameters); + } + } + + break; + case JsonValueKind.Null: + break; + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static void JsonToDeepObject( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToDeepObject(property.Value, newPrefix, parameters); + } + + break; + case JsonValueKind.Array: + var index = 0; + foreach (var item in element.EnumerateArray()) + { + var newPrefix = $"{prefix}[{index++}]"; + + if ( + item.ValueKind != JsonValueKind.Object + && item.ValueKind != JsonValueKind.Array + ) + { + parameters.Add( + new KeyValuePair(newPrefix, ValueToString(item)) + ); + } + else + { + JsonToDeepObject(item, newPrefix, parameters); + } + } + + break; + case JsonValueKind.Null: + case JsonValueKind.Undefined: + // Skip null and undefined values - don't add parameters for them + break; + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static string ValueToString(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString() ?? "", + JsonValueKind.Number => element.GetRawText(), + JsonValueKind.True => "true", + JsonValueKind.False => "false", + JsonValueKind.Null => "", + _ => element.GetRawText(), + }; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs new file mode 100644 index 000000000000..edf973008a15 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs @@ -0,0 +1,343 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; +using global::System.Text; +using SystemTask = global::System.Threading.Tasks.Task; + +namespace SeedApi.Core; + +/// +/// Utility class for making raw HTTP requests to the API. +/// +internal partial class RawClient(ClientOptions clientOptions) +{ + private const int MaxRetryDelayMs = 60000; + private const double JitterFactor = 0.2; +#if NET6_0_OR_GREATER + // Use Random.Shared for thread-safe random number generation on .NET 6+ +#else + private static readonly object JitterLock = new(); + private static readonly Random JitterRandom = new(); +#endif + internal int BaseRetryDelay { get; set; } = 1000; + + /// + /// The client options applied on every request. + /// + internal readonly ClientOptions Options = clientOptions; + + internal async global::System.Threading.Tasks.Task SendRequestAsync( + global::SeedApi.Core.BaseRequest request, + CancellationToken cancellationToken = default + ) + { + // Apply the request timeout. + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = request.Options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); + + var httpRequest = await CreateHttpRequestAsync(request).ConfigureAwait(false); + // Send the request. + return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token) + .ConfigureAwait(false); + } + + internal async global::System.Threading.Tasks.Task SendRequestAsync( + HttpRequestMessage request, + IRequestOptions? options, + CancellationToken cancellationToken = default + ) + { + // Apply the request timeout. + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); + + // Send the request. + return await SendWithRetriesAsync(request, options, cts.Token).ConfigureAwait(false); + } + + private static async global::System.Threading.Tasks.Task CloneRequestAsync( + HttpRequestMessage request + ) + { + var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri); + clonedRequest.Version = request.Version; + + if (request.Content != null) + { + switch (request.Content) + { + case MultipartContent oldMultipartFormContent: + var originalBoundary = + oldMultipartFormContent + .Headers.ContentType?.Parameters.First(p => + p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) + ) + .Value?.Trim('"') ?? Guid.NewGuid().ToString(); + var newMultipartContent = oldMultipartFormContent switch + { + MultipartFormDataContent => new MultipartFormDataContent(originalBoundary), + _ => new MultipartContent(), + }; + foreach (var content in oldMultipartFormContent) + { + var ms = new MemoryStream(); + await content.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + var newPart = new StreamContent(ms); + foreach (var header in content.Headers) + { + newPart.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + newMultipartContent.Add(newPart); + } + + clonedRequest.Content = newMultipartContent; + break; + default: + var bodyStream = new MemoryStream(); + await request.Content.CopyToAsync(bodyStream).ConfigureAwait(false); + bodyStream.Position = 0; + var clonedContent = new StreamContent(bodyStream); + foreach (var header in request.Content.Headers) + { + clonedContent.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + clonedRequest.Content = clonedContent; + break; + } + } + + foreach (var header in request.Headers) + { + clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return clonedRequest; + } + + /// + /// Sends the request with retries, unless the request content is not retryable, + /// such as stream requests and multipart form data with stream content. + /// + private async global::System.Threading.Tasks.Task SendWithRetriesAsync( + HttpRequestMessage request, + IRequestOptions? options, + CancellationToken cancellationToken + ) + { + var httpClient = options?.HttpClient ?? Options.HttpClient; + var maxRetries = options?.MaxRetries ?? Options.MaxRetries; + var response = await httpClient + .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + var isRetryableContent = IsRetryableContent(request); + + if (!isRetryableContent) + { + return new global::SeedApi.Core.ApiResponse + { + StatusCode = (int)response.StatusCode, + Raw = response, + }; + } + + for (var i = 0; i < maxRetries; i++) + { + if (!ShouldRetry(response)) + { + break; + } + + var delayMs = GetRetryDelayFromHeaders(response, i); + await SystemTask.Delay(delayMs, cancellationToken).ConfigureAwait(false); + using var retryRequest = await CloneRequestAsync(request).ConfigureAwait(false); + response = await httpClient + .SendAsync( + retryRequest, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken + ) + .ConfigureAwait(false); + } + + return new global::SeedApi.Core.ApiResponse + { + StatusCode = (int)response.StatusCode, + Raw = response, + }; + } + + private static bool ShouldRetry(HttpResponseMessage response) + { + var statusCode = (int)response.StatusCode; + return statusCode is 408 or 429 or >= 500; + } + + private static int AddPositiveJitter(int delayMs) + { +#if NET6_0_OR_GREATER + var random = Random.Shared.NextDouble(); +#else + double random; + lock (JitterLock) + { + random = JitterRandom.NextDouble(); + } +#endif + var jitterMultiplier = 1 + random * JitterFactor; + return (int)(delayMs * jitterMultiplier); + } + + private static int AddSymmetricJitter(int delayMs) + { +#if NET6_0_OR_GREATER + var random = Random.Shared.NextDouble(); +#else + double random; + lock (JitterLock) + { + random = JitterRandom.NextDouble(); + } +#endif + var jitterMultiplier = 1 + (random - 0.5) * JitterFactor; + return (int)(delayMs * jitterMultiplier); + } + + private int GetRetryDelayFromHeaders(HttpResponseMessage response, int retryAttempt) + { + if (response.Headers.TryGetValues("Retry-After", out var retryAfterValues)) + { + var retryAfter = retryAfterValues.FirstOrDefault(); + if (!string.IsNullOrEmpty(retryAfter)) + { + if (int.TryParse(retryAfter, out var retryAfterSeconds) && retryAfterSeconds > 0) + { + return Math.Min(retryAfterSeconds * 1000, MaxRetryDelayMs); + } + + if (DateTimeOffset.TryParse(retryAfter, out var retryAfterDate)) + { + var delay = (int)(retryAfterDate - DateTimeOffset.UtcNow).TotalMilliseconds; + if (delay > 0) + { + return Math.Min(delay, MaxRetryDelayMs); + } + } + } + } + + if (response.Headers.TryGetValues("X-RateLimit-Reset", out var rateLimitResetValues)) + { + var rateLimitReset = rateLimitResetValues.FirstOrDefault(); + if ( + !string.IsNullOrEmpty(rateLimitReset) + && long.TryParse(rateLimitReset, out var resetTime) + ) + { + var resetDateTime = DateTimeOffset.FromUnixTimeSeconds(resetTime); + var delay = (int)(resetDateTime - DateTimeOffset.UtcNow).TotalMilliseconds; + if (delay > 0) + { + return AddPositiveJitter(Math.Min(delay, MaxRetryDelayMs)); + } + } + } + + var exponentialDelay = Math.Min(BaseRetryDelay * (1 << retryAttempt), MaxRetryDelayMs); + return AddSymmetricJitter(exponentialDelay); + } + + private static bool IsRetryableContent(HttpRequestMessage request) + { + return request.Content switch + { + IIsRetryableContent c => c.IsRetryable, + StreamContent => false, + MultipartContent content => !content.Any(c => c is StreamContent), + _ => true, + }; + } + + internal async global::System.Threading.Tasks.Task CreateHttpRequestAsync( + global::SeedApi.Core.BaseRequest request + ) + { + var url = BuildUrl(request); + var httpRequest = new HttpRequestMessage(request.Method, url); + httpRequest.Content = request.CreateContent(); + SetHeaders(httpRequest, request.Headers); + + return httpRequest; + } + + private string BuildUrl(global::SeedApi.Core.BaseRequest request) + { + var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl ?? Options.BaseUrl; + + var trimmedBaseUrl = baseUrl.TrimEnd('/'); + var trimmedBasePath = request.Path.TrimStart('/'); + var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; + + // Append query string if present + if (!string.IsNullOrEmpty(request.QueryString)) + { + return url + request.QueryString; + } + + return url; + } + + private void SetHeaders(HttpRequestMessage httpRequest, Dictionary? headers) + { + if (headers is null) + { + return; + } + + foreach (var kv in headers) + { + if (kv.Value is null) + { + continue; + } + + httpRequest.Headers.TryAddWithoutValidation(kv.Key, kv.Value); + } + } + + private static (Encoding encoding, string? charset, string mediaType) ParseContentTypeOrDefault( + string? contentType, + Encoding encodingFallback, + string mediaTypeFallback + ) + { + var encoding = encodingFallback; + var mediaType = mediaTypeFallback; + string? charset = null; + if (string.IsNullOrEmpty(contentType)) + { + return (encoding, charset, mediaType); + } + + if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) + { + return (encoding, charset, mediaType); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) + { + charset = mediaTypeHeaderValue.CharSet; + encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) + { + mediaType = mediaTypeHeaderValue.MediaType; + } + + return (encoding, charset, mediaType); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs new file mode 100644 index 000000000000..5fc790dcc6f1 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs @@ -0,0 +1,24 @@ +using global::System.Net; + +namespace SeedApi.Core; + +/// +/// Contains HTTP response metadata including status code, URL, and headers. +/// +public record RawResponse +{ + /// + /// The HTTP status code of the response. + /// + public required HttpStatusCode StatusCode { get; init; } + + /// + /// The request URL that generated this response. + /// + public required Uri Url { get; init; } + + /// + /// The HTTP response headers. + /// + public required Core.ResponseHeaders Headers { get; init; } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs new file mode 100644 index 000000000000..2c394dd9a7a9 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs @@ -0,0 +1,108 @@ +using global::System.Collections; +using global::System.Net.Http.Headers; + +namespace SeedApi.Core; + +/// +/// Represents HTTP response headers with case-insensitive lookup. +/// +public readonly struct ResponseHeaders : IEnumerable +{ + private readonly HttpResponseHeaders? _headers; + private readonly HttpContentHeaders? _contentHeaders; + + private ResponseHeaders(HttpResponseHeaders headers, HttpContentHeaders? contentHeaders) + { + _headers = headers; + _contentHeaders = contentHeaders; + } + + /// + /// Gets the Content-Type header value, if present. + /// + public string? ContentType => _contentHeaders?.ContentType?.ToString(); + + /// + /// Gets the Content-Length header value, if present. + /// + public long? ContentLength => _contentHeaders?.ContentLength; + + /// + /// Creates a ResponseHeaders instance from an HttpResponseMessage. + /// + public static ResponseHeaders FromHttpResponseMessage(HttpResponseMessage response) + { + return new ResponseHeaders(response.Headers, response.Content?.Headers); + } + + /// + /// Tries to get a single header value. Returns the first value if multiple values exist. + /// + public bool TryGetValue(string name, out string? value) + { + if (TryGetValues(name, out var values) && values is not null) + { + value = values.FirstOrDefault(); + return true; + } + + value = null; + return false; + } + + /// + /// Tries to get all values for a header. + /// + public bool TryGetValues(string name, out IEnumerable? values) + { + if (_headers?.TryGetValues(name, out values) == true) + { + return true; + } + + if (_contentHeaders?.TryGetValues(name, out values) == true) + { + return true; + } + + values = null; + return false; + } + + /// + /// Checks if the headers contain a specific header name. + /// + public bool Contains(string name) + { + return _headers?.Contains(name) == true || _contentHeaders?.Contains(name) == true; + } + + /// + /// Gets an enumerator for all headers. + /// + public IEnumerator GetEnumerator() + { + if (_headers is not null) + { + foreach (var header in _headers) + { + yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); + } + } + + if (_contentHeaders is not null) + { + foreach (var header in _contentHeaders) + { + yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +/// +/// Represents a single HTTP header. +/// +public readonly record struct HttpHeader(string Name, string Value); diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs new file mode 100644 index 000000000000..54076b686602 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs @@ -0,0 +1,29 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; + +namespace SeedApi.Core; + +/// +/// The request object to be sent for streaming uploads. +/// +internal record StreamRequest : BaseRequest +{ + internal Stream? Body { get; init; } + + internal override HttpContent? CreateContent() + { + if (Body is null) + { + return null; + } + + var content = new StreamContent(Body) + { + Headers = + { + ContentType = MediaTypeHeaderValue.Parse(ContentType ?? "application/octet-stream"), + }, + }; + return content; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs new file mode 100644 index 000000000000..9f1f4a1c1181 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +public interface IStringEnum : IEquatable +{ + public string Value { get; } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs new file mode 100644 index 000000000000..704cb6836ab8 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +internal static class StringEnumExtensions +{ + public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs new file mode 100644 index 000000000000..ba1f818399e2 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs @@ -0,0 +1,115 @@ +using global::System.Globalization; + +namespace SeedApi.Core; + +/// +/// Convert values to string for path and query parameters. +/// +public static class ValueConvert +{ + internal static string ToPathParameterString(T value) => ToString(value); + + internal static string ToPathParameterString(bool v) => ToString(v); + + internal static string ToPathParameterString(int v) => ToString(v); + + internal static string ToPathParameterString(long v) => ToString(v); + + internal static string ToPathParameterString(float v) => ToString(v); + + internal static string ToPathParameterString(double v) => ToString(v); + + internal static string ToPathParameterString(decimal v) => ToString(v); + + internal static string ToPathParameterString(short v) => ToString(v); + + internal static string ToPathParameterString(ushort v) => ToString(v); + + internal static string ToPathParameterString(uint v) => ToString(v); + + internal static string ToPathParameterString(ulong v) => ToString(v); + + internal static string ToPathParameterString(string v) => + QueryStringBuilder.EncodePathSegment(v); + + internal static string ToPathParameterString(char v) => ToString(v); + + internal static string ToPathParameterString(Guid v) => ToString(v); + + internal static string ToQueryStringValue(T value) => value is null ? "" : ToString(value); + + internal static string ToQueryStringValue(bool v) => ToString(v); + + internal static string ToQueryStringValue(int v) => ToString(v); + + internal static string ToQueryStringValue(long v) => ToString(v); + + internal static string ToQueryStringValue(float v) => ToString(v); + + internal static string ToQueryStringValue(double v) => ToString(v); + + internal static string ToQueryStringValue(decimal v) => ToString(v); + + internal static string ToQueryStringValue(short v) => ToString(v); + + internal static string ToQueryStringValue(ushort v) => ToString(v); + + internal static string ToQueryStringValue(uint v) => ToString(v); + + internal static string ToQueryStringValue(ulong v) => ToString(v); + + internal static string ToQueryStringValue(string v) => v is null ? "" : v; + + internal static string ToQueryStringValue(char v) => ToString(v); + + internal static string ToQueryStringValue(Guid v) => ToString(v); + + internal static string ToString(T value) + { + return value switch + { + null => "null", + string str => str, + true => "true", + false => "false", + int i => ToString(i), + long l => ToString(l), + float f => ToString(f), + double d => ToString(d), + decimal dec => ToString(dec), + short s => ToString(s), + ushort u => ToString(u), + uint u => ToString(u), + ulong u => ToString(u), + char c => ToString(c), + Guid guid => ToString(guid), + _ => JsonUtils.SerializeRelaxedEscaping(value, value.GetType()).Trim('"'), + }; + } + + internal static string ToString(bool v) => v ? "true" : "false"; + + internal static string ToString(int v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(long v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(float v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(double v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(decimal v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(short v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(ushort v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(uint v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(ulong v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(char v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(string v) => v; + + internal static string ToString(Guid v) => v.ToString("D"); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs new file mode 100644 index 000000000000..4b1eb3e650c8 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs @@ -0,0 +1,31 @@ +namespace SeedApi; + +public partial interface ISeedApiClient +{ + WithRawResponseTask SearchRuleTypesAsync( + SearchRuleTypesRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask CreateRuleAsync( + RuleCreateRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask ListUsersAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask GetEntityAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask GetOrganizationAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs new file mode 100644 index 000000000000..6437574c29a8 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs @@ -0,0 +1,20 @@ +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleCreateRequest +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("executionContext")] + public required RuleExecutionContext ExecutionContext { get; set; } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs new file mode 100644 index 000000000000..3c1562150a13 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs @@ -0,0 +1,17 @@ +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record SearchRuleTypesRequest +{ + [JsonIgnore] + public string? Query { get; set; } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props new file mode 100644 index 000000000000..17a84cada530 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props @@ -0,0 +1,20 @@ + + + + diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj new file mode 100644 index 000000000000..d74cb5af93ca --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj @@ -0,0 +1,56 @@ + + + net462;net8.0;net9.0;netstandard2.0 + enable + 12 + enable + 0.0.1 + $(Version) + $(Version) + README.md + https://github.com/allof-inline/fern + true + + + false + + + $(DefineConstants);USE_PORTABLE_DATE_ONLY + true + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + <_Parameter1>SeedApi.Test + + + + diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs new file mode 100644 index 000000000000..789e4c07f0e6 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs @@ -0,0 +1,429 @@ +using global::System.Text.Json; +using SeedApi.Core; + +namespace SeedApi; + +public partial class SeedApiClient : ISeedApiClient +{ + private readonly RawClient _client; + + public SeedApiClient(ClientOptions? clientOptions = null) + { + clientOptions ??= new ClientOptions(); + var platformHeaders = new Headers( + new Dictionary() + { + { "X-Fern-Language", "C#" }, + { "X-Fern-SDK-Name", "SeedApi" }, + { "X-Fern-SDK-Version", Version.Current }, + { "User-Agent", "Fernallof-inline/0.0.1" }, + } + ); + foreach (var header in platformHeaders) + { + if (!clientOptions.Headers.ContainsKey(header.Key)) + { + clientOptions.Headers[header.Key] = header.Value; + } + } + _client = new RawClient(clientOptions); + } + + private async Task> SearchRuleTypesAsyncCore( + SearchRuleTypesRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _queryString = new SeedApi.Core.QueryStringBuilder.Builder(capacity: 1) + .Add("query", request.Query) + .MergeAdditional(options?.AdditionalQueryParameters) + .Build(); + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "rule-types", + QueryString = _queryString, + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> CreateRuleAsyncCore( + RuleCreateRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Post, + Path = "rules", + Body = request, + Headers = _headers, + ContentType = "application/json", + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> ListUsersAsyncCore( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "users", + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> GetEntityAsyncCore( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "entities", + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> GetOrganizationAsyncCore( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "organizations", + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + /// + /// await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); + /// + public WithRawResponseTask SearchRuleTypesAsync( + SearchRuleTypesRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + SearchRuleTypesAsyncCore(request, options, cancellationToken) + ); + } + + /// + /// await client.CreateRuleAsync( + /// new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } + /// ); + /// + public WithRawResponseTask CreateRuleAsync( + RuleCreateRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + CreateRuleAsyncCore(request, options, cancellationToken) + ); + } + + /// + /// await client.ListUsersAsync(); + /// + public WithRawResponseTask ListUsersAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + ListUsersAsyncCore(options, cancellationToken) + ); + } + + /// + /// await client.GetEntityAsync(); + /// + public WithRawResponseTask GetEntityAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + GetEntityAsyncCore(options, cancellationToken) + ); + } + + /// + /// await client.GetOrganizationAsync(); + /// + public WithRawResponseTask GetOrganizationAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + GetOrganizationAsyncCore(options, cancellationToken) + ); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs new file mode 100644 index 000000000000..c0d843281678 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs @@ -0,0 +1,56 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +/// +/// Common audit metadata. +/// +[Serializable] +public record AuditInfo : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs new file mode 100644 index 000000000000..eb944d0030da --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public BaseOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs new file mode 100644 index 000000000000..fcb0efec5fea --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from BaseOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Subscription tier. + /// + [JsonPropertyName("tier")] + public string? Tier { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs new file mode 100644 index 000000000000..633fb725fb81 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs @@ -0,0 +1,46 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record CombinedEntity : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Describable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonPropertyName("status")] + public required CombinedEntityStatus Status { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs new file mode 100644 index 000000000000..0ab2467f6bd7 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs @@ -0,0 +1,115 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] +[Serializable] +public readonly record struct CombinedEntityStatus : IStringEnum +{ + public static readonly CombinedEntityStatus Active = new(Values.Active); + + public static readonly CombinedEntityStatus Archived = new(Values.Archived); + + public CombinedEntityStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static CombinedEntityStatus FromCustom(string value) + { + return new CombinedEntityStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(CombinedEntityStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(CombinedEntityStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(CombinedEntityStatus value) => value.Value; + + public static explicit operator CombinedEntityStatus(string value) => new(value); + + internal class CombinedEntityStatusSerializer : JsonConverter + { + public override CombinedEntityStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override CombinedEntityStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Archived = "archived"; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs new file mode 100644 index 000000000000..f521e9082be7 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Describable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Display name from Describable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs new file mode 100644 index 000000000000..7bc064682236 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs @@ -0,0 +1,28 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("metadata")] + public DetailedOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs new file mode 100644 index 000000000000..798903997238 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from DetailedOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Custom domain name. + /// + [JsonPropertyName("domain")] + public string? Domain { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs new file mode 100644 index 000000000000..05b3808b610b --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Identifiable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Identifiable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs new file mode 100644 index 000000000000..602a7e16c442 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Organization : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public OrganizationMetadata? Metadata { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs new file mode 100644 index 000000000000..084daaac298a --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record OrganizationMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from DetailedOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Custom domain name. + /// + [JsonPropertyName("domain")] + public string? Domain { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs new file mode 100644 index 000000000000..9f4b1b5c0820 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PaginatedResult : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable Results { get; set; } = new List(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs new file mode 100644 index 000000000000..b1f0001a4733 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PagingCursors : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Cursor for the next page of results. + /// + [JsonPropertyName("next")] + public required string Next { get; set; } + + /// + /// Cursor for the previous page of results. + /// + [JsonPropertyName("previous")] + public string? Previous { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs new file mode 100644 index 000000000000..d22a26079f4e --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] +[Serializable] +public readonly record struct RuleExecutionContext : IStringEnum +{ + public static readonly RuleExecutionContext Prod = new(Values.Prod); + + public static readonly RuleExecutionContext Staging = new(Values.Staging); + + public static readonly RuleExecutionContext Dev = new(Values.Dev); + + public RuleExecutionContext(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleExecutionContext FromCustom(string value) + { + return new RuleExecutionContext(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleExecutionContext value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleExecutionContext value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleExecutionContext value) => value.Value; + + public static explicit operator RuleExecutionContext(string value) => new(value); + + internal class RuleExecutionContextSerializer : JsonConverter + { + public override RuleExecutionContext Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleExecutionContext ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Prod = "prod"; + + public const string Staging = "staging"; + + public const string Dev = "dev"; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs new file mode 100644 index 000000000000..78e1a0e6d50e --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs @@ -0,0 +1,65 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("status")] + public required RuleResponseStatus Status { get; set; } + + [JsonPropertyName("executionContext")] + public RuleExecutionContext? ExecutionContext { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs new file mode 100644 index 000000000000..9b587cdbcba0 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] +[Serializable] +public readonly record struct RuleResponseStatus : IStringEnum +{ + public static readonly RuleResponseStatus Active = new(Values.Active); + + public static readonly RuleResponseStatus Inactive = new(Values.Inactive); + + public static readonly RuleResponseStatus Draft = new(Values.Draft); + + public RuleResponseStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleResponseStatus FromCustom(string value) + { + return new RuleResponseStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleResponseStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleResponseStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleResponseStatus value) => value.Value; + + public static explicit operator RuleResponseStatus(string value) => new(value); + + internal class RuleResponseStatusSerializer : JsonConverter + { + public override RuleResponseStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleResponseStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Inactive = "inactive"; + + public const string Draft = "draft"; + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs new file mode 100644 index 000000000000..578b90315dde --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleType : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs new file mode 100644 index 000000000000..e05899151a38 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleTypeSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs new file mode 100644 index 000000000000..abc389c0a6b6 --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record User : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("email")] + public required string Email { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs new file mode 100644 index 000000000000..55c5368d774e --- /dev/null +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record UserSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/.editorconfig b/seed/csharp-sdk/allof/.editorconfig new file mode 100644 index 000000000000..1e7a0adbac80 --- /dev/null +++ b/seed/csharp-sdk/allof/.editorconfig @@ -0,0 +1,35 @@ +root = true + +[*.cs] +resharper_arrange_object_creation_when_type_evident_highlighting = hint +resharper_auto_property_can_be_made_get_only_global_highlighting = hint +resharper_check_namespace_highlighting = hint +resharper_class_never_instantiated_global_highlighting = hint +resharper_class_never_instantiated_local_highlighting = hint +resharper_collection_never_updated_global_highlighting = hint +resharper_convert_type_check_pattern_to_null_check_highlighting = hint +resharper_inconsistent_naming_highlighting = hint +resharper_member_can_be_private_global_highlighting = hint +resharper_member_hides_static_from_outer_class_highlighting = hint +resharper_not_accessed_field_local_highlighting = hint +resharper_nullable_warning_suppression_is_used_highlighting = suggestion +resharper_partial_type_with_single_part_highlighting = hint +resharper_prefer_concrete_value_over_default_highlighting = none +resharper_private_field_can_be_converted_to_local_variable_highlighting = hint +resharper_property_can_be_made_init_only_global_highlighting = hint +resharper_property_can_be_made_init_only_local_highlighting = hint +resharper_redundant_name_qualifier_highlighting = none +resharper_redundant_using_directive_highlighting = hint +resharper_replace_slice_with_range_indexer_highlighting = none +resharper_unused_auto_property_accessor_global_highlighting = hint +resharper_unused_auto_property_accessor_local_highlighting = hint +resharper_unused_member_global_highlighting = hint +resharper_unused_type_global_highlighting = hint +resharper_use_string_interpolation_highlighting = hint +dotnet_diagnostic.CS1591.severity = suggestion + +[src/**/Types/*.cs] +resharper_check_namespace_highlighting = none + +[src/**/Core/Public/*.cs] +resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-sdk/allof/.fern/metadata.json b/seed/csharp-sdk/allof/.fern/metadata.json new file mode 100644 index 000000000000..91d0855bee07 --- /dev/null +++ b/seed/csharp-sdk/allof/.fern/metadata.json @@ -0,0 +1,8 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-csharp-sdk", + "generatorVersion": "latest", + "generatorConfig": {}, + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/csharp-sdk/allof/.github/workflows/ci.yml b/seed/csharp-sdk/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..87068349b616 --- /dev/null +++ b/seed/csharp-sdk/allof/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + DOTNET_NOLOGO: true + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Install tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore src/SeedApi/SeedApi.csproj + + - name: Build + run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release + + - name: Restore test dependencies + run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj + + - name: Build tests + run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release + + - name: Test + run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release + + - name: Pack + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release + + - name: Publish to NuGet.org + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" + diff --git a/seed/csharp-sdk/allof/.gitignore b/seed/csharp-sdk/allof/.gitignore new file mode 100644 index 000000000000..11014f2b33d7 --- /dev/null +++ b/seed/csharp-sdk/allof/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +## This is based on `dotnet new gitignore` and customized by Fern + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +# [Rr]elease/ (Ignored by Fern) +# [Rr]eleases/ (Ignored by Fern) +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +# [Ll]og/ (Ignored by Fern) +# [Ll]ogs/ (Ignored by Fern) + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/seed/csharp-sdk/allof/README.md b/seed/csharp-sdk/allof/README.md new file mode 100644 index 000000000000..8680768cea49 --- /dev/null +++ b/seed/csharp-sdk/allof/README.md @@ -0,0 +1,216 @@ +# Seed C# Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FC%23) +[![nuget shield](https://img.shields.io/nuget/v/Fernallof)](https://nuget.org/packages/Fernallof) + +The Seed C# library provides convenient access to the Seed APIs from C#. + +## Table of Contents + +- [Requirements](#requirements) +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Raw Response](#raw-response) + - [Additional Headers](#additional-headers) + - [Additional Query Parameters](#additional-query-parameters) + - [Forward Compatible Enums](#forward-compatible-enums) +- [Contributing](#contributing) + +## Requirements + +This SDK requires: + +## Installation + +```sh +dotnet add package Fernallof +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```csharp +using SeedApi; + +var client = new SeedApiClient(); +await client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } +); +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```csharp +using SeedApi; + +var client = new SeedApiClient(new ClientOptions +{ + BaseUrl = SeedApiEnvironment.Default +}); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```csharp +using SeedApi; + +try { + var response = await client.CreateRuleAsync(...); +} catch (SeedApiApiException e) { + System.Console.WriteLine(e.Body); + System.Console.WriteLine(e.StatusCode); +} +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `MaxRetries` request option to configure this behavior. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + MaxRetries: 0 // Override MaxRetries at the request level + } +); +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s + } +); +``` + +### Raw Response + +Access raw HTTP response data (status code, headers, URL) alongside parsed response data using the `.WithRawResponse()` method. + +```csharp +using SeedApi; + +// Access raw response data (status code, headers, etc.) alongside the parsed response +var result = await client.CreateRuleAsync(...).WithRawResponse(); + +// Access the parsed data +var data = result.Data; + +// Access raw response metadata +var statusCode = result.RawResponse.StatusCode; +var headers = result.RawResponse.Headers; +var url = result.RawResponse.Url; + +// Access specific headers (case-insensitive) +if (headers.TryGetValue("X-Request-Id", out var requestId)) +{ + System.Console.WriteLine($"Request ID: {requestId}"); +} + +// For the default behavior, simply await without .WithRawResponse() +var data = await client.CreateRuleAsync(...); +``` + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + AdditionalHeaders = new Dictionary + { + { "X-Custom-Header", "custom-value" } + } + } +); +``` + +### Additional Query Parameters + +If you would like to send additional query parameters as part of the request, use the `AdditionalQueryParameters` request option. + +```csharp +var response = await client.CreateRuleAsync( + ..., + new RequestOptions { + AdditionalQueryParameters = new Dictionary + { + { "custom_param", "custom-value" } + } + } +); +``` + +### Forward Compatible Enums + +This SDK uses forward-compatible enums that can handle unknown values gracefully. + +```csharp +using SeedApi; + +// Using a built-in value +var ruleExecutionContext = RuleExecutionContext.Prod; + +// Using a custom value +var customRuleExecutionContext = RuleExecutionContext.FromCustom("custom-value"); + +// Using in a switch statement +switch (ruleExecutionContext.Value) +{ + case RuleExecutionContext.Values.Prod: + Console.WriteLine("Prod"); + break; + default: + Console.WriteLine($"Unknown value: {ruleExecutionContext.Value}"); + break; +} + +// Explicit casting +string ruleExecutionContextString = (string)RuleExecutionContext.Prod; +RuleExecutionContext ruleExecutionContextFromString = (RuleExecutionContext)"prod"; +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/csharp-sdk/allof/SeedApi.slnx b/seed/csharp-sdk/allof/SeedApi.slnx new file mode 100644 index 000000000000..d4c63c241aad --- /dev/null +++ b/seed/csharp-sdk/allof/SeedApi.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/seed/csharp-sdk/allof/reference.md b/seed/csharp-sdk/allof/reference.md new file mode 100644 index 000000000000..09ba75269a0f --- /dev/null +++ b/seed/csharp-sdk/allof/reference.md @@ -0,0 +1,158 @@ +# Reference +
client.SearchRuleTypesAsync(SearchRuleTypesRequest { ... }) -> WithRawResponseTask<RuleTypeSearchResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SearchRuleTypesRequest` + +
+
+
+
+ + +
+
+
+ +
client.CreateRuleAsync(RuleCreateRequest { ... }) -> WithRawResponseTask<RuleResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `RuleCreateRequest` + +
+
+
+
+ + +
+
+
+ +
client.ListUsersAsync() -> WithRawResponseTask<UserSearchResponse> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.ListUsersAsync(); +``` +
+
+
+
+ + +
+
+
+ +
client.GetEntityAsync() -> WithRawResponseTask<CombinedEntity> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.GetEntityAsync(); +``` +
+
+
+
+ + +
+
+
+ +
client.GetOrganizationAsync() -> WithRawResponseTask<Organization> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.GetOrganizationAsync(); +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/csharp-sdk/allof/snippet.json b/seed/csharp-sdk/allof/snippet.json new file mode 100644 index 000000000000..df8ab96f5bc6 --- /dev/null +++ b/seed/csharp-sdk/allof/snippet.json @@ -0,0 +1,65 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.SearchRuleTypesAsync(new SearchRuleTypesRequest());\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.CreateRuleAsync(\n new RuleCreateRequest { Name = \"name\", ExecutionContext = RuleExecutionContext.Prod }\n);\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.ListUsersAsync();\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetEntityAsync();\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "csharp", + "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetOrganizationAsync();\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs new file mode 100644 index 000000000000..0c6994903107 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs @@ -0,0 +1,19 @@ +using SeedApi; + +namespace Usage; + +public class Example0 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.SearchRuleTypesAsync( + new SearchRuleTypesRequest() + ); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs new file mode 100644 index 000000000000..44ebc3965c0d --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs @@ -0,0 +1,21 @@ +using SeedApi; + +namespace Usage; + +public class Example1 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.SearchRuleTypesAsync( + new SearchRuleTypesRequest { + Query = "query" + } + ); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs new file mode 100644 index 000000000000..b63878a26a91 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs @@ -0,0 +1,22 @@ +using SeedApi; + +namespace Usage; + +public class Example2 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.CreateRuleAsync( + new RuleCreateRequest { + Name = "name", + ExecutionContext = RuleExecutionContext.Prod + } + ); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs new file mode 100644 index 000000000000..eb0371508e02 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs @@ -0,0 +1,22 @@ +using SeedApi; + +namespace Usage; + +public class Example3 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.CreateRuleAsync( + new RuleCreateRequest { + Name = "name", + ExecutionContext = RuleExecutionContext.Prod + } + ); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs new file mode 100644 index 000000000000..440f17e55522 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example4 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.ListUsersAsync(); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs new file mode 100644 index 000000000000..c1d081509932 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example5 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.ListUsersAsync(); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs new file mode 100644 index 000000000000..8694c56b98a0 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example6 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetEntityAsync(); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs new file mode 100644 index 000000000000..bc47bce361fb --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example7 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetEntityAsync(); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs new file mode 100644 index 000000000000..897c0c509b2e --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example8 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetOrganizationAsync(); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs new file mode 100644 index 000000000000..2f7b06bd2b7b --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs @@ -0,0 +1,17 @@ +using SeedApi; + +namespace Usage; + +public class Example9 +{ + public async Task Do() { + var client = new SeedApiClient( + clientOptions: new ClientOptions { + BaseUrl = "https://api.fern.com" + } + ); + + await client.GetOrganizationAsync(); + } + +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj new file mode 100644 index 000000000000..3417db2e58e2 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + 12 + enable + enable + + + + + + \ No newline at end of file diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs new file mode 100644 index 000000000000..7ad11f298637 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs @@ -0,0 +1,326 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class HeadersBuilderTests +{ + [Test] + public async global::System.Threading.Tasks.Task Add_SimpleHeaders() + { + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") + .Add("Authorization", "Bearer token123") + .Add("X-API-Key", "key456") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); + Assert.That(headers["Authorization"], Is.EqualTo("Bearer token123")); + Assert.That(headers["X-API-Key"], Is.EqualTo("key456")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_NullValuesIgnored() + { + var headers = await new HeadersBuilder.Builder() + .Add("Header1", "value1") + .Add("Header2", null) + .Add("Header3", "value3") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(2)); + Assert.That(headers.ContainsKey("Header1"), Is.True); + Assert.That(headers.ContainsKey("Header2"), Is.False); + Assert.That(headers.ContainsKey("Header3"), Is.True); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_OverwritesExistingHeader() + { + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") + .Add("Content-Type", "application/xml") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_MergesExistingHeaders() + { + var existingHeaders = new Headers( + new Dictionary { { "Header1", "value1" }, { "Header2", "value2" } } + ); + + var result = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result["Header1"], Is.EqualTo("value1")); + Assert.That(result["Header2"], Is.EqualTo("value2")); + Assert.That(result["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_OverwritesExistingHeaders() + { + var existingHeaders = new Headers( + new Dictionary { { "Header1", "override" } } + ); + + var result = await new HeadersBuilder.Builder() + .Add("Header1", "original") + .Add("Header2", "keep") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result["Header1"], Is.EqualTo("override")); + Assert.That(result["Header2"], Is.EqualTo("keep")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_NullHeadersIgnored() + { + var result = await new HeadersBuilder.Builder() + .Add("Header1", "value1") + .Add((Headers?)null) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result["Header1"], Is.EqualTo("value1")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_AddsHeaders() + { + var additionalHeaders = new List> + { + new("Header1", "value1"), + new("Header2", "value2"), + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(additionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + Assert.That(headers["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_IgnoresNullValues() + { + var additionalHeaders = new List> + { + new("Header1", "value1"), + new("Header2", null), // Should be ignored + }; + + var headers = await new HeadersBuilder.Builder() + .Add(additionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers.ContainsKey("Header2"), Is.False); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_DictionaryOverload_AddsHeaders() + { + var dict = new Dictionary + { + { "Header1", "value1" }, + { "Header2", "value2" }, + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Header3", "value3") + .Add(dict) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(3)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + Assert.That(headers["Header3"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task EmptyBuilder_ReturnsEmptyHeaders() + { + var headers = await new HeadersBuilder.Builder().BuildAsync().ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task OnlyNullValues_ReturnsEmptyHeaders() + { + var headers = await new HeadersBuilder.Builder() + .Add("Header1", null) + .Add("Header2", null) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task ComplexMergingScenario() + { + // Simulates real SDK usage: endpoint headers + client headers + request options + var clientHeaders = new Headers( + new Dictionary + { + { "X-Client-Version", "1.0.0" }, + { "User-Agent", "MyClient/1.0" }, + } + ); + + var clientAdditionalHeaders = new List> + { + new("X-Custom-Header", "custom-value"), + }; + + var requestOptionsHeaders = new Headers( + new Dictionary + { + { "Authorization", "Bearer user-token" }, + { "User-Agent", "MyClient/2.0" }, // Override + } + ); + + var requestAdditionalHeaders = new List> + { + new("X-Request-ID", "req-123"), + new("X-Custom-Header", "overridden-value"), // Override + }; + + var headers = await new HeadersBuilder.Builder() + .Add("Content-Type", "application/json") // Endpoint header + .Add("X-Endpoint-ID", "endpoint-1") + .Add(clientHeaders) + .Add(clientAdditionalHeaders) + .Add(requestOptionsHeaders) + .Add(requestAdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + + // Verify precedence + Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); + Assert.That(headers["X-Endpoint-ID"], Is.EqualTo("endpoint-1")); + Assert.That(headers["X-Client-Version"], Is.EqualTo("1.0.0")); + Assert.That(headers["User-Agent"], Is.EqualTo("MyClient/2.0")); // Overridden + Assert.That(headers["Authorization"], Is.EqualTo("Bearer user-token")); + Assert.That(headers["X-Request-ID"], Is.EqualTo("req-123")); + Assert.That(headers["X-Custom-Header"], Is.EqualTo("overridden-value")); // Overridden + } + + [Test] + public async global::System.Threading.Tasks.Task Builder_WithCapacity() + { + // Test that capacity constructor works without errors + var headers = await new HeadersBuilder.Builder(capacity: 10) + .Add("Header1", "value1") + .Add("Header2", "value2") + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(2)); + Assert.That(headers["Header1"], Is.EqualTo("value1")); + Assert.That(headers["Header2"], Is.EqualTo("value2")); + } + + [Test] + public async global::System.Threading.Tasks.Task Add_HeadersOverload_ResolvesDynamicHeaderValues() + { + // Test that BuildAsync properly resolves HeaderValue instances + var existingHeaders = new Headers(); + existingHeaders["DynamicHeader"] = + (Func>)( + () => global::System.Threading.Tasks.Task.FromResult("dynamic-value") + ); + + var result = await new HeadersBuilder.Builder() + .Add("StaticHeader", "static-value") + .Add(existingHeaders) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result["StaticHeader"], Is.EqualTo("static-value")); + Assert.That(result["DynamicHeader"], Is.EqualTo("dynamic-value")); + } + + [Test] + public async global::System.Threading.Tasks.Task MultipleSyncAdds() + { + var headers1 = new Headers(new Dictionary { { "H1", "v1" } }); + var headers2 = new Headers(new Dictionary { { "H2", "v2" } }); + var headers3 = new Headers(new Dictionary { { "H3", "v3" } }); + + var result = await new HeadersBuilder.Builder() + .Add(headers1) + .Add(headers2) + .Add(headers3) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result["H1"], Is.EqualTo("v1")); + Assert.That(result["H2"], Is.EqualTo("v2")); + Assert.That(result["H3"], Is.EqualTo("v3")); + } + + [Test] + public async global::System.Threading.Tasks.Task PrecedenceOrder_LatestWins() + { + // Test that later operations override earlier ones + var headers1 = new Headers(new Dictionary { { "Key", "value1" } }); + var headers2 = new Headers(new Dictionary { { "Key", "value2" } }); + var additional = new List> { new("Key", "value3") }; + + var result = await new HeadersBuilder.Builder() + .Add("Key", "value0") + .Add(headers1) + .Add(headers2) + .Add(additional) + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(result["Key"], Is.EqualTo("value3")); + } + + [Test] + public async global::System.Threading.Tasks.Task CaseInsensitiveKeys() + { + // Test that header keys are case-insensitive + var headers = await new HeadersBuilder.Builder() + .Add("content-type", "application/json") + .Add("Content-Type", "application/xml") // Should overwrite + .BuildAsync() + .ConfigureAwait(false); + + Assert.That(headers.Count, Is.EqualTo(1)); + Assert.That(headers["content-type"], Is.EqualTo("application/xml")); + Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); + Assert.That(headers["CONTENT-TYPE"], Is.EqualTo("application/xml")); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs new file mode 100644 index 000000000000..a12183113312 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs @@ -0,0 +1,365 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class AdditionalPropertiesTests +{ + [Test] + public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); + Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); + }); + } + + [Test] + public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecord + { + Id = "1", + AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.Id, Is.EqualTo("1")); + Assert.That( + deserializedRecord.AdditionalProperties["category"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), + Is.EqualTo("fiction") + ); + Assert.That( + deserializedRecord.AdditionalProperties["title"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() + { + // Arrange + var extensionData = new Dictionary + { + ["key1"] = JsonUtils.SerializeToElement("value1"), + ["key2"] = JsonUtils.SerializeToElement(123), + }; + var readOnlyProps = new ReadOnlyAdditionalProperties(); + readOnlyProps.CopyFromExtensionData(extensionData); + + // Act & Assert + Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); + Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); + } + + [Test] + public void AdditionalProperties_ShouldBehaveAsDictionary() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + additionalProps["key3"] = true; + + // Assert + Assert.Multiple(() => + { + Assert.That(additionalProps["key1"], Is.EqualTo("value1")); + Assert.That(additionalProps["key2"], Is.EqualTo(123)); + Assert.That((bool)additionalProps["key3"]!, Is.True); + Assert.That(additionalProps.Count, Is.EqualTo(3)); + }); + } + + [Test] + public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + var jsonObject = additionalProps.ToJsonObject(); + + Assert.Multiple(() => + { + // Assert + Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); + Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); + }); + } + + [Test] + public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + var record = JsonUtils.Deserialize(json); + + // Act + record.AdditionalProperties["category"] = "non-fiction"; + + // Assert + Assert.Multiple(() => + { + Assert.That(record, Is.Not.Null); + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); + Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); + Assert.That( + ((JsonElement)record.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": 42, + "extra2": 99 + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithInts + { + AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": { "key1": true, "key2": false }, + "extra2": { "key3": true } + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithDictionaries + { + AdditionalProperties = + { + ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, + ["extra2"] = new Dictionary { { "key3", true } }, + }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + private record Record : IJsonOnDeserialized + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithInts : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithDictionaries : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties< + Dictionary + > AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties> AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs new file mode 100644 index 000000000000..c0f258680b78 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs @@ -0,0 +1,100 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateOnlyJsonTests +{ + [Test] + public void SerializeDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly? dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly? expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateOnlyKey() + { + var key = new DateOnly(2023, 10, 5); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateOnlyKey() + { + var json = """ + { + "2023-10-05": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateOnly(2023, 10, 5); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs new file mode 100644 index 000000000000..1dde45a8e939 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs @@ -0,0 +1,134 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateTimeJsonTests +{ + [Test] + public void SerializeDateTime_ShouldMatchExpectedFormat() + { + (DateTime dateTime, string expected)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + foreach (var (dateTime, expected) in testCases) + { + var json = JsonUtils.Serialize(dateTime); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateTime_ShouldMatchExpectedDateTime() + { + (DateTime expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateTime_ShouldMatchExpectedFormat() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateTimeKey() + { + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateTimeKey() + { + var json = """ + { + "2023-10-05T14:30:00.000Z": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs new file mode 100644 index 000000000000..969acd620998 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs @@ -0,0 +1,160 @@ +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class JsonAccessAttributeTests +{ + private class MyClass + { + [JsonPropertyName("read_only_prop")] + [JsonAccess(JsonAccessType.ReadOnly)] + public string? ReadOnlyProp { get; set; } + + [JsonPropertyName("write_only_prop")] + [JsonAccess(JsonAccessType.WriteOnly)] + public string? WriteOnlyProp { get; set; } + + [JsonPropertyName("normal_prop")] + public string? NormalProp { get; set; } + + [JsonPropertyName("read_only_nullable_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable? ReadOnlyNullableList { get; set; } + + [JsonPropertyName("read_only_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable ReadOnlyList { get; set; } = []; + + [JsonPropertyName("write_only_nullable_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable? WriteOnlyNullableList { get; set; } + + [JsonPropertyName("write_only_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable WriteOnlyList { get; set; } = []; + + [JsonPropertyName("normal_list")] + public IEnumerable NormalList { get; set; } = []; + + [JsonPropertyName("normal_nullable_list")] + public IEnumerable? NullableNormalList { get; set; } + } + + [Test] + public void JsonAccessAttribute_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "write_only_prop": "write", + "normal_prop": "normal_prop", + "read_only_nullable_list": ["item1", "item2"], + "read_only_list": ["item3", "item4"], + "write_only_nullable_list": ["item5", "item6"], + "write_only_list": ["item7", "item8"], + "normal_list": ["normal1", "normal2"], + "normal_nullable_list": ["normal1", "normal2"] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // String properties + Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); + Assert.That(obj.WriteOnlyProp, Is.Null); + Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); + + // List properties - read only + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Not.Null); + Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); + Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); + Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); + + var readOnlyList = obj.ReadOnlyList.ToArray(); + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Has.Length.EqualTo(2)); + Assert.That(readOnlyList[0], Is.EqualTo("item3")); + Assert.That(readOnlyList[1], Is.EqualTo("item4")); + + // List properties - write only + Assert.That(obj.WriteOnlyNullableList, Is.Null); + Assert.That(obj.WriteOnlyList, Is.Not.Null); + Assert.That(obj.WriteOnlyList, Is.Empty); + + // Normal list property + var normalList = obj.NormalList.ToArray(); + Assert.That(normalList, Is.Not.Null); + Assert.That(normalList, Has.Length.EqualTo(2)); + Assert.That(normalList[0], Is.EqualTo("normal1")); + Assert.That(normalList[1], Is.EqualTo("normal2")); + }); + + // Set up values for serialization + obj.WriteOnlyProp = "write"; + obj.NormalProp = "new_value"; + obj.WriteOnlyNullableList = new List { "write1", "write2" }; + obj.WriteOnlyList = new List { "write3", "write4" }; + obj.NormalList = new List { "new_normal" }; + obj.NullableNormalList = new List { "new_normal" }; + + var serializedJson = JsonUtils.Serialize(obj); + const string expectedJson = """ + { + "write_only_prop": "write", + "normal_prop": "new_value", + "write_only_nullable_list": [ + "write1", + "write2" + ], + "write_only_list": [ + "write3", + "write4" + ], + "normal_list": [ + "new_normal" + ], + "normal_nullable_list": [ + "new_normal" + ] + } + """; + Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); + } + + [Test] + public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "normal_prop": "normal_prop", + "read_only_nullable_list": null, + "read_only_list": [] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // Read-only nullable list should be null when JSON contains null + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Null); + + // Read-only non-nullable list should never be null, but empty when JSON contains null + var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Is.Empty); + }); + + // Serialize and verify read-only lists are not included + var serializedJson = JsonUtils.Serialize(obj); + Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); + Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); + Assert.That(serializedJson, Does.Not.Contain("read_only_list")); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs new file mode 100644 index 000000000000..493d8e99c329 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs @@ -0,0 +1,658 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class QueryStringBuilderTests +{ + [Test] + public void Build_SimpleParameters() + { + var parameters = new List> + { + new("name", "John Doe"), + new("age", "30"), + new("city", "New York"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?name=John%20Doe&age=30&city=New%20York")); + } + + [Test] + public void Build_EmptyList_ReturnsEmptyString() + { + var parameters = new List>(); + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Build_SpecialCharacters() + { + var parameters = new List> + { + new("email", "test@example.com"), + new("url", "https://example.com/path?query=value"), + new("special", "a+b=c&d"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That( + result, + Is.EqualTo( + "?email=test@example.com&url=https://example.com/path?query=value&special=a%2Bb=c%26d" + ) + ); + } + + [Test] + public void Build_UnicodeCharacters() + { + var parameters = new List> { new("greeting", "Hello 世界") }; + + var result = QueryStringBuilder.Build(parameters); + + // Verify the Chinese characters are properly UTF-8 encoded + Assert.That(result, Does.StartWith("?greeting=Hello%20")); + Assert.That(result, Does.Contain("%E4%B8%96%E7%95%8C")); // 世界 + } + + [Test] + public void Build_SessionSettings_DeepObject() + { + // Simulate session settings with nested properties + var sessionSettings = new + { + custom_session_id = "my-custom-session-id", + system_prompt = "You are a helpful assistant", + variables = new Dictionary + { + { "userName", "John" }, + { "userAge", 30 }, + { "isPremium", true }, + }, + }; + + // Build query parameters list + var queryParams = new List> { new("api_key", "test_key_123") }; + + // Add session_settings with prefix using the new overload + queryParams.AddRange( + QueryStringConverter.ToDeepObject("session_settings", sessionSettings) + ); + + var result = QueryStringBuilder.Build(queryParams); + + // Verify the result contains properly formatted deep object notation + // Note: Square brackets are URL-encoded as %5B and %5D + Assert.That(result, Does.StartWith("?api_key=test_key_123")); + Assert.That( + result, + Does.Contain("session_settings%5Bcustom_session_id%5D=my-custom-session-id") + ); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20a%20helpful%20assistant") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BisPremium%5D=true")); + + // Verify it's NOT JSON encoded (no braces or quotes in the original format) + Assert.That(result, Does.Not.Contain("%7B%22")); // Not {" sequence + } + + [Test] + public void Build_ChatApiLikeParameters() + { + // Simulate what ChatApi constructor does + var sessionSettings = new + { + system_prompt = "You are helpful", + variables = new Dictionary { { "name", "Alice" } }, + }; + + var queryParams = new List>(); + + // Simple parameters + var simpleParams = new Dictionary + { + { "access_token", "token123" }, + { "config_id", "config456" }, + { "api_key", "key789" }, + }; + queryParams.AddRange(QueryStringConverter.ToExplodedForm(simpleParams)); + + // Session settings as deep object with prefix + queryParams.AddRange( + QueryStringConverter.ToDeepObject("session_settings", sessionSettings) + ); + + var result = QueryStringBuilder.Build(queryParams); + + // Verify structure (square brackets are URL-encoded) + Assert.That(result, Does.StartWith("?")); + Assert.That(result, Does.Contain("access_token=token123")); + Assert.That(result, Does.Contain("config_id=config456")); + Assert.That(result, Does.Contain("api_key=key789")); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); + } + + [Test] + public void Build_ReservedCharacters_NotEncoded() + { + var parameters = new List> + { + new("path", "some-path"), + new("id", "123-456_789.test~value"), + }; + + var result = QueryStringBuilder.Build(parameters); + + // Safe query characters include RFC 3986 unreserved + sub-delimiters (except & = +) + : @ / + Assert.That(result, Is.EqualTo("?path=some-path&id=123-456_789.test~value")); + } + + [Test] + public void Builder_Add_SimpleParameters() + { + var result = new QueryStringBuilder.Builder() + .Add("name", "John Doe") + .Add("age", 30) + .Add("active", true) + .Build(); + + Assert.That(result, Does.Contain("name=John%20Doe")); + Assert.That(result, Does.Contain("age=30")); + Assert.That(result, Does.Contain("active=true")); + } + + [Test] + public void Builder_Add_NullValuesIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("name", "John") + .Add("middle", null) + .Add("age", 30) + .Build(); + + Assert.That(result, Does.Contain("name=John")); + Assert.That(result, Does.Contain("age=30")); + Assert.That(result, Does.Not.Contain("middle")); + } + + [Test] + public void Builder_AddDeepObject_WithPrefix() + { + var settings = new + { + custom_session_id = "id-123", + system_prompt = "You are helpful", + variables = new { name = "Alice", age = 25 }, + }; + + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddDeepObject("session_settings", settings) + .Build(); + + Assert.That(result, Does.Contain("api_key=key123")); + Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=id-123")); + Assert.That( + result, + Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") + ); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bage%5D=25")); + } + + [Test] + public void Builder_AddDeepObject_NullIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddDeepObject("settings", null) + .Build(); + + Assert.That(result, Is.EqualTo("?api_key=key123")); + Assert.That(result, Does.Not.Contain("settings")); + } + + [Test] + public void Builder_AddExploded_WithPrefix() + { + var filter = new { status = "active", type = "user" }; + + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddExploded("filter", filter) + .Build(); + + Assert.That(result, Does.Contain("api_key=key123")); + Assert.That(result, Does.Contain("filter%5Bstatus%5D=active")); + Assert.That(result, Does.Contain("filter%5Btype%5D=user")); + } + + [Test] + public void Builder_AddExploded_NullIgnored() + { + var result = new QueryStringBuilder.Builder() + .Add("api_key", "key123") + .AddExploded("filter", null) + .Build(); + + Assert.That(result, Is.EqualTo("?api_key=key123")); + Assert.That(result, Does.Not.Contain("filter")); + } + + [Test] + public void Builder_WithCapacity() + { + // Test that capacity constructor works without errors + var result = new QueryStringBuilder.Builder(capacity: 10) + .Add("param1", "value1") + .Add("param2", "value2") + .Build(); + + Assert.That(result, Does.Contain("param1=value1")); + Assert.That(result, Does.Contain("param2=value2")); + } + + [Test] + public void Builder_ChatApiLikeUsage() + { + // Simulate real usage from ChatApi + var sessionSettings = new + { + custom_session_id = "session-123", + variables = new Dictionary + { + { "userName", "John" }, + { "userAge", 30 }, + }, + }; + + var result = new QueryStringBuilder.Builder(capacity: 16) + .Add("access_token", "token123") + .Add("allow_connection", true) + .Add("config_id", "config456") + .Add("api_key", "key789") + .AddDeepObject("session_settings", sessionSettings) + .Build(); + + Assert.That(result, Does.StartWith("?")); + Assert.That(result, Does.Contain("access_token=token123")); + Assert.That(result, Does.Contain("allow_connection=true")); + Assert.That(result, Does.Contain("config_id=config456")); + Assert.That(result, Does.Contain("api_key=key789")); + Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=session-123")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); + Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); + } + + [Test] + public void Builder_EmptyBuilder_ReturnsEmptyString() + { + var result = new QueryStringBuilder.Builder().Build(); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Builder_OnlyNullValues_ReturnsEmptyString() + { + var result = new QueryStringBuilder.Builder() + .Add("param1", null) + .Add("param2", null) + .AddDeepObject("settings", null) + .Build(); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Builder_Set_OverridesSingleValue() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Set("foo", "override") + .Build(); + + Assert.That(result, Is.EqualTo("?foo=override")); + } + + [Test] + public void Builder_Set_OverridesMultipleValues() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "value1") + .Add("foo", "value2") + .Set("foo", "override") + .Build(); + + Assert.That(result, Is.EqualTo("?foo=override")); + } + + [Test] + public void Builder_Set_WithArray_CreatesMultipleParameters() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Set("foo", new[] { "value1", "value2" }) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value1&foo=value2")); + } + + [Test] + public void Builder_Set_WithNull_RemovesParameter() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "original") + .Add("bar", "keep") + .Set("foo", null) + .Build(); + + Assert.That(result, Is.EqualTo("?bar=keep")); + } + + [Test] + public void Builder_MergeAdditional_WithSingleValues() + { + var additional = new List> + { + new("foo", "bar"), + new("baz", "qux"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("existing", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("existing=value")); + Assert.That(result, Does.Contain("foo=bar")); + Assert.That(result, Does.Contain("baz=qux")); + } + + [Test] + public void Builder_MergeAdditional_WithDuplicateKeys_CreatesList() + { + var additional = new List> + { + new("foo", "bar1"), + new("foo", "bar2"), + new("baz", "qux"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("existing", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("existing=value")); + Assert.That(result, Does.Contain("foo=bar1")); + Assert.That(result, Does.Contain("foo=bar2")); + Assert.That(result, Does.Contain("baz=qux")); + } + + [Test] + public void Builder_MergeAdditional_OverridesExistingParameters() + { + var additional = new List> { new("foo", "override") }; + + var result = new QueryStringBuilder.Builder() + .Add("foo", "original1") + .Add("foo", "original2") + .Add("bar", "keep") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("bar=keep")); + Assert.That(result, Does.Contain("foo=override")); + Assert.That(result, Does.Not.Contain("original1")); + Assert.That(result, Does.Not.Contain("original2")); + } + + [Test] + public void Builder_MergeAdditional_WithDuplicates_OverridesExisting() + { + var additional = new List> + { + new("foo", "new1"), + new("foo", "new2"), + new("foo", "new3"), + }; + + var result = new QueryStringBuilder.Builder() + .Add("foo", "original1") + .Add("foo", "original2") + .Add("bar", "keep") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Does.Contain("bar=keep")); + Assert.That(result, Does.Contain("foo=new1")); + Assert.That(result, Does.Contain("foo=new2")); + Assert.That(result, Does.Contain("foo=new3")); + Assert.That(result, Does.Not.Contain("original1")); + Assert.That(result, Does.Not.Contain("original2")); + } + + [Test] + public void Builder_MergeAdditional_WithNull_NoOp() + { + var result = new QueryStringBuilder.Builder() + .Add("foo", "value") + .MergeAdditional(null) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value")); + } + + [Test] + public void Builder_MergeAdditional_WithEmptyList_NoOp() + { + var additional = new List>(); + + var result = new QueryStringBuilder.Builder() + .Add("foo", "value") + .MergeAdditional(additional) + .Build(); + + Assert.That(result, Is.EqualTo("?foo=value")); + } + + [Test] + public void Builder_MergeAdditional_RealWorldScenario() + { + // SDK generates foo=foo1&foo=foo2 + var builder = new QueryStringBuilder.Builder() + .Add("foo", "foo1") + .Add("foo", "foo2") + .Add("bar", "baz"); + + // User provides foo=override in AdditionalQueryParameters + var additional = new List> { new("foo", "override") }; + + var result = builder.MergeAdditional(additional).Build(); + + // Result should be foo=override&bar=baz (user overrides SDK) + Assert.That(result, Does.Contain("bar=baz")); + Assert.That(result, Does.Contain("foo=override")); + Assert.That(result, Does.Not.Contain("foo1")); + Assert.That(result, Does.Not.Contain("foo2")); + } + + [Test] + public void Builder_MergeAdditional_UserProvidesMultipleValues() + { + // SDK generates no foo parameter + var builder = new QueryStringBuilder.Builder().Add("bar", "baz"); + + // User provides foo=bar1&foo=bar2 in AdditionalQueryParameters + var additional = new List> + { + new("foo", "bar1"), + new("foo", "bar2"), + }; + + var result = builder.MergeAdditional(additional).Build(); + + // Result should be bar=baz&foo=bar1&foo=bar2 + Assert.That(result, Does.Contain("bar=baz")); + Assert.That(result, Does.Contain("foo=bar1")); + Assert.That(result, Does.Contain("foo=bar2")); + } + + [Test] + public void Builder_Add_WithCollection_CreatesMultipleParameters() + { + var tags = new[] { "tag1", "tag2", "tag3" }; + var result = new QueryStringBuilder.Builder().Add("tag", tags).Build(); + + Assert.That(result, Does.Contain("tag=tag1")); + Assert.That(result, Does.Contain("tag=tag2")); + Assert.That(result, Does.Contain("tag=tag3")); + } + + [Test] + public void Builder_Add_WithList_CreatesMultipleParameters() + { + var ids = new List { 1, 2, 3 }; + var result = new QueryStringBuilder.Builder().Add("id", ids).Build(); + + Assert.That(result, Does.Contain("id=1")); + Assert.That(result, Does.Contain("id=2")); + Assert.That(result, Does.Contain("id=3")); + } + + [Test] + public void Builder_Set_WithCollection_ReplacesAllPreviousValues() + { + var result = new QueryStringBuilder.Builder() + .Add("id", 1) + .Add("id", 2) + .Set("id", new[] { 10, 20, 30 }) + .Build(); + + Assert.That(result, Does.Contain("id=10")); + Assert.That(result, Does.Contain("id=20")); + Assert.That(result, Does.Contain("id=30")); + // Check that old values are not present (use word boundaries to avoid false positives with id=10) + Assert.That(result, Does.Not.Contain("id=1&")); + Assert.That(result, Does.Not.Contain("id=2&")); + Assert.That(result, Does.Not.Contain("id=1?")); + Assert.That(result, Does.Not.Contain("id=2?")); + Assert.That(result, Does.Not.EndWith("id=1")); + Assert.That(result, Does.Not.EndWith("id=2")); + } + + [Test] + public void EncodePathSegment_UnreservedChars_NotEncoded() + { + var result = QueryStringBuilder.EncodePathSegment("hello-world_test.value~123"); + Assert.That(result, Is.EqualTo("hello-world_test.value~123")); + } + + [Test] + public void EncodePathSegment_SubDelimiters_NotEncoded() + { + // All sub-delimiters are safe in path segments per RFC 3986 + var result = QueryStringBuilder.EncodePathSegment("a!b$c&d'e(f)g*h+i,j;k=l"); + Assert.That(result, Is.EqualTo("a!b$c&d'e(f)g*h+i,j;k=l")); + } + + [Test] + public void EncodePathSegment_ColonAndAt_NotEncoded() + { + var result = QueryStringBuilder.EncodePathSegment("user@host:8080"); + Assert.That(result, Is.EqualTo("user@host:8080")); + } + + [Test] + public void EncodePathSegment_SlashAndQuestion_Encoded() + { + // "/" and "?" are NOT part of pchar, so they must be encoded in path segments + var result = QueryStringBuilder.EncodePathSegment("path/with?query"); + Assert.That(result, Is.EqualTo("path%2Fwith%3Fquery")); + } + + [Test] + public void EncodePathSegment_Space_Encoded() + { + var result = QueryStringBuilder.EncodePathSegment("hello world"); + Assert.That(result, Is.EqualTo("hello%20world")); + } + + [Test] + public void EncodePathSegment_EmptyAndNull() + { + Assert.That(QueryStringBuilder.EncodePathSegment(""), Is.EqualTo("")); + Assert.That(QueryStringBuilder.EncodePathSegment(null!), Is.Null); + } + + [Test] + public void Build_QueryKeyVsValue_DifferentEncoding() + { + // "=" is safe in query values but NOT in query keys + var parameters = new List> + { + new("key=with=equals", "value=with=equals"), + }; + + var result = QueryStringBuilder.Build(parameters); + + // Key: "=" must be encoded + // Value: "=" is safe (part of query value safe chars) + Assert.That(result, Is.EqualTo("?key%3Dwith%3Dequals=value=with=equals")); + } + + [Test] + public void Build_QueryValue_QuestionMarkNotEncoded() + { + // "?" is safe in both query keys and query values per RFC 3986 + var parameters = new List> { new("q?key", "is this?") }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?q?key=is%20this?")); + } + + [Test] + public void Build_QueryKey_PlusEncoded() + { + // "+" must be encoded in both query keys and query values + var parameters = new List> { new("a+b", "c+d") }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?a%2Bb=c%2Bd")); + } + + [Test] + public void Build_ODataFilter_DollarPreserved() + { + // "$" is safe in query keys (sub-delimiter), verifies OData-style parameters work + var parameters = new List> + { + new("$filter", "status eq 'active'"), + new("$top", "10"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Does.Contain("$filter=status%20eq%20'active'")); + Assert.That(result, Does.Contain("$top=10")); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs new file mode 100644 index 000000000000..06293e022863 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs @@ -0,0 +1,158 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class QueryStringConverterTests +{ + [Test] + public void ToQueryStringCollection_Form() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToForm(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates]", "39.78172,-89.65015"), + new("Tags", "Developer,Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_ExplodedForm() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToExplodedForm(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates]", "39.78172"), + new("Address[Coordinates]", "-89.65015"), + new("Tags", "Developer"), + new("Tags", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_DeepObject() + { + var obj = new + { + Name = "John", + Age = 30, + Address = new + { + Street = "123 Main St", + City = "Anytown", + Coordinates = new[] { 39.781721f, -89.650148f }, + }, + Tags = new[] { "Developer", "Blogger" }, + }; + var result = QueryStringConverter.ToDeepObject(obj); + var expected = new List> + { + new("Name", "John"), + new("Age", "30"), + new("Address[Street]", "123 Main St"), + new("Address[City]", "Anytown"), + new("Address[Coordinates][0]", "39.78172"), + new("Address[Coordinates][1]", "-89.65015"), + new("Tags[0]", "Developer"), + new("Tags[1]", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_OnString_ThrowsException() + { + var exception = Assert.Throws(() => + QueryStringConverter.ToForm("invalid") + ); + Assert.That( + exception.Message, + Is.EqualTo( + "Only objects can be converted to query string collections. Given type is String." + ) + ); + } + + [Test] + public void ToQueryStringCollection_OnArray_ThrowsException() + { + var exception = Assert.Throws(() => + QueryStringConverter.ToForm(Array.Empty()) + ); + Assert.That( + exception.Message, + Is.EqualTo( + "Only objects can be converted to query string collections. Given type is Array." + ) + ); + } + + [Test] + public void ToQueryStringCollection_DeepObject_WithPrefix() + { + var obj = new + { + custom_session_id = "my-id", + system_prompt = "You are helpful", + variables = new { name = "Alice", age = 25 }, + }; + var result = QueryStringConverter.ToDeepObject("session_settings", obj); + var expected = new List> + { + new("session_settings[custom_session_id]", "my-id"), + new("session_settings[system_prompt]", "You are helpful"), + new("session_settings[variables][name]", "Alice"), + new("session_settings[variables][age]", "25"), + }; + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void ToQueryStringCollection_ExplodedForm_WithPrefix() + { + var obj = new { Name = "John", Tags = new[] { "Developer", "Blogger" } }; + var result = QueryStringConverter.ToExplodedForm("user", obj); + var expected = new List> + { + new("user[Name]", "John"), + new("user[Tags]", "Developer"), + new("user[Tags]", "Blogger"), + }; + Assert.That(result, Is.EqualTo(expected)); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs new file mode 100644 index 000000000000..39ac12fc18b2 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs @@ -0,0 +1,1120 @@ +using global::System.Net.Http; +using global::System.Text; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; +using SystemTask = global::System.Threading.Tasks.Task; + +namespace SeedApi.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class MultipartFormTests +{ + private static SimpleObject _simpleObject = new(); + + private static string _simpleFormEncoded = + "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data,2023-10-01,12:00:00,01:00:00,1a1bb98f-47c6-407b-9481-78476affe52a,true,42,A"; + + private static string _simpleExplodedFormEncoded = + "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data&Values=2023-10-01&Values=12:00:00&Values=01:00:00&Values=1a1bb98f-47c6-407b-9481-78476affe52a&Values=true&Values=42&Values=A"; + + private static ComplexObject _complexObject = new(); + + private static string _complexJson = """ + { + "meta": "data", + "Nested": { + "foo": "value" + }, + "NestedDictionary": { + "key": { + "foo": "value" + } + }, + "ListOfObjects": [ + { + "foo": "value" + }, + { + "foo": "value2" + } + ], + "Date": "2023-10-01", + "Time": "12:00:00", + "Duration": "01:00:00", + "Id": "1a1bb98f-47c6-407b-9481-78476affe52a", + "IsActive": true, + "Count": 42, + "Initial": "A" + } + """; + + [Test] + public async SystemTask ShouldAddStringPart() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, partInput]); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddStringPart() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", null); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithNullsInList() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, null, partInput]); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringPart_WithContentType() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput, "text/xml"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringPart_WithContentTypeAndCharset() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringPart("string", partInput, "text/xml; charset=utf-8"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=string + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithContentType() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts("strings", [partInput, partInput], "text/xml"); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/xml + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddStringParts_WithContentTypeAndCharset() + { + const string partInput = "string content"; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddStringParts( + "strings", + [partInput, partInput], + "text/xml; charset=utf-8" + ); + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary} + Content-Type: text/xml; charset=utf-8 + Content-Disposition: form-data; name=strings + + {partInput} + --{boundary}-- + """; + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFileName() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithoutFileName() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", partInput); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithContentType() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter + { + Stream = partInput, + FileName = "test.txt", + ContentType = "text/plain", + }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithContentTypeAndCharset() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter + { + Stream = partInput, + FileName = "test.txt", + ContentType = "text/plain; charset=utf-8", + }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain; charset=utf-8 + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFallbackContentType() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "text/plain"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameter_WithFallbackContentTypeAndCharset() + { + var (partInput, partExpectedString) = GetFileParameterTestData(); + var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", file, "text/plain; charset=utf-8"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: text/plain; charset=utf-8 + Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt + + {partExpectedString} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameters() + { + var (partInput1, partExpectedString1) = GetFileParameterTestData(); + var (partInput2, partExpectedString2) = GetFileParameterTestData(); + var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; + var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterParts("file", [file1, file2]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt + + {partExpectedString1} + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt + + {partExpectedString2} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFileParameters_WithNullsInList() + { + var (partInput1, partExpectedString1) = GetFileParameterTestData(); + var (partInput2, partExpectedString2) = GetFileParameterTestData(); + var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; + var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterParts("file", [file1, null, file2]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt + + {partExpectedString1} + --{boundary} + Content-Type: application/octet-stream + Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt + + {partExpectedString2} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddFileParameter() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFileParameterPart("file", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonPart_WithComplexObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonPart("object", _complexObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=object + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonPart_WithComplexObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [_complexObject, _complexObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask GivenNull_ShouldNotAddJsonPart() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonPart("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [_complexObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/json + Content-Disposition: form-data; name=objects + + {_complexJson} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddJsonParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddJsonParts("objects", [new { }], "application/json-patch+json"); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $$""" + --{{boundary}} + Content-Type: application/json-patch+json + Content-Disposition: form-data; name=objects + + {} + --{{boundary}}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithSimpleObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart("object", _simpleObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=object + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithSimpleObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, _simpleObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddFormEncodedParts_WithNull() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddFormEncodedParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedPart_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedPart_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddFormEncodedParts_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObject() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart("object", _simpleObject); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=object + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObjectList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, _simpleObject]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNull() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart("object", null); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNullsInList() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, null]); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + {EscapeFormEncodedString(_simpleExplodedFormEncoded)} + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedPart( + "objects", + new { foo = "bar" }, + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentType() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + [Test] + public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentTypeAndCharset() + { + var multipartFormRequest = CreateMultipartFormRequest(); + multipartFormRequest.AddExplodedFormEncodedParts( + "objects", + [new { foo = "bar" }], + "application/x-www-form-urlencoded; charset=utf-8" + ); + + var httpContent = multipartFormRequest.CreateContent(); + Assert.That(httpContent, Is.InstanceOf()); + var multipartContent = (MultipartFormDataContent)httpContent; + + var boundary = GetBoundary(multipartContent); + var expected = $""" + --{boundary} + Content-Type: application/x-www-form-urlencoded; charset=utf-8 + Content-Disposition: form-data; name=objects + + foo=bar + --{boundary}-- + """; + + var actual = await multipartContent.ReadAsStringAsync(); + Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); + } + + private static string EscapeFormEncodedString(string input) + { + return string.Join( + "&", + input + .Split('&') + .Select(x => x.Split('=')) + .Select(x => $"{Uri.EscapeDataString(x[0])}={Uri.EscapeDataString(x[1])}") + ); + } + + private static string GetBoundary(MultipartFormDataContent content) + { + return content + .Headers.ContentType?.Parameters.Single(p => + p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) + ) + .Value?.Trim('"') ?? throw new global::System.Exception("Boundary not found"); + } + + private static SeedApi.Core.MultipartFormRequest CreateMultipartFormRequest() + { + return new SeedApi.Core.MultipartFormRequest + { + BaseUrl = "https://localhost", + Method = HttpMethod.Post, + Path = "", + }; + } + + private static (Stream partInput, string partExpectedString) GetFileParameterTestData() + { + const string partExpectedString = "file content"; + var partInput = new MemoryStream(Encoding.Default.GetBytes(partExpectedString)); + return (partInput, partExpectedString); + } + + private class SimpleObject + { + [JsonPropertyName("meta")] + public string Meta { get; set; } = "data"; + public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); + public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); + public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); + public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); + public bool IsActive { get; set; } = true; + public int Count { get; set; } = 42; + public char Initial { get; set; } = 'A'; + public IEnumerable Values { get; set; } = + [ + "data", + DateOnly.Parse("2023-10-01"), + TimeOnly.Parse("12:00:00"), + TimeSpan.FromHours(1), + Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), + true, + 42, + 'A', + ]; + } + + private class ComplexObject + { + [JsonPropertyName("meta")] + public string Meta { get; set; } = "data"; + + public object Nested { get; set; } = new { foo = "value" }; + + public Dictionary NestedDictionary { get; set; } = + new() { { "key", new { foo = "value" } } }; + + public IEnumerable ListOfObjects { get; set; } = + new List { new { foo = "value" }, new { foo = "value2" } }; + + public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); + public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); + public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); + public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); + public bool IsActive { get; set; } = true; + public int Count { get; set; } = 42; + public char Initial { get; set; } = 'A'; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs new file mode 100644 index 000000000000..f4edf9ef52a6 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs @@ -0,0 +1,108 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class QueryParameterTests +{ + [Test] + public void QueryParameters_BasicParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .Add("baz", "qux") + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar&baz=qux")); + } + + [Test] + public void QueryParameters_SpecialCharacterEscaping() + { + var queryString = new QueryStringBuilder.Builder() + .Add("email", "bob+test@example.com") + .Add("%Complete", "100") + .Add("space test", "hello world") + .Build(); + + Assert.That(queryString, Does.Contain("email=bob%2Btest@example.com")); + Assert.That(queryString, Does.Contain("%25Complete=100")); + Assert.That(queryString, Does.Contain("space%20test=hello%20world")); + } + + [Test] + public void QueryParameters_MergeAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("sdk", "param") + .MergeAdditional(new List> { new("user", "value") }) + .Build(); + + Assert.That(queryString, Does.Contain("sdk=param")); + Assert.That(queryString, Does.Contain("user=value")); + } + + [Test] + public void QueryParameters_AdditionalOverridesSdk() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "sdk_value") + .MergeAdditional(new List> { new("foo", "user_override") }) + .Build(); + + Assert.That(queryString, Does.Contain("foo=user_override")); + Assert.That(queryString, Does.Not.Contain("sdk_value")); + } + + [Test] + public void QueryParameters_AdditionalMultipleValues() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "sdk_value") + .MergeAdditional( + new List> { new("foo", "user1"), new("foo", "user2") } + ) + .Build(); + + Assert.That(queryString, Does.Contain("foo=user1")); + Assert.That(queryString, Does.Contain("foo=user2")); + Assert.That(queryString, Does.Not.Contain("sdk_value")); + } + + [Test] + public void QueryParameters_OnlyAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .MergeAdditional( + new List> { new("foo", "bar"), new("baz", "qux") } + ) + .Build(); + + Assert.That(queryString, Does.Contain("foo=bar")); + Assert.That(queryString, Does.Contain("baz=qux")); + } + + [Test] + public void QueryParameters_EmptyAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .MergeAdditional(new List>()) + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar")); + } + + [Test] + public void QueryParameters_NullAdditionalParameters() + { + var queryString = new QueryStringBuilder.Builder() + .Add("foo", "bar") + .MergeAdditional(null) + .Build(); + + Assert.That(queryString, Is.EqualTo("?foo=bar")); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs new file mode 100644 index 000000000000..22e8103847cb --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs @@ -0,0 +1,406 @@ +using global::System.Net.Http; +using global::System.Text.Json; +using NUnit.Framework; +using SeedApi.Core; +using WireMock.Server; +using SystemTask = global::System.Threading.Tasks.Task; +using WireMockRequest = WireMock.RequestBuilders.Request; +using WireMockResponse = WireMock.ResponseBuilders.Response; + +namespace SeedApi.Test.Core.RawClientTests; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class RetriesTests +{ + private const int MaxRetries = 3; + private WireMockServer _server; + private HttpClient _httpClient; + private RawClient _rawClient; + private string _baseUrl; + + [SetUp] + public void SetUp() + { + _server = WireMockServer.Start(); + _baseUrl = _server.Url ?? ""; + _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) }; + _rawClient = new RawClient( + new ClientOptions { HttpClient = _httpClient, MaxRetries = MaxRetries } + ) + { + BaseRetryDelay = 0, + }; + } + + [Test] + [TestCase(408)] + [TestCase(429)] + [TestCase(500)] + [TestCase(504)] + public async SystemTask SendRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Server Error") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + + Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); + } + } + + [Test] + [TestCase(400)] + [TestCase(409)] + public async SystemTask SendRequestAsync_ShouldRetry_OnNonRetryableStatusCodes(int statusCode) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure")); + + var request = new JsonRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + Body = new { }, + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(statusCode)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldNotRetry_WithStreamRequest() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); + + var request = new StreamRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + Body = new MemoryStream(), + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(429)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest_WithStream() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); + + var request = new SeedApi.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddFileParameterPart("file", new MemoryStream()); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(429)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Failure")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_WithoutStream() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(429)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WhenStateIs("Server Error") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(429)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("Retry") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new SeedApi.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddJsonPart("object", new { }); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithSecondsValue() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfter") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse.Create().WithStatusCode(429).WithHeader("Retry-After", "1") + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfter") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithHttpDateValue() + { + var retryAfterDate = DateTimeOffset.UtcNow.AddSeconds(1).ToString("R"); + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfterDate") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse + .Create() + .WithStatusCode(429) + .WithHeader("Retry-After", retryAfterDate) + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RetryAfterDate") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldRespectXRateLimitResetHeader() + { + var resetTime = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds().ToString(); + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RateLimitReset") + .WillSetStateTo("Success") + .RespondWith( + WireMockResponse + .Create() + .WithStatusCode(429) + .WithHeader("X-RateLimit-Reset", resetTime) + ); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("RateLimitReset") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new EmptyRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.Multiple(() => + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + }); + } + + [Test] + public async SystemTask SendRequestAsync_ShouldPreserveJsonBody_OnRetry() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryWithBody") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(500)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryWithBody") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new JsonRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + Body = new { key = "value" }, + }; + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + + // Verify the retried request preserved the JSON body (compare parsed to ignore formatting differences) + var retriedEntry = _server.LogEntries.ElementAt(1); + using var actualJson = JsonDocument.Parse(retriedEntry.RequestMessage.Body!); + Assert.That(actualJson.RootElement.GetProperty("key").GetString(), Is.EqualTo("value")); + } + } + + [Test] + public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryMultipart") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(500)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) + .InScenario("RetryMultipart") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new SeedApi.Core.MultipartFormRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Post, + Path = "/test", + }; + request.AddJsonPart("object", new { key = "value" }); + + var response = await _rawClient.SendRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(content, Is.EqualTo("Success")); + Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); + + // Verify the retried request preserved the multipart body (check key/value presence to ignore formatting differences) + var retriedEntry = _server.LogEntries.ElementAt(1); + Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"key\"")); + Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"value\"")); + } + } + + [TearDown] + public void TearDown() + { + _server.Dispose(); + _httpClient.Dispose(); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs new file mode 100644 index 000000000000..27337ed343e8 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs @@ -0,0 +1,269 @@ +using global::System.Net; +using global::System.Net.Http.Headers; +using NUnit.Framework; +using SeedApi; +using SeedApi.Core; + +namespace SeedApi.Test.Core; + +[TestFixture] +public class WithRawResponseTests +{ + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_DirectAwait_ReturnsData() + { + // Arrange + var expectedData = "test-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act + var result = await task; + + // Assert + Assert.That(result, Is.EqualTo(expectedData)); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_WithRawResponse_ReturnsDataAndMetadata() + { + // Arrange + var expectedData = "test-data"; + var expectedStatusCode = HttpStatusCode.Created; + var task = CreateWithRawResponseTask(expectedData, expectedStatusCode); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.Data, Is.EqualTo(expectedData)); + Assert.That(result.RawResponse.StatusCode, Is.EqualTo(expectedStatusCode)); + Assert.That(result.RawResponse.Url, Is.Not.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_CaseInsensitive() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Request-Id", "12345"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act & Assert + Assert.That(headers.TryGetValue("X-Request-Id", out var value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + + Assert.That(headers.TryGetValue("x-request-id", out value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + + Assert.That(headers.TryGetValue("X-REQUEST-ID", out value), Is.True); + Assert.That(value, Is.EqualTo("12345")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_ReturnsMultipleValues() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("Set-Cookie", new[] { "cookie1=value1", "cookie2=value2" }); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValues("Set-Cookie", out var values); + + // Assert + Assert.That(success, Is.True); + Assert.That(values, Is.Not.Null); + Assert.That(values!.Count(), Is.EqualTo(2)); + Assert.That(values, Does.Contain("cookie1=value1")); + Assert.That(values, Does.Contain("cookie2=value2")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_ContentType_ReturnsValue() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Content = new StringContent( + "{}", + global::System.Text.Encoding.UTF8, + "application/json" + ); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var contentType = headers.ContentType; + + // Assert + Assert.That(contentType, Is.Not.Null); + Assert.That(contentType, Does.Contain("application/json")); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_ContentLength_ReturnsValue() + { + // Arrange + var content = "test content"; + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Content = new StringContent(content); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var contentLength = headers.ContentLength; + + // Assert + Assert.That(contentLength, Is.Not.Null); + Assert.That(contentLength, Is.GreaterThan(0)); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_Contains_ReturnsTrueForExistingHeader() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Custom-Header", "value"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act & Assert + Assert.That(headers.Contains("X-Custom-Header"), Is.True); + Assert.That(headers.Contains("x-custom-header"), Is.True); + Assert.That(headers.Contains("NonExistent"), Is.False); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_Enumeration_IncludesAllHeaders() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + response.Headers.Add("X-Header-1", "value1"); + response.Headers.Add("X-Header-2", "value2"); + response.Content = new StringContent("test"); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var allHeaders = headers.ToList(); + + // Assert + Assert.That(allHeaders.Count, Is.GreaterThan(0)); + Assert.That(allHeaders.Any(h => h.Name == "X-Header-1"), Is.True); + Assert.That(allHeaders.Any(h => h.Name == "X-Header-2"), Is.True); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_ErrorStatusCode_StillReturnsMetadata() + { + // Arrange + var expectedData = "error-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.BadRequest); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.Data, Is.EqualTo(expectedData)); + Assert.That(result.RawResponse.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_Url_IsPreserved() + { + // Arrange + var expectedUrl = new Uri("https://api.example.com/users/123"); + var task = CreateWithRawResponseTask("data", HttpStatusCode.OK, expectedUrl); + + // Act + var result = await task.WithRawResponse(); + + // Assert + Assert.That(result.RawResponse.Url, Is.EqualTo(expectedUrl)); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_NonExistentHeader_ReturnsFalse() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValue("X-NonExistent", out var value); + + // Assert + Assert.That(success, Is.False); + Assert.That(value, Is.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_NonExistentHeader_ReturnsFalse() + { + // Arrange + using var response = CreateHttpResponse(HttpStatusCode.OK); + var headers = ResponseHeaders.FromHttpResponseMessage(response); + + // Act + var success = headers.TryGetValues("X-NonExistent", out var values); + + // Assert + Assert.That(success, Is.False); + Assert.That(values, Is.Null); + } + + [Test] + public async global::System.Threading.Tasks.Task WithRawResponseTask_ImplicitConversion_ToTask() + { + // Arrange + var expectedData = "test-data"; + var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act - implicitly convert to Task + global::System.Threading.Tasks.Task regularTask = task; + var result = await regularTask; + + // Assert + Assert.That(result, Is.EqualTo(expectedData)); + } + + [Test] + public void WithRawResponseTask_ImplicitConversion_AssignToTaskVariable() + { + // Arrange + var expectedData = "test-data"; + var wrappedTask = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); + + // Act - assign to Task variable + global::System.Threading.Tasks.Task regularTask = wrappedTask; + + // Assert + Assert.That(regularTask, Is.Not.Null); + Assert.That(regularTask, Is.InstanceOf>()); + } + + // Helper methods + + private static WithRawResponseTask CreateWithRawResponseTask( + T data, + HttpStatusCode statusCode, + Uri? url = null + ) + { + url ??= new Uri("https://api.example.com/test"); + using var httpResponse = CreateHttpResponse(statusCode); + httpResponse.RequestMessage = new HttpRequestMessage(HttpMethod.Get, url); + + var rawResponse = new RawResponse + { + StatusCode = statusCode, + Url = url, + Headers = ResponseHeaders.FromHttpResponseMessage(httpResponse), + }; + + var withRawResponse = new WithRawResponse { Data = data, RawResponse = rawResponse }; + + var task = global::System.Threading.Tasks.Task.FromResult(withRawResponse); + return new WithRawResponseTask(task); + } + + private static HttpResponseMessage CreateHttpResponse(HttpStatusCode statusCode) + { + return new HttpResponseMessage(statusCode) { Content = new StringContent("") }; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props new file mode 100644 index 000000000000..aac9b5020d80 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props @@ -0,0 +1,6 @@ + + diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj new file mode 100644 index 000000000000..a4dfe85227f6 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj @@ -0,0 +1,33 @@ + + + net9.0 + 12 + enable + enable + false + true + true + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs new file mode 100644 index 000000000000..18aaa67904d8 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs @@ -0,0 +1,6 @@ +using NUnit.Framework; + +namespace SeedApi.Test; + +[TestFixture] +public class TestClient; diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs new file mode 100644 index 000000000000..3f4ed8503ec1 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs @@ -0,0 +1,37 @@ +using NUnit.Framework; +using SeedApi; +using WireMock.Logging; +using WireMock.Server; +using WireMock.Settings; + +namespace SeedApi.Test.Unit.MockServer; + +public class BaseMockServerTest +{ + protected WireMockServer Server { get; set; } = null!; + + protected SeedApiClient Client { get; set; } = null!; + + protected RequestOptions RequestOptions { get; set; } = new(); + + [OneTimeSetUp] + public void GlobalSetup() + { + // Start the WireMock server + Server = WireMockServer.Start( + new WireMockServerSettings { Logger = new WireMockConsoleLogger() } + ); + + // Initialize the Client + Client = new SeedApiClient( + clientOptions: new ClientOptions { BaseUrl = Server.Urls[0], MaxRetries = 0 } + ); + } + + [OneTimeTearDown] + public void GlobalTeardown() + { + Server.Stop(); + Server.Dispose(); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs new file mode 100644 index 000000000000..053c0fd711a1 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs @@ -0,0 +1,92 @@ +using NUnit.Framework; +using SeedApi; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class CreateRuleTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string requestJson = """ + { + "name": "name", + "executionContext": "prod" + } + """; + + const string mockResponse = """ + { + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/rules") + .WithHeader("Content-Type", "application/json") + .UsingPost() + .WithBodyAsJson(requestJson) + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } + ); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string requestJson = """ + { + "name": "name", + "executionContext": "prod" + } + """; + + const string mockResponse = """ + { + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/rules") + .WithHeader("Content-Type", "application/json") + .UsingPost() + .WithBodyAsJson(requestJson) + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.CreateRuleAsync( + new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } + ); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs new file mode 100644 index 000000000000..6b0e6c1e74ac --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs @@ -0,0 +1,59 @@ +using NUnit.Framework; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class GetEntityTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "status": "active", + "id": "id", + "name": "name", + "summary": "summary" + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetEntityAsync(); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "name": "name", + "summary": "summary", + "id": "id", + "status": "active" + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetEntityAsync(); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs new file mode 100644 index 000000000000..c43f55c6dfcd --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs @@ -0,0 +1,63 @@ +using NUnit.Framework; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class GetOrganizationTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "name": "name", + "id": "id", + "metadata": { + "region": "region", + "tier": "tier" + } + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetOrganizationAsync(); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "metadata": { + "region": "region", + "tier": "tier" + }, + "id": "id", + "name": "name" + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.GetOrganizationAsync(); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs new file mode 100644 index 000000000000..2fd3294e9d26 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs @@ -0,0 +1,75 @@ +using NUnit.Framework; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class ListUsersTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "results": [ + { + "id": "id", + "email": "email" + }, + { + "id": "id", + "email": "email" + } + ], + "paging": { + "next": "next", + "previous": "previous" + } + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.ListUsersAsync(); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "email": "email" + } + ] + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.ListUsersAsync(); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs new file mode 100644 index 000000000000..54703790876e --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs @@ -0,0 +1,87 @@ +using NUnit.Framework; +using SeedApi; +using SeedApi.Test.Utils; + +namespace SeedApi.Test.Unit.MockServer; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class SearchRuleTypesTest : BaseMockServerTest +{ + [NUnit.Framework.Test] + public async Task MockServerTest_1() + { + const string mockResponse = """ + { + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ], + "paging": { + "next": "next", + "previous": "previous" + } + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/rule-types") + .WithParam("query", "query") + .UsingGet() + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.SearchRuleTypesAsync( + new SearchRuleTypesRequest { Query = "query" } + ); + JsonAssert.AreEqual(response, mockResponse); + } + + [NUnit.Framework.Test] + public async Task MockServerTest_2() + { + const string mockResponse = """ + { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + """; + + Server + .Given(WireMock.RequestBuilders.Request.Create().WithPath("/rule-types").UsingGet()) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); + JsonAssert.AreEqual(response, mockResponse); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs new file mode 100644 index 000000000000..3ac7e5310f95 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs @@ -0,0 +1,219 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; +using SeedApi; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle AdditionalProperties values. +/// +public static class AdditionalPropertiesComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their + /// serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) + { + constraint.Using( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + /// + /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing + /// their serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) + { + constraint.Using>( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => + JsonElementsAreEqual(x, y); + + private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) + { + if (x.ValueKind != y.ValueKind) + { + return false; + } + + return x.ValueKind switch + { + JsonValueKind.Object => CompareJsonObjects(x, y), + JsonValueKind.Array => CompareJsonArrays(x, y), + JsonValueKind.String => x.GetString() == y.GetString(), + JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), + JsonValueKind.True => true, + JsonValueKind.False => true, + JsonValueKind.Null => true, + _ => false, + }; + } + + private static bool CompareJsonObjects(JsonElement x, JsonElement y) + { + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + if (xProps.Count != yProps.Count) + { + return false; + } + + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + return false; + } + + if (!JsonElementsAreEqual(xProps[key], yProps[key])) + { + return false; + } + } + + return true; + } + + private static bool CompareJsonArrays(JsonElement x, JsonElement y) + { + var xArray = x.EnumerateArray().ToList(); + var yArray = y.EnumerateArray().ToList(); + + if (xArray.Count != yArray.Count) + { + return false; + } + + for (var i = 0; i < xArray.Count; i++) + { + if (!JsonElementsAreEqual(xArray[i], yArray[i])) + { + return false; + } + } + + return true; + } + + /// + /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. + /// When UsingPropertiesComparer() walks object properties and encounters a property typed as + /// 'object', the expected side may be a Dictionary<object, object?> while the actual + /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing + /// the non-JsonElement side and comparing JSON representations. + /// + /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic + /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only + /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all + /// same-type comparisons normally. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) + { + // Handle: expected is non-JsonElement, actual is JsonElement + constraint.Using( + (actualJsonElement, expectedObj) => + { + try + { + var expectedElement = JsonUtils.SerializeToElement(expectedObj); + return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); + } + catch + { + return false; + } + } + ); + // Handle reverse: expected is JsonElement, actual is non-JsonElement + constraint.Using( + (actualObj, expectedJsonElement) => + { + try + { + var actualElement = JsonUtils.SerializeToElement(actualObj); + return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); + } + catch + { + return false; + } + } + ); + return constraint; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs new file mode 100644 index 000000000000..3f4b5eb602b2 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs @@ -0,0 +1,29 @@ +using global::System.Text.Json; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Utils; + +internal static class JsonAssert +{ + /// + /// Asserts that the serialized JSON of an object equals the expected JSON string. + /// Uses JsonElement comparison for reliable deep equality of collections and union types. + /// + internal static void AreEqual(object actual, string expectedJson) + { + var actualElement = JsonUtils.SerializeToElement(actual); + var expectedElement = JsonUtils.Deserialize(expectedJson); + Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); + } + + /// + /// Asserts that the given JSON string survives a deserialization/serialization round-trip + /// intact: deserializes to T then re-serializes and compares to the original JSON. + /// + internal static void Roundtrips(string json) + { + var deserialized = JsonUtils.Deserialize(json); + AreEqual(deserialized!, json); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs new file mode 100644 index 000000000000..a37ef402c1ac --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs @@ -0,0 +1,236 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle JsonElement objects. +/// +public static class JsonElementComparerExtensions +{ + /// + /// Extension method for comparing JsonElement objects in NUnit tests. + /// Property order doesn't matter, but array order does matter. + /// Includes special handling for DateTime string formats. + /// + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare JsonElements with detailed diffs. + public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) + { + return constraint.Using(new JsonElementComparer()); + } +} + +/// +/// Equality comparer for JsonElement with detailed reporting. +/// Property order doesn't matter, but array order does matter. +/// Now includes special handling for DateTime string formats with improved null handling. +/// +public class JsonElementComparer : IEqualityComparer +{ + private string _failurePath = string.Empty; + + /// + public bool Equals(JsonElement x, JsonElement y) + { + _failurePath = string.Empty; + return CompareJsonElements(x, y, string.Empty); + } + + /// + public int GetHashCode(JsonElement obj) + { + return JsonSerializer.Serialize(obj).GetHashCode(); + } + + private bool CompareJsonElements(JsonElement x, JsonElement y, string path) + { + // If value kinds don't match, they're not equivalent + if (x.ValueKind != y.ValueKind) + { + _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; + return false; + } + + switch (x.ValueKind) + { + case JsonValueKind.Object: + return CompareJsonObjects(x, y, path); + + case JsonValueKind.Array: + return CompareJsonArraysInOrder(x, y, path); + + case JsonValueKind.String: + string? xStr = x.GetString(); + string? yStr = y.GetString(); + + // Handle null strings + if (xStr is null && yStr is null) + return true; + + if (xStr is null || yStr is null) + { + _failurePath = + $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; + return false; + } + + // Check if they are identical strings + if (xStr == yStr) + return true; + + // Try to handle DateTime strings + if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) + { + if (AreEquivalentDateTimeStrings(xStr, yStr)) + return true; + } + + _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; + return false; + + case JsonValueKind.Number: + if (x.GetDecimal() != y.GetDecimal()) + { + _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; + return false; + } + + return true; + + case JsonValueKind.True: + case JsonValueKind.False: + if (x.GetBoolean() != y.GetBoolean()) + { + _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; + return false; + } + + return true; + + case JsonValueKind.Null: + return true; + + default: + _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; + return false; + } + } + + private bool IsLikelyDateTimeString(string? str) + { + // Simple heuristic to identify likely ISO date time strings + return str is not null + && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); + } + + private bool AreEquivalentDateTimeStrings(string str1, string str2) + { + // Try to parse both as DateTime + if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) + { + return dt1 == dt2; + } + + return false; + } + + private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) + { + // Create dictionaries for both JSON objects + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + // Check if all properties in x exist in y + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + _failurePath = $"{path}: Missing property '{key}'"; + return false; + } + } + + // Check if y has extra properties + foreach (var key in yProps.Keys) + { + if (!xProps.ContainsKey(key)) + { + _failurePath = $"{path}: Unexpected property '{key}'"; + return false; + } + } + + // Compare each property value + foreach (var key in xProps.Keys) + { + var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; + if (!CompareJsonElements(xProps[key], yProps[key], propPath)) + { + return false; + } + } + + return true; + } + + private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) + { + var xArray = x.EnumerateArray(); + var yArray = y.EnumerateArray(); + + // Count x elements + var xCount = 0; + var xElements = new List(); + foreach (var item in xArray) + { + xElements.Add(item); + xCount++; + } + + // Count y elements + var yCount = 0; + var yElements = new List(); + foreach (var item in yArray) + { + yElements.Add(item); + yCount++; + } + + // Check if counts match + if (xCount != yCount) + { + _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; + return false; + } + + // Compare elements in order + for (var i = 0; i < xCount; i++) + { + var itemPath = $"{path}[{i}]"; + if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) + { + return false; + } + } + + return true; + } + + /// + public override string ToString() + { + if (!string.IsNullOrEmpty(_failurePath)) + { + return $"JSON comparison failed at {_failurePath}"; + } + + return "JsonElementEqualityComparer"; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs new file mode 100644 index 000000000000..816f4c010e6e --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs @@ -0,0 +1,32 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class NUnitExtensions +{ + /// + /// Modifies the EqualConstraint to use our own set of default comparers. + /// + /// + /// + public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => + constraint + .UsingPropertiesComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingOneOfComparer() + .UsingJsonElementComparer() + .UsingOptionalComparer() + .UsingObjectDictionaryComparer() + .UsingAdditionalPropertiesComparer() + .UsingJsonSerializationComparer(); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs new file mode 100644 index 000000000000..767439174363 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs @@ -0,0 +1,86 @@ +using NUnit.Framework.Constraints; +using OneOf; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle OneOf values. +/// +public static class EqualConstraintExtensions +{ + /// + /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOneOf types + constraint.Using( + (x, y) => + { + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (x.Value is null && y.Value is null) + { + return true; + } + + if (x.Value is null) + { + return false; + } + + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs new file mode 100644 index 000000000000..98bfcac477b8 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs @@ -0,0 +1,104 @@ +using NUnit.Framework.Constraints; +using OneOf; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle Optional values. +/// +public static class OptionalComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOptional types + constraint.Using( + (x, y) => + { + // Both must have the same IsDefined state + if (x.IsDefined != y.IsDefined) + { + return false; + } + + // If both are undefined, they're equal + if (!x.IsDefined) + { + return true; + } + + // Both are defined, compare their boxed values + var xValue = x.GetBoxedValue(); + var yValue = y.GetBoxedValue(); + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (xValue is null && yValue is null) + { + return true; + } + + if (xValue is null || yValue is null) + { + return false; + } + + // Use NUnit's property comparer for the inner values + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values within Optional types. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs new file mode 100644 index 000000000000..fc0b595a5e54 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs @@ -0,0 +1,87 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class ReadOnlyMemoryComparerExtensions +{ + /// + /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. + /// + /// The type of elements in the ReadOnlyMemory. + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare ReadOnlyMemory<T>. + public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) + where T : IComparable + { + return constraint.Using(new ReadOnlyMemoryComparer()); + } +} + +/// +/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. +/// +/// +/// The type of elements in the ReadOnlyMemory. +/// +public class ReadOnlyMemoryComparer : IComparer> + where T : IComparable +{ + /// + public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) + { + // Check if sequences are equal + var xSpan = x.Span; + var ySpan = y.Span; + + // Optimized case for IEquatable implementations + if (typeof(IEquatable).IsAssignableFrom(typeof(T))) + { + var areEqual = xSpan.SequenceEqual(ySpan); + if (areEqual) + { + return 0; // Sequences are equal + } + } + else + { + // Manual equality check for non-IEquatable types + if (xSpan.Length == ySpan.Length) + { + var areEqual = true; + for (var i = 0; i < xSpan.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + areEqual = false; + break; + } + } + + if (areEqual) + { + return 0; // Sequences are equal + } + } + } + + // For non-equal sequences, we need to return a consistent ordering + // First compare lengths + if (x.Length != y.Length) + return x.Length.CompareTo(y.Length); + + // Same length but different content - compare first differing element + for (var i = 0; i < x.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + return xSpan[i].CompareTo(ySpan[i]); + } + } + + // Should never reach here if not equal + return 0; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs new file mode 100644 index 000000000000..838d0c00b960 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs @@ -0,0 +1,13 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// The response object returned from the API. +/// +internal record ApiResponse +{ + internal required int StatusCode { get; init; } + + internal required HttpResponseMessage Raw { get; init; } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs new file mode 100644 index 000000000000..9a3cabb806a3 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs @@ -0,0 +1,67 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; +using global::System.Text; + +namespace SeedApi.Core; + +internal abstract record BaseRequest +{ + internal string? BaseUrl { get; init; } + + internal required HttpMethod Method { get; init; } + + internal required string Path { get; init; } + + internal string? ContentType { get; init; } + + /// + /// The query string for this request (including the leading '?' if non-empty). + /// + internal string? QueryString { get; init; } + + internal Dictionary Headers { get; init; } = + new(StringComparer.OrdinalIgnoreCase); + + internal IRequestOptions? Options { get; init; } + + internal abstract HttpContent? CreateContent(); + + protected static ( + Encoding encoding, + string? charset, + string mediaType + ) ParseContentTypeOrDefault( + string? contentType, + Encoding encodingFallback, + string mediaTypeFallback + ) + { + var encoding = encodingFallback; + var mediaType = mediaTypeFallback; + string? charset = null; + if (string.IsNullOrEmpty(contentType)) + { + return (encoding, charset, mediaType); + } + + if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) + { + return (encoding, charset, mediaType); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) + { + charset = mediaTypeHeaderValue.CharSet; + encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) + { + mediaType = mediaTypeHeaderValue.MediaType; + } + + return (encoding, charset, mediaType); + } + + protected static Encoding Utf8NoBom => EncodingCache.Utf8NoBom; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs new file mode 100644 index 000000000000..b684f33d750e --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +internal class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter, new() +{ + private static readonly TConverterType _converter = new TConverterType(); + + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new global::System.Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs new file mode 100644 index 000000000000..ccf4e963cc89 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace SeedApi.Core; + +internal static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; + public const string DateFormat = "yyyy-MM-dd"; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs new file mode 100644 index 000000000000..af61cc061ae5 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs @@ -0,0 +1,747 @@ +// ReSharper disable All +#pragma warning disable + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using global::System.Diagnostics; +using global::System.Diagnostics.CodeAnalysis; +using global::System.Globalization; +using global::System.Runtime.CompilerServices; +using global::System.Runtime.InteropServices; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +// ReSharper disable SuggestVarOrType_SimpleTypes +// ReSharper disable SuggestVarOrType_BuiltInTypes + +namespace SeedApi.Core +{ + /// + /// Custom converter for handling the data type with the System.Text.Json library. + /// + /// + /// This class backported from: + /// + /// System.Text.Json.Serialization.Converters.DateOnlyConverter + /// + public sealed class DateOnlyConverter : JsonConverter + { + private const int FormatLength = 10; // YYYY-MM-DD + + private const int MaxEscapedFormatLength = + FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; + + /// + public override DateOnly Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType != JsonTokenType.String) + { + ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); + } + + return ReadCore(ref reader); + } + + /// + public override DateOnly ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + return ReadCore(ref reader); + } + + private static DateOnly ReadCore(ref Utf8JsonReader reader) + { + if ( + !JsonHelpers.IsInRangeInclusive( + reader.ValueLength(), + FormatLength, + MaxEscapedFormatLength + ) + ) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + scoped ReadOnlySpan source; + if (!reader.HasValueSequence && !reader.ValueIsEscaped) + { + source = reader.ValueSpan; + } + else + { + Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; + int bytesWritten = reader.CopyString(stackSpan); + source = stackSpan.Slice(0, bytesWritten); + } + + if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + return value; + } + + /// + public override void Write( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WriteStringValue(buffer); + } + + /// + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WritePropertyName(buffer); + } + } + + internal static class JsonConstants + { + // The maximum number of fraction digits the Json DateTime parser allows + public const int DateTimeParseNumFractionDigits = 16; + + // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. + public const int MaxExpansionFactorWhileEscaping = 6; + + // The largest fraction expressible by TimeSpan and DateTime formats + public const int MaxDateTimeFraction = 9_999_999; + + // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. + public const int DateTimeNumFractionDigits = 7; + + public const byte UtcOffsetToken = (byte)'Z'; + + public const byte TimePrefix = (byte)'T'; + + public const byte Period = (byte)'.'; + + public const byte Hyphen = (byte)'-'; + + public const byte Colon = (byte)':'; + + public const byte Plus = (byte)'+'; + } + + // ReSharper disable SuggestVarOrType_Elsewhere + // ReSharper disable SuggestVarOrType_SimpleTypes + // ReSharper disable SuggestVarOrType_BuiltInTypes + + internal static class JsonHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => + (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); + + public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; + + [StructLayout(LayoutKind.Auto)] + private struct DateTimeParseData + { + public int Year; + public int Month; + public int Day; + public bool IsCalendarDateOnly; + public int Hour; + public int Minute; + public int Second; + public int Fraction; // This value should never be greater than 9_999_999. + public int OffsetHours; + public int OffsetMinutes; + + // ReSharper disable once NotAccessedField.Local + public byte OffsetToken; + } + + public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) + { + if ( + TryParseDateTimeOffset(source, out DateTimeParseData parseData) + && parseData.IsCalendarDateOnly + && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) + ) + { + value = DateOnly.FromDateTime(dateTime); + return true; + } + + value = default; + return false; + } + + /// + /// ISO 8601 date time parser (ISO 8601-1:2019). + /// + /// The date/time to parse in UTF-8 format. + /// The parsed for the given . + /// + /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day + /// representations with optional specification of seconds and fractional seconds. + /// + /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). + /// If unspecified they are considered to be local per spec. + /// + /// Examples: (TZD is either "Z" or hh:mm offset from UTC) + /// + /// YYYY-MM-DD (e.g. 1997-07-16) + /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) + /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) + /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) + /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) + /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) + /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) + /// + /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). + /// The extended variants have separator characters between components ('-', ':', '.', etc.). + /// Spaces are not permitted. + /// + /// "true" if successfully parsed. + private static bool TryParseDateTimeOffset( + ReadOnlySpan source, + out DateTimeParseData parseData + ) + { + parseData = default; + + // too short datetime + Debug.Assert(source.Length >= 10); + + // Parse the calendar date + // ----------------------- + // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" + // [dateX] = [year]["-"][month]["-"][day] + // [year] = [YYYY] [0000 - 9999] (4.3.2) + // [month] = [MM] [01 - 12] (4.3.3) + // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) + // + // Note: 5.2.2.2 "Representations with reduced precision" allows for + // just [year]["-"][month] (a) and just [year] (b), but we currently + // don't permit it. + + { + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + uint digit3 = source[2] - (uint)'0'; + uint digit4 = source[3] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) + { + return false; + } + + parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); + } + + if ( + source[4] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) + || source[7] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) + ) + { + return false; + } + + // We now have YYYY-MM-DD [dateX] + // ReSharper disable once ConvertIfStatementToSwitchStatement + if (source.Length == 10) + { + parseData.IsCalendarDateOnly = true; + return true; + } + + // Parse the time of day + // --------------------- + // + // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" + // [timeX] = ["T"][hour][":"][min][":"][sec] + // [hour] = [hh] [00 - 23] (4.3.8a) + // [minute] = [mm] [00 - 59] (4.3.9a) + // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) + // + // ISO 8601-1:2019 5.3.3 "UTC of day" + // [timeX]["Z"] + // + // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between + // local timescale and UTC" (Extended format) + // + // [shiftX] = ["+"|"-"][hour][":"][min] + // + // Notes: + // + // "T" is optional per spec, but _only_ when times are used alone. In our + // case, we're reading out a complete date & time and as such require "T". + // (5.4.2.1b). + // + // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations + // with reduced precision". 5.3.1.3b allows just specifying the hour, but + // we currently don't permit this. + // + // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). + // We only allow fractions for seconds currently. Lower order components + // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be + // one digit, but the max number of digits is implementation defined. We + // currently allow up to 16 digits of fractional seconds only. While we + // support 16 fractional digits we only parse the first seven, anything + // past that is considered a zero. This is to stay compatible with the + // DateTime implementation which is limited to this resolution. + + if (source.Length < 16) + { + // Source does not have enough characters for YYYY-MM-DDThh:mm + return false; + } + + // Parse THH:MM (e.g. "T10:32") + if ( + source[10] != JsonConstants.TimePrefix + || source[13] != JsonConstants.Colon + || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) + || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm + Debug.Assert(source.Length >= 16); + if (source.Length == 16) + { + return true; + } + + byte curByte = source[16]; + int sourceIndex = 17; + + // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Colon: + break; + default: + return false; + } + + // Try reading the seconds + if ( + source.Length < 19 + || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss + Debug.Assert(source.Length >= 19); + if (source.Length == 19) + { + return true; + } + + curByte = source[19]; + sourceIndex = 20; + + // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Period: + break; + default: + return false; + } + + // Source does not have enough characters for second fractions (i.e. ".s") + // YYYY-MM-DDThh:mm:ss.s + if (source.Length < 21) + { + return false; + } + + // Parse fraction. This value should never be greater than 9_999_999 + int numDigitsRead = 0; + int fractionEnd = Math.Min( + sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, + source.Length + ); + + while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) + { + if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); + numDigitsRead++; + } + + sourceIndex++; + } + + if (parseData.Fraction != 0) + { + while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction *= 10; + numDigitsRead++; + } + } + + // We now have YYYY-MM-DDThh:mm:ss.s + Debug.Assert(sourceIndex <= source.Length); + if (sourceIndex == source.Length) + { + return true; + } + + curByte = source[sourceIndex++]; + + // TZD ['Z'|'+'|'-'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + default: + return false; + } + + static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) + { + // Parse the hours for the offset + if ( + offsetData.Length < 2 + || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss.s+|-hh + + if (offsetData.Length == 2) + { + // Just hours offset specified + return true; + } + + // Ensure we have enough for ":mm" + return offsetData.Length == 5 + && offsetData[2] == JsonConstants.Colon + && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantAssignment + private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) + { + Debug.Assert(source.Length == 2); + + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9) + { + value = 0; + return false; + } + + value = (int)(digit1 * 10 + digit2); + return true; + } + + // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs + + /// + /// Overflow-safe DateTime factory. + /// + private static bool TryCreateDateTime( + DateTimeParseData parseData, + DateTimeKind kind, + out DateTime value + ) + { + if (parseData.Year == 0) + { + value = default; + return false; + } + + Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. + + if ((uint)parseData.Month - 1 >= 12) + { + value = default; + return false; + } + + uint dayMinusOne = (uint)parseData.Day - 1; + if ( + dayMinusOne >= 28 + && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) + ) + { + value = default; + return false; + } + + if ((uint)parseData.Hour > 23) + { + value = default; + return false; + } + + if ((uint)parseData.Minute > 59) + { + value = default; + return false; + } + + // This needs to allow leap seconds when appropriate. + // See https://github.com/dotnet/runtime/issues/30135. + if ((uint)parseData.Second > 59) + { + value = default; + return false; + } + + Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. + + ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) + ? DaysToMonth366 + : DaysToMonth365; + int yearMinusOne = parseData.Year - 1; + int totalDays = + yearMinusOne * 365 + + yearMinusOne / 4 + - yearMinusOne / 100 + + yearMinusOne / 400 + + days[parseData.Month - 1] + + parseData.Day + - 1; + long ticks = totalDays * TimeSpan.TicksPerDay; + int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; + ticks += totalSeconds * TimeSpan.TicksPerSecond; + ticks += parseData.Fraction; + value = new DateTime(ticks: ticks, kind: kind); + return true; + } + + private static ReadOnlySpan DaysToMonth365 => + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + private static ReadOnlySpan DaysToMonth366 => + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + } + + internal static class ThrowHelper + { + private const string ExceptionSourceValueToRethrowAsJsonException = + "System.Text.Json.Rethrowable"; + + [DoesNotReturn] + public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) + { + throw GetInvalidOperationException("string", tokenType); + } + + public static void ThrowFormatException(DataType dataType) + { + throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + + private static global::System.Exception GetInvalidOperationException( + string message, + JsonTokenType tokenType + ) + { + return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); + } + + private static InvalidOperationException GetInvalidOperationException(string message) + { + return new InvalidOperationException(message) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + } + + internal static class Utf8JsonReaderExtensions + { + internal static int ValueLength(this Utf8JsonReader reader) => + reader.HasValueSequence + ? checked((int)reader.ValueSequence.Length) + : reader.ValueSpan.Length; + } + + internal enum DataType + { + TimeOnly, + DateOnly, + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal static class SR + { + private static readonly bool s_usingResourceKeys = + AppContext.TryGetSwitch( + "System.Resources.UseSystemResourceKeys", + out bool usingResourceKeys + ) && usingResourceKeys; + + public static string UnsupportedFormat => Strings.UnsupportedFormat; + + public static string InvalidCast => Strings.InvalidCast; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1) + : string.Format(resourceFormat, p1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1, object? p2) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1, p2) + : string.Format(resourceFormat, p1, p2); + } + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( + "System.Resources.Tools.StronglyTypedResourceBuilder", + "17.0.0.0" + )] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings + { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode" + )] + internal Strings() { } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager( + "System.Text.Json.Resources.Strings", + typeof(Strings).Assembly + ); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Globalization.CultureInfo Culture + { + get { return resourceCulture; } + set { resourceCulture = value; } + } + + /// + /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. + /// + internal static string InvalidCast + { + get { return ResourceManager.GetString("InvalidCast", resourceCulture); } + } + + /// + /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. + /// + internal static string UnsupportedFormat + { + get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } + } + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs new file mode 100644 index 000000000000..d7dedc7f165b --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs @@ -0,0 +1,40 @@ +using global::System.Globalization; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +internal class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } + + public override DateTime ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateTime value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs new file mode 100644 index 000000000000..d14fc3bfa37e --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs @@ -0,0 +1,11 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// The request object to send without a request body. +/// +internal record EmptyRequest : BaseRequest +{ + internal override HttpContent? CreateContent() => null; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs new file mode 100644 index 000000000000..2dae8b535a18 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs @@ -0,0 +1,11 @@ +using global::System.Text; + +namespace SeedApi.Core; + +internal static class EncodingCache +{ + internal static readonly Encoding Utf8NoBom = new UTF8Encoding( + encoderShouldEmitUTF8Identifier: false, + throwOnInvalidBytes: true + ); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs new file mode 100644 index 000000000000..7338b20e748c --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs @@ -0,0 +1,55 @@ +using global::System.Diagnostics.CodeAnalysis; +using global::System.Runtime.Serialization; + +namespace SeedApi.Core; + +internal static class Extensions +{ + public static string Stringify(this Enum value) + { + var field = value.GetType().GetField(value.ToString()); + if (field is not null) + { + var attribute = (EnumMemberAttribute?) + global::System.Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); + return attribute?.Value ?? value.ToString(); + } + return value.ToString(); + } + + /// + /// Asserts that a condition is true, throwing an exception with the specified message if it is false. + /// + /// The condition to assert. + /// The exception message if the assertion fails. + /// Thrown when the condition is false. + internal static void Assert(this object value, bool condition, string message) + { + if (!condition) + { + throw new global::System.Exception(message); + } + } + + /// + /// Asserts that a value is not null, throwing an exception with the specified message if it is null. + /// + /// The type of the value to assert. + /// The value to assert is not null. + /// The exception message if the assertion fails. + /// The non-null value. + /// Thrown when the value is null. + internal static TValue Assert( + this object _unused, + [NotNull] TValue? value, + string message + ) + where TValue : class + { + if (value is null) + { + throw new global::System.Exception(message); + } + return value; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs new file mode 100644 index 000000000000..343c13716c24 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs @@ -0,0 +1,33 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// Encodes an object into a form URL-encoded content. +/// +public static class FormUrlEncoder +{ + /// + /// Encodes an object into a form URL-encoded content using Deep Object notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsDeepObject(object value) => + new(QueryStringConverter.ToDeepObject(value)); + + /// + /// Encodes an object into a form URL-encoded content using Exploded Form notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsExplodedForm(object value) => + new(QueryStringConverter.ToExplodedForm(value)); + + /// + /// Encodes an object into a form URL-encoded content using Form notation without exploding parameters. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + internal static FormUrlEncodedContent EncodeAsForm(object value) => + new(QueryStringConverter.ToForm(value)); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs new file mode 100644 index 000000000000..e908825e31f1 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs @@ -0,0 +1,52 @@ +namespace SeedApi.Core; + +internal sealed class HeaderValue +{ + private readonly Func> _resolver; + + public HeaderValue(string value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value); + } + + public HeaderValue(Func value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); + } + + public HeaderValue(Func> value) + { + _resolver = value; + } + + public HeaderValue(Func> value) + { + _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); + } + + public static implicit operator HeaderValue(string value) => new(value); + + public static implicit operator HeaderValue(Func value) => new(value); + + public static implicit operator HeaderValue( + Func> value + ) => new(value); + + public static implicit operator HeaderValue( + Func> value + ) => new(value); + + public static HeaderValue FromString(string value) => new(value); + + public static HeaderValue FromFunc(Func value) => new(value); + + public static HeaderValue FromValueTaskFunc( + Func> value + ) => new(value); + + public static HeaderValue FromTaskFunc( + Func> value + ) => new(value); + + internal global::System.Threading.Tasks.ValueTask ResolveAsync() => _resolver(); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs new file mode 100644 index 000000000000..5b2bfc62f423 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs @@ -0,0 +1,28 @@ +namespace SeedApi.Core; + +/// +/// Represents the headers sent with the request. +/// +internal sealed class Headers : Dictionary +{ + internal Headers() { } + + /// + /// Initializes a new instance of the Headers class with the specified value. + /// + /// + internal Headers(Dictionary value) + { + foreach (var kvp in value) + { + this[kvp.Key] = kvp.Value; + } + } + + /// + /// Initializes a new instance of the Headers class with the specified value. + /// + /// + internal Headers(IEnumerable> value) + : base(value.ToDictionary(e => e.Key, e => e.Value)) { } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs new file mode 100644 index 000000000000..734b7ff41065 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs @@ -0,0 +1,197 @@ +namespace SeedApi.Core; + +/// +/// Fluent builder for constructing HTTP headers with support for merging from multiple sources. +/// Provides a clean API for building headers with proper precedence handling. +/// +internal static class HeadersBuilder +{ + /// + /// Fluent builder for constructing HTTP headers. + /// + public sealed class Builder + { + private readonly Dictionary _headers; + + /// + /// Initializes a new instance with default capacity. + /// Uses case-insensitive header name comparison. + /// + public Builder() + { + _headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Initializes a new instance with the specified initial capacity. + /// Uses case-insensitive header name comparison. + /// + public Builder(int capacity) + { + _headers = new Dictionary( + capacity, + StringComparer.OrdinalIgnoreCase + ); + } + + /// + /// Adds a header with the specified key and value. + /// If a header with the same key already exists, it will be overwritten. + /// Null values are ignored. + /// + /// The header name. + /// The header value. Null values are ignored. + /// This builder instance for method chaining. + public Builder Add(string key, string? value) + { + if (value is not null) + { + _headers[key] = (value); + } + return this; + } + + /// + /// Adds a header with the specified key and object value. + /// The value will be converted to string using ValueConvert for consistent serialization. + /// If a header with the same key already exists, it will be overwritten. + /// Null values are ignored. + /// + /// The header name. + /// The header value. Null values are ignored. + /// This builder instance for method chaining. + public Builder Add(string key, object? value) + { + if (value is null) + { + return this; + } + + // Use ValueConvert for consistent serialization across headers, query params, and path params + var stringValue = ValueConvert.ToString(value); + if (stringValue is not null) + { + _headers[key] = (stringValue); + } + return this; + } + + /// + /// Adds multiple headers from a Headers dictionary. + /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. + /// Overwrites any existing headers with the same key. + /// Null entries are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(Headers? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + _headers[header.Key] = header.Value; + } + + return this; + } + + /// + /// Adds multiple headers from a Headers dictionary, excluding the Authorization header. + /// This is useful for endpoints that don't require authentication, to avoid triggering + /// lazy auth token resolution. + /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. + /// Overwrites any existing headers with the same key. + /// Null entries are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder AddWithoutAuth(Headers? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + _headers[header.Key] = header.Value; + } + + return this; + } + + /// + /// Adds multiple headers from a key-value pair collection. + /// Overwrites any existing headers with the same key. + /// Null values are ignored. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(IEnumerable>? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + if (header.Value is not null) + { + _headers[header.Key] = (header.Value); + } + } + + return this; + } + + /// + /// Adds multiple headers from a dictionary. + /// Overwrites any existing headers with the same key. + /// + /// The headers to add. Null is treated as empty. + /// This builder instance for method chaining. + public Builder Add(Dictionary? headers) + { + if (headers is null) + { + return this; + } + + foreach (var header in headers) + { + _headers[header.Key] = (header.Value); + } + + return this; + } + + /// + /// Asynchronously builds the final headers dictionary containing all merged headers. + /// Resolves all HeaderValue instances that may contain async operations. + /// Returns a case-insensitive dictionary. + /// + /// A task that represents the asynchronous operation, containing a case-insensitive dictionary of headers. + public async global::System.Threading.Tasks.Task> BuildAsync() + { + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in _headers) + { + var value = await kvp.Value.ResolveAsync().ConfigureAwait(false); + if (value is not null) + { + headers[kvp.Key] = value; + } + } + return headers; + } + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs new file mode 100644 index 000000000000..4295da097dc9 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs @@ -0,0 +1,20 @@ +#if !NET5_0_OR_GREATER +namespace SeedApi.Core; + +/// +/// Polyfill extension providing a ReadAsStringAsync(CancellationToken) overload +/// for target frameworks older than .NET 5, where only the parameterless +/// ReadAsStringAsync() is available. +/// +internal static class HttpContentExtensions +{ + internal static Task ReadAsStringAsync( + this HttpContent httpContent, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return httpContent.ReadAsStringAsync(); + } +} +#endif diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs new file mode 100644 index 000000000000..cedb977973d1 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs @@ -0,0 +1,8 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +internal static class HttpMethodExtensions +{ + public static readonly HttpMethod Patch = new("PATCH"); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs new file mode 100644 index 000000000000..1a5d48064427 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +public interface IIsRetryableContent +{ + public bool IsRetryable { get; } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs new file mode 100644 index 000000000000..4562c1723cf9 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs @@ -0,0 +1,83 @@ +namespace SeedApi.Core; + +internal interface IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional headers to be sent with the request. + /// Headers previously set with matching keys will be overwritten. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The max number of retries to attempt. + /// + public int? MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional query parameters sent with the request. + /// + public IEnumerable> AdditionalQueryParameters { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional body properties sent with the request. + /// This is only applied to JSON requests. + /// + public object? AdditionalBodyProperties { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs new file mode 100644 index 000000000000..93dcc6dd6bca --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs @@ -0,0 +1,15 @@ +namespace SeedApi.Core; + +[global::System.AttributeUsage( + global::System.AttributeTargets.Property | global::System.AttributeTargets.Field +)] +internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute +{ + internal JsonAccessType AccessType { get; init; } = accessType; +} + +internal enum JsonAccessType +{ + ReadOnly, + WriteOnly, +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs new file mode 100644 index 000000000000..2fa8cfb6ad8c --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs @@ -0,0 +1,275 @@ +using global::System.Reflection; +using global::System.Text.Encodings.Web; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using global::System.Text.Json.Serialization; +using global::System.Text.Json.Serialization.Metadata; + +namespace SeedApi.Core; + +internal static partial class JsonOptions +{ + internal static readonly JsonSerializerOptions JsonSerializerOptions; + internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; + + static JsonOptions() + { + var options = new JsonSerializerOptions + { + Converters = + { + new DateTimeSerializer(), +#if USE_PORTABLE_DATE_ONLY + new DateOnlyConverter(), +#endif + new OneOfSerializer(), + new OptionalJsonConverterFactory(), + }, +#if DEBUG + WriteIndented = true, +#endif + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + NullableOptionalModifier, + JsonAccessAndIgnoreModifier, + HandleExtensionDataFields, + }, + }, + }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; + + var relaxedOptions = new JsonSerializerOptions(options) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + JsonSerializerOptionsRelaxedEscaping = relaxedOptions; + } + + private static void NullableOptionalModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var property in typeInfo.Properties) + { + var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; + + if (propertyInfo is null) + continue; + + // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior + var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); + if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) + { + // ReadOnly means "never serialize", which completely overrides Optional/Nullable. + // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier + // will set ShouldSerialize = false anyway. + continue; + } + // Note: WriteOnly doesn't conflict with Optional/Nullable since it only + // affects deserialization (Set), not serialization (ShouldSerialize) + + var isOptionalType = + property.PropertyType.IsGenericType + && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); + + var hasOptionalAttribute = + propertyInfo.GetCustomAttribute() is not null; + var hasNullableAttribute = + propertyInfo.GetCustomAttribute() is not null; + + if (isOptionalType && hasOptionalAttribute) + { + var originalGetter = property.Get; + if (originalGetter is not null) + { + var capturedIsNullable = hasNullableAttribute; + + property.ShouldSerialize = (obj, value) => + { + var optionalValue = originalGetter(obj); + if (optionalValue is not IOptional optional) + return false; + + if (!optional.IsDefined) + return false; + + if (!capturedIsNullable) + { + var innerValue = optional.GetBoxedValue(); + if (innerValue is null) + return false; + } + + return true; + }; + } + } + else if (hasNullableAttribute) + { + // Force serialization of nullable properties even when null + property.ShouldSerialize = (obj, value) => true; + } + } + } + + private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var propertyInfo in typeInfo.Properties) + { + var jsonAccessAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonAccessAttribute is not null) + { + propertyInfo.IsRequired = false; + switch (jsonAccessAttribute.AccessType) + { + case JsonAccessType.ReadOnly: + propertyInfo.ShouldSerialize = (_, _) => false; + break; + case JsonAccessType.WriteOnly: + propertyInfo.Set = null; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + var jsonIgnoreAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonIgnoreAttribute is not null) + { + propertyInfo.IsRequired = false; + } + } + } + + private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) + { + if ( + typeInfo.Kind == JsonTypeInfoKind.Object + && typeInfo.Properties.All(prop => !prop.IsExtensionData) + ) + { + var extensionProp = typeInfo + .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) + .FirstOrDefault(prop => + prop.GetCustomAttribute() is not null + ); + + if (extensionProp is not null) + { + var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( + extensionProp.FieldType, + extensionProp.Name + ); + jsonPropertyInfo.Get = extensionProp.GetValue; + jsonPropertyInfo.Set = extensionProp.SetValue; + jsonPropertyInfo.IsExtensionData = true; + typeInfo.Properties.Add(jsonPropertyInfo); + } + } + } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); +} + +internal static class JsonUtils +{ + internal static string Serialize(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + + internal static string Serialize(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); + + internal static string SerializeRelaxedEscaping(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static JsonElement SerializeToElement(T obj) => + JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonElement SerializeToElement(object obj, global::System.Type type) => + JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); + + internal static JsonDocument SerializeToDocument(T obj) => + JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonNode? SerializeToNode(T obj) => + JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); + + internal static byte[] SerializeToUtf8Bytes(T obj) => + JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); + + internal static string SerializeWithAdditionalProperties( + T obj, + object? additionalProperties = null + ) + { + if (additionalProperties is null) + { + return Serialize(obj); + } + var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); + if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) + { + throw new InvalidOperationException( + "The additional properties must serialize to a JSON object." + ); + } + var jsonNode = SerializeToNode(obj); + if (jsonNode is not JsonObject jsonObject) + { + throw new InvalidOperationException( + "The serialized object must be a JSON object to add properties." + ); + } + MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); + return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); + } + + private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) + { + foreach (var property in overrideObject) + { + if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) + { + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + continue; + } + if ( + existingValue is JsonObject nestedBaseObject + && property.Value is JsonObject nestedOverrideObject + ) + { + // If both values are objects, recursively merge them. + MergeJsonObjects(nestedBaseObject, nestedOverrideObject); + continue; + } + // Otherwise, the overrideObject takes precedence. + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + } + } + + internal static T Deserialize(string json) => + JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs new file mode 100644 index 000000000000..0a85891304f1 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs @@ -0,0 +1,36 @@ +using global::System.Net.Http; + +namespace SeedApi.Core; + +/// +/// The request object to be sent for JSON APIs. +/// +internal record JsonRequest : BaseRequest +{ + internal object? Body { get; init; } + + internal override HttpContent? CreateContent() + { + if (Body is null && Options?.AdditionalBodyProperties is null) + { + return null; + } + + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + ContentType, + Utf8NoBom, + "application/json" + ); + var content = new StringContent( + JsonUtils.SerializeWithAdditionalProperties(Body, Options?.AdditionalBodyProperties), + encoding, + mediaType + ); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + return content; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs new file mode 100644 index 000000000000..bf72225d461b --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs @@ -0,0 +1,294 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; + +namespace SeedApi.Core; + +/// +/// The request object to be sent for multipart form data. +/// +internal record MultipartFormRequest : BaseRequest +{ + private readonly List> _partAdders = []; + + internal void AddJsonPart(string name, object? value) => AddJsonPart(name, value, null); + + internal void AddJsonPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + contentType, + Utf8NoBom, + "application/json" + ); + var content = new StringContent(JsonUtils.Serialize(value), encoding, mediaType); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + form.Add(content, name); + }); + } + + internal void AddJsonParts(string name, IEnumerable? value) => + AddJsonParts(name, value, null); + + internal void AddJsonParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddJsonPart(name, item, contentType); + } + } + + internal void AddJsonParts(string name, IEnumerable? value) => + AddJsonParts(name, value, null); + + internal void AddJsonParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddJsonPart(name, item, contentType); + } + } + + internal void AddStringPart(string name, object? value) => AddStringPart(name, value, null); + + internal void AddStringPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + AddStringPart(name, ValueConvert.ToString(value), contentType); + } + + internal void AddStringPart(string name, string? value) => AddStringPart(name, value, null); + + internal void AddStringPart(string name, string? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var (encoding, charset, mediaType) = ParseContentTypeOrDefault( + contentType, + Utf8NoBom, + "text/plain" + ); + var content = new StringContent(value, encoding, mediaType); + if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) + { + content.Headers.ContentType.CharSet = ""; + } + + form.Add(content, name); + }); + } + + internal void AddStringParts(string name, IEnumerable? value) => + AddStringParts(name, value, null); + + internal void AddStringParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + AddStringPart(name, ValueConvert.ToString(value), contentType); + } + + internal void AddStringParts(string name, IEnumerable? value) => + AddStringParts(name, value, null); + + internal void AddStringParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddStringPart(name, item, contentType); + } + } + + internal void AddStreamPart(string name, Stream? stream, string? fileName) => + AddStreamPart(name, stream, fileName, null); + + internal void AddStreamPart(string name, Stream? stream, string? fileName, string? contentType) + { + if (stream is null) + { + return; + } + + _partAdders.Add(form => + { + var content = new StreamContent(stream) + { + Headers = + { + ContentType = MediaTypeHeaderValue.Parse( + contentType ?? "application/octet-stream" + ), + }, + }; + + if (fileName is not null) + { + form.Add(content, name, fileName); + } + else + { + form.Add(content, name); + } + }); + } + + internal void AddFileParameterPart(string name, Stream? stream) => + AddStreamPart(name, stream, null, null); + + internal void AddFileParameterPart(string name, FileParameter? file) => + AddFileParameterPart(name, file, null); + + internal void AddFileParameterPart( + string name, + FileParameter? file, + string? fallbackContentType + ) => + AddStreamPart(name, file?.Stream, file?.FileName, file?.ContentType ?? fallbackContentType); + + internal void AddFileParameterParts(string name, IEnumerable? files) => + AddFileParameterParts(name, files, null); + + internal void AddFileParameterParts( + string name, + IEnumerable? files, + string? fallbackContentType + ) + { + if (files is null) + { + return; + } + + foreach (var file in files) + { + AddFileParameterPart(name, file, fallbackContentType); + } + } + + internal void AddFormEncodedPart(string name, object? value) => + AddFormEncodedPart(name, value, null); + + internal void AddFormEncodedPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var content = FormUrlEncoder.EncodeAsForm(value); + if (!string.IsNullOrEmpty(contentType)) + { + content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + + form.Add(content, name); + }); + } + + internal void AddFormEncodedParts(string name, IEnumerable? value) => + AddFormEncodedParts(name, value, null); + + internal void AddFormEncodedParts(string name, IEnumerable? value, string? contentType) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddFormEncodedPart(name, item, contentType); + } + } + + internal void AddExplodedFormEncodedPart(string name, object? value) => + AddExplodedFormEncodedPart(name, value, null); + + internal void AddExplodedFormEncodedPart(string name, object? value, string? contentType) + { + if (value is null) + { + return; + } + + _partAdders.Add(form => + { + var content = FormUrlEncoder.EncodeAsExplodedForm(value); + if (!string.IsNullOrEmpty(contentType)) + { + content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + + form.Add(content, name); + }); + } + + internal void AddExplodedFormEncodedParts(string name, IEnumerable? value) => + AddExplodedFormEncodedParts(name, value, null); + + internal void AddExplodedFormEncodedParts( + string name, + IEnumerable? value, + string? contentType + ) + { + if (value is null) + { + return; + } + + foreach (var item in value) + { + AddExplodedFormEncodedPart(name, item, contentType); + } + } + + internal override HttpContent CreateContent() + { + var form = new MultipartFormDataContent(); + foreach (var adder in _partAdders) + { + adder(form); + } + + return form; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs new file mode 100644 index 000000000000..a1d30328bf9a --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs @@ -0,0 +1,18 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as nullable in the OpenAPI specification. +/// When applied to Optional properties, this indicates that null values should be +/// written to JSON when the optional is defined with null. +/// +/// +/// For regular (required) properties: +/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) +/// - With [Nullable]: null values are written to JSON +/// +/// For Optional properties (also marked with [Optional]): +/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) +/// - With [Nullable]: Optional.Of(null) → write null to JSON +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs new file mode 100644 index 000000000000..6eeb68fcba46 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs @@ -0,0 +1,145 @@ +using global::System.Reflection; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using OneOf; + +namespace SeedApi.Core; + +internal class OneOfSerializer : JsonConverter +{ + public override IOneOf? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (IOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + public override IOneOf ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = reader.GetString(); + if (stringValue == null) + throw new JsonException("Cannot deserialize null property name into OneOf type"); + + // Try to deserialize the string value into one of the supported types + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + // For primitive types, try direct conversion + if (type == typeof(string)) + { + return (IOneOf)cast.Invoke(null, [stringValue])!; + } + + // For other types, try to deserialize from JSON string + var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); + if (result != null) + { + return (IOneOf)cast.Invoke(null, [result])!; + } + } + catch { } + } + + // If no type-specific deserialization worked, default to string if available + var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); + if (stringType != default) + { + return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; + } + + throw new JsonException( + $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" + ); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + IOneOf value, + JsonSerializerOptions options + ) + { + // Serialize the underlying value to a string suitable for use as a dictionary key + var stringValue = value.Value?.ToString() ?? "null"; + writer.WritePropertyName(stringValue); + } + + private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( + global::System.Type typeToConvert + ) + { + var type = typeToConvert; + if (Nullable.GetUnderlyingType(type) is { } underlyingType) + { + type = underlyingType; + } + + var casts = type.GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + while (type is not null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + var genericArguments = type.GetGenericArguments(); + if (genericArguments.Length == 1) + { + return [(genericArguments[0], casts[0])]; + } + + // if object type is present, make sure it is last + var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); + if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) + { + genericArguments = genericArguments + .OrderBy(t => t == typeof(object) ? 1 : 0) + .ToArray(); + } + + return genericArguments + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(global::System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs new file mode 100644 index 000000000000..d174943cb2cf --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs @@ -0,0 +1,474 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Non-generic interface for Optional types to enable reflection-free checks. +/// +public interface IOptional +{ + /// + /// Returns true if the value is defined (set), even if the value is null. + /// + bool IsDefined { get; } + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + object? GetBoxedValue(); +} + +/// +/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). +/// Use this for HTTP PATCH requests where you need to distinguish between: +/// +/// Undefined: Don't send this field (leave it unchanged on the server) +/// Defined with null: Send null (clear the field on the server) +/// Defined with value: Send the value (update the field on the server) +/// +/// +/// The type of the value. Use nullable types (T?) for fields that can be null. +/// +/// For nullable string fields, use Optional<string?>: +/// +/// public class UpdateUserRequest +/// { +/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; +/// } +/// +/// var request = new UpdateUserRequest +/// { +/// Name = "John" // Will send: { "name": "John" } +/// }; +/// +/// var request2 = new UpdateUserRequest +/// { +/// Name = Optional<string?>.Of(null) // Will send: { "name": null } +/// }; +/// +/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) +/// +/// +public readonly struct Optional : IOptional, IEquatable> +{ + private readonly T _value; + private readonly bool _isDefined; + + private Optional(T value, bool isDefined) + { + _value = value; + _isDefined = isDefined; + } + + /// + /// Creates an undefined value - the field will not be included in the HTTP request. + /// Use this as the default value for optional fields. + /// + /// + /// + /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; + /// + /// + public static Optional Undefined => new(default!, false); + + /// + /// Creates a defined value - the field will be included in the HTTP request. + /// The value can be null if T is a nullable type. + /// + /// The value to set. Can be null if T is nullable (e.g., string?, int?). + /// + /// + /// // Set to a value + /// request.Name = Optional<string?>.Of("John"); + /// + /// // Set to null (clears the field) + /// request.Email = Optional<string?>.Of(null); + /// + /// // Or use implicit conversion + /// request.Name = "John"; // Same as Of("John") + /// request.Email = null; // Same as Of(null) + /// + /// + public static Optional Of(T value) => new(value, true); + + /// + /// Returns true if the field is defined (set), even if the value is null. + /// Use this to determine if the field should be included in the HTTP request. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// requestBody["name"] = request.Name.Value; // Include in request (can be null) + /// } + /// + /// + public bool IsDefined => _isDefined; + + /// + /// Returns true if the field is undefined (not set). + /// Use this to check if the field should be excluded from the HTTP request. + /// + /// + /// + /// if (request.Email.IsUndefined) + /// { + /// // Don't include email in the request - leave it unchanged + /// } + /// + /// + public bool IsUndefined => !_isDefined; + + /// + /// Gets the value. The value may be null if T is a nullable type. + /// + /// Thrown if the value is undefined. + /// + /// Always check before accessing Value, or use instead. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> + /// } + /// + /// // Or check for null explicitly + /// if (request.Email.IsDefined && request.Email.Value is null) + /// { + /// // Email is explicitly set to null (clear it) + /// } + /// + /// + public T Value + { + get + { + if (!_isDefined) + throw new InvalidOperationException("Optional value is undefined"); + return _value; + } + } + + /// + /// Gets the value if defined, otherwise returns the specified default value. + /// Note: If the value is defined as null, this returns null (not the default). + /// + /// The value to return if undefined. + /// The actual value if defined (can be null), otherwise the default value. + /// + /// + /// string name = request.Name.GetValueOrDefault("Anonymous"); + /// // If Name is undefined: returns "Anonymous" + /// // If Name is Of(null): returns null + /// // If Name is Of("John"): returns "John" + /// + /// + public T GetValueOrDefault(T defaultValue = default!) + { + return _isDefined ? _value : defaultValue; + } + + /// + /// Tries to get the value. Returns true if the value is defined (even if null). + /// + /// + /// When this method returns, contains the value if defined, or default(T) if undefined. + /// The value may be null if T is nullable. + /// + /// True if the value is defined; otherwise, false. + /// + /// + /// if (request.Email.TryGetValue(out var email)) + /// { + /// requestBody["email"] = email; // email can be null + /// } + /// else + /// { + /// // Email is undefined - don't include in request + /// } + /// + /// + public bool TryGetValue(out T value) + { + if (_isDefined) + { + value = _value; + return true; + } + value = default!; + return false; + } + + /// + /// Implicitly converts a value to Optional<T>.Of(value). + /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). + /// + /// The value to convert (can be null if T is nullable). + public static implicit operator Optional(T value) => Of(value); + + /// + /// Returns a string representation of this Optional value. + /// + /// "Undefined" if not set, or "Defined(value)" if set. + public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + public object? GetBoxedValue() + { + if (!_isDefined) + return null; + return _value; + } + + /// + public bool Equals(Optional other) => + _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); + + /// + public override bool Equals(object? obj) => obj is Optional other && Equals(other); + + /// + public override int GetHashCode() + { + if (!_isDefined) + return 0; + unchecked + { + int hash = 17; + hash = hash * 31 + 1; // _isDefined = true + hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); + return hash; + } + } + + /// + /// Determines whether two Optional values are equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are equal; otherwise, false. + public static bool operator ==(Optional left, Optional right) => left.Equals(right); + + /// + /// Determines whether two Optional values are not equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are not equal; otherwise, false. + public static bool operator !=(Optional left, Optional right) => !left.Equals(right); +} + +/// +/// Extension methods for Optional to simplify common operations. +/// +public static class OptionalExtensions +{ + /// + /// Adds the value to a dictionary if the optional is defined (even if the value is null). + /// This is useful for building JSON request payloads where null values should be included. + /// + /// The type of the optional value. + /// The optional value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined + /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined + /// + /// + public static void AddTo( + this Optional optional, + Dictionary dictionary, + string key + ) + { + if (optional.IsDefined) + { + dictionary[key] = optional.Value; + } + } + + /// + /// Executes an action if the optional is defined. + /// + /// The type of the optional value. + /// The optional value. + /// The action to execute with the value. + /// + /// + /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); + /// + /// + public static void IfDefined(this Optional optional, Action action) + { + if (optional.IsDefined) + { + action(optional.Value); + } + } + + /// + /// Maps the value to a new type if the optional is defined, otherwise returns undefined. + /// + /// The type of the original value. + /// The type to map to. + /// The optional value to map. + /// The mapping function. + /// An optional containing the mapped value if defined, otherwise undefined. + /// + /// + /// Optional<string?> name = Optional<string?>.Of("John"); + /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) + /// + /// + public static Optional Map( + this Optional optional, + Func mapper + ) + { + return optional.IsDefined + ? Optional.Of(mapper(optional.Value)) + : Optional.Undefined; + } + + /// + /// Adds a nullable value to a dictionary only if it is not null. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The type of the value (must be a reference type or Nullable). + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : class + { + if (value is not null) + { + dictionary[key] = value; + } + } + + /// + /// Adds a nullable value type to a dictionary only if it has a value. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The underlying value type. + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : struct + { + if (value.HasValue) + { + dictionary[key] = value.Value; + } + } +} + +/// +/// JSON converter factory for Optional that handles undefined vs null correctly. +/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. +/// +public class OptionalJsonConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(global::System.Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + return false; + + return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); + } + + public override JsonConverter? CreateConverter( + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var valueType = typeToConvert.GetGenericArguments()[0]; + var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); + return (JsonConverter?)global::System.Activator.CreateInstance(converterType); + } +} + +/// +/// JSON converter for Optional that unwraps the value during serialization. +/// The actual property skipping is handled by the OptionalTypeInfoResolver. +/// +public class OptionalJsonConverter : JsonConverter> +{ + public override Optional Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return Optional.Of(default!); + } + + var value = JsonSerializer.Deserialize(ref reader, options); + return Optional.Of(value!); + } + + public override void Write( + Utf8JsonWriter writer, + Optional value, + JsonSerializerOptions options + ) + { + // This will be called by the serializer + // We need to unwrap and serialize the inner value + // The TypeInfoResolver will handle skipping undefined values + + if (value.IsUndefined) + { + // This shouldn't be called for undefined values due to ShouldSerialize + // But if it is, write null and let the resolver filter it + writer.WriteNullValue(); + return; + } + + // Get the inner value + var innerValue = value.Value; + + // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) + if (innerValue is null) + { + writer.WriteNullValue(); + return; + } + + // Serialize the unwrapped value + JsonSerializer.Serialize(writer, innerValue, options); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs new file mode 100644 index 000000000000..4c4c4073a0ae --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs @@ -0,0 +1,17 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as optional in the OpenAPI specification. +/// Optional properties use the Optional type and can be undefined (not present in JSON). +/// +/// +/// Properties marked with [Optional] should use the Optional type: +/// - Undefined: Optional.Undefined → omitted from JSON +/// - Defined: Optional.Of(value) → written to JSON +/// +/// Combine with [Nullable] to allow null values: +/// - [Optional, Nullable] Optional → can be undefined, null, or a value +/// - [Optional] Optional → can be undefined or a value (null is invalid) +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs new file mode 100644 index 000000000000..8b43322350bd --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs @@ -0,0 +1,353 @@ +using global::System.Collections; +using global::System.Collections.ObjectModel; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using SeedApi.Core; + +namespace SeedApi; + +public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties +{ + internal ReadOnlyAdditionalProperties() { } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record ReadOnlyAdditionalProperties : IReadOnlyDictionary +{ + private readonly Dictionary _extensionData = new(); + private readonly Dictionary _convertedCache = new(); + + internal ReadOnlyAdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + if (kvp.Value is JsonElement element) + { + _extensionData.Add(kvp.Key, element); + } + else + { + _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); + } + + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(JsonElement value) + { + if (typeof(T) == typeof(JsonElement)) + { + return (T)(object)value; + } + + return value.Deserialize(JsonOptions.JsonSerializerOptions)!; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var cached)) + { + return cached; + } + + var value = ConvertToT(_extensionData[key]); + _convertedCache[key] = value; + return value; + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _extensionData.Count; + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var element)) + { + value = ConvertToT(element); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public T this[string key] => GetCached(key); + + public IEnumerable Keys => _extensionData.Keys; + + public IEnumerable Values => Keys.Select(GetCached); +} + +public record AdditionalProperties : AdditionalProperties +{ + public AdditionalProperties() { } + + public AdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record AdditionalProperties : IDictionary +{ + private readonly Dictionary _extensionData; + private readonly Dictionary _convertedCache; + + public AdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + public AdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + _extensionData[kvp.Key] = kvp.Value; + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(object? extensionDataValue) + { + return extensionDataValue switch + { + T value => value, + JsonElement jsonElement => jsonElement.Deserialize( + JsonOptions.JsonSerializerOptions + )!, + JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, + _ => JsonUtils + .SerializeToElement(extensionDataValue) + .Deserialize(JsonOptions.JsonSerializerOptions)!, + }; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + internal void CopyToExtensionData(IDictionary extensionData) + { + extensionData.Clear(); + foreach (var kvp in _extensionData) + { + extensionData[kvp.Key] = kvp.Value; + } + } + + public JsonObject ToJsonObject() => + ( + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ) + ).AsObject(); + + public JsonNode ToJsonNode() => + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ); + + public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); + + public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); + + public IReadOnlyDictionary ToJsonElementDictionary() + { + return new ReadOnlyDictionary( + _extensionData.ToDictionary( + kvp => kvp.Key, + kvp => + { + if (kvp.Value is JsonElement jsonElement) + { + return jsonElement; + } + + return JsonUtils.SerializeToElement(kvp.Value); + } + ) + ); + } + + public ICollection Keys => _extensionData.Keys; + + public ICollection Values + { + get + { + var values = new T[_extensionData.Count]; + var i = 0; + foreach (var key in Keys) + { + values[i++] = GetCached(key); + } + + return values; + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var value)) + { + return value; + } + + value = ConvertToT(_extensionData[key]); + _convertedCache.Add(key, value); + return value; + } + + private void SetCached(string key, T value) + { + _extensionData[key] = value; + _convertedCache[key] = value; + } + + private void AddCached(string key, T value) + { + _extensionData.Add(key, value); + _convertedCache.Add(key, value); + } + + private bool RemoveCached(string key) + { + var isRemoved = _extensionData.Remove(key); + _convertedCache.Remove(key); + return isRemoved; + } + + public int Count => _extensionData.Count; + public bool IsReadOnly => false; + + public T this[string key] + { + get => GetCached(key); + set => SetCached(key, value); + } + + public void Add(string key, T value) => AddCached(key, value); + + public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); + + public bool Remove(string key) => RemoveCached(key); + + public bool Remove(KeyValuePair item) => RemoveCached(item.Key); + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool Contains(KeyValuePair item) + { + return _extensionData.ContainsKey(item.Key) + && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); + } + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var extensionDataValue)) + { + value = ConvertToT(extensionDataValue); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public void Clear() + { + _extensionData.Clear(); + _convertedCache.Clear(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (array.Length - arrayIndex < _extensionData.Count) + { + throw new ArgumentException( + "The array does not have enough space to copy the elements." + ); + } + + foreach (var kvp in _extensionData) + { + array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); + } + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs new file mode 100644 index 000000000000..837716a987f2 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs @@ -0,0 +1,84 @@ +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public partial class ClientOptions +{ + /// + /// The http headers sent with the request. + /// + internal Headers Headers { get; init; } = new(); + + /// + /// The Base URL for the API. + /// + public string BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = SeedApiEnvironment.Default; + + /// + /// The http client used to make requests. + /// + public HttpClient HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = new HttpClient(); + + /// + /// Additional headers to be sent with HTTP requests. + /// Headers with matching keys will be overwritten by headers set on the request. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = []; + + /// + /// The max number of retries to attempt. + /// + public int MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = 2; + + /// + /// The timeout for the request. + /// + public TimeSpan Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = TimeSpan.FromSeconds(30); + + /// + /// Clones this and returns a new instance + /// + internal ClientOptions Clone() + { + return new ClientOptions + { + BaseUrl = BaseUrl, + HttpClient = HttpClient, + MaxRetries = MaxRetries, + Timeout = Timeout, + Headers = new Headers(new Dictionary(Headers)), + AdditionalHeaders = AdditionalHeaders, + }; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs new file mode 100644 index 000000000000..f33d49028884 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs @@ -0,0 +1,63 @@ +namespace SeedApi; + +/// +/// File parameter for uploading files. +/// +public record FileParameter : IDisposable +#if NET6_0_OR_GREATER + , IAsyncDisposable +#endif +{ + private bool _disposed; + + /// + /// The name of the file to be uploaded. + /// + public string? FileName { get; set; } + + /// + /// The content type of the file to be uploaded. + /// + public string? ContentType { get; set; } + + /// + /// The content of the file to be uploaded. + /// + public required Stream Stream { get; set; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + if (disposing) + { + Stream.Dispose(); + } + + _disposed = true; + } + +#if NET6_0_OR_GREATER + /// + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + await Stream.DisposeAsync().ConfigureAwait(false); + _disposed = true; + } + + GC.SuppressFinalize(this); + } +#endif + + public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs new file mode 100644 index 000000000000..f711c7f774d2 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs @@ -0,0 +1,24 @@ +using global::System.Net; + +namespace SeedApi; + +/// +/// Contains HTTP response metadata including status code, URL, and headers. +/// +public record RawResponse +{ + /// + /// The HTTP status code of the response. + /// + public required HttpStatusCode StatusCode { get; init; } + + /// + /// The request URL that generated this response. + /// + public required Uri Url { get; init; } + + /// + /// The HTTP response headers. + /// + public required Core.ResponseHeaders Headers { get; init; } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs new file mode 100644 index 000000000000..ea7db76b032f --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs @@ -0,0 +1,86 @@ +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public partial class RequestOptions : IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional headers to be sent with the request. + /// Headers previously set with matching keys will be overwritten. + /// + public IEnumerable> AdditionalHeaders { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = []; + + /// + /// The max number of retries to attempt. + /// + public int? MaxRetries { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } + + /// + /// Additional query parameters sent with the request. + /// + public IEnumerable> AdditionalQueryParameters { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } = Enumerable.Empty>(); + + /// + /// Additional body properties sent with the request. + /// This is only applied to JSON requests. + /// + public object? AdditionalBodyProperties { get; +#if NET5_0_OR_GREATER + init; +#else + set; +#endif + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs new file mode 100644 index 000000000000..94afb43fa0a3 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs @@ -0,0 +1,22 @@ +namespace SeedApi; + +/// +/// This exception type will be thrown for any non-2XX API responses. +/// +public class SeedApiApiException( + string message, + int statusCode, + object body, + Exception? innerException = null +) : SeedApiException(message, innerException) +{ + /// + /// The error code of the response that triggered the exception. + /// + public int StatusCode => statusCode; + + /// + /// The body of the response that triggered the exception. + /// + public object Body => body; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs new file mode 100644 index 000000000000..d30f17ce0829 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +[Serializable] +public class SeedApiEnvironment +{ + public const string Default = "https://api.example.com"; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs new file mode 100644 index 000000000000..90e03e71e695 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +/// +/// Base exception class for all exceptions thrown by the SDK. +/// +public class SeedApiException(string message, Exception? innerException = null) + : Exception(message, innerException); diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs new file mode 100644 index 000000000000..3d210b7e0b4c --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +[Serializable] +internal class Version +{ + public const string Current = "0.0.1"; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs new file mode 100644 index 000000000000..4c173fb2c115 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs @@ -0,0 +1,18 @@ +namespace SeedApi; + +/// +/// Wraps a parsed response value with its raw HTTP response metadata. +/// +/// The type of the parsed response data. +public readonly struct WithRawResponse +{ + /// + /// The parsed response data. + /// + public required T Data { get; init; } + + /// + /// The raw HTTP response metadata. + /// + public required RawResponse RawResponse { get; init; } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs new file mode 100644 index 000000000000..7c939859cb9f --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs @@ -0,0 +1,144 @@ +using global::System.Runtime.CompilerServices; + +namespace SeedApi; + +/// +/// A task-like type that wraps Task<WithRawResponse<T>> and provides dual-mode awaiting: +/// - Direct await yields just T (zero-allocation path for common case) +/// - .WithRawResponse() yields WithRawResponse<T> (when raw response metadata is needed) +/// +/// The type of the parsed response data. +public readonly struct WithRawResponseTask +{ + private readonly global::System.Threading.Tasks.Task> _task; + + /// + /// Creates a new WithRawResponseTask wrapping the given task. + /// + public WithRawResponseTask(global::System.Threading.Tasks.Task> task) + { + _task = task; + } + + /// + /// Returns the underlying task that yields both the data and raw response metadata. + /// + public global::System.Threading.Tasks.Task> WithRawResponse() => _task; + + /// + /// Gets the custom awaiter that unwraps to just T when awaited. + /// + public Awaiter GetAwaiter() => new(_task.GetAwaiter()); + + /// + /// Configures the awaiter to continue on the captured context or not. + /// + public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => + new(_task.ConfigureAwait(continueOnCapturedContext)); + + /// + /// Implicitly converts WithRawResponseTask<T> to global::System.Threading.Tasks.Task<T> for backward compatibility. + /// The resulting task will yield just the data when awaited. + /// + public static implicit operator global::System.Threading.Tasks.Task( + WithRawResponseTask task + ) + { + return task._task.ContinueWith( + t => t.Result.Data, + TaskContinuationOptions.ExecuteSynchronously + ); + } + + /// + /// Custom awaiter that unwraps WithRawResponse<T> to just T. + /// + public readonly struct Awaiter : ICriticalNotifyCompletion + { + private readonly TaskAwaiter> _awaiter; + + internal Awaiter(TaskAwaiter> awaiter) + { + _awaiter = awaiter; + } + + /// + /// Gets whether the underlying task has completed. + /// + public bool IsCompleted => _awaiter.IsCompleted; + + /// + /// Gets the result, unwrapping to just the data. + /// + public T GetResult() => _awaiter.GetResult().Data; + + /// + /// Schedules the continuation action. + /// + public void OnCompleted(global::System.Action continuation) => + _awaiter.OnCompleted(continuation); + + /// + /// Schedules the continuation action without capturing the execution context. + /// + public void UnsafeOnCompleted(global::System.Action continuation) => + _awaiter.UnsafeOnCompleted(continuation); + } + + /// + /// Awaitable type returned by ConfigureAwait that unwraps to just T. + /// + public readonly struct ConfiguredTaskAwaitable + { + private readonly ConfiguredTaskAwaitable> _configuredTask; + + internal ConfiguredTaskAwaitable(ConfiguredTaskAwaitable> configuredTask) + { + _configuredTask = configuredTask; + } + + /// + /// Gets the configured awaiter that unwraps to just T. + /// + public ConfiguredAwaiter GetAwaiter() => new(_configuredTask.GetAwaiter()); + + /// + /// Custom configured awaiter that unwraps WithRawResponse<T> to just T. + /// + public readonly struct ConfiguredAwaiter : ICriticalNotifyCompletion + { + private readonly ConfiguredTaskAwaitable< + WithRawResponse + >.ConfiguredTaskAwaiter _awaiter; + + internal ConfiguredAwaiter( + ConfiguredTaskAwaitable>.ConfiguredTaskAwaiter awaiter + ) + { + _awaiter = awaiter; + } + + /// + /// Gets whether the underlying task has completed. + /// + public bool IsCompleted => _awaiter.IsCompleted; + + /// + /// Gets the result, unwrapping to just the data. + /// + public T GetResult() => _awaiter.GetResult().Data; + + /// + /// Schedules the continuation action. + /// + public void OnCompleted(global::System.Action continuation) => + _awaiter.OnCompleted(continuation); + + /// + /// Schedules the continuation action without capturing the execution context. + /// + public void UnsafeOnCompleted(global::System.Action continuation) => + _awaiter.UnsafeOnCompleted(continuation); + } + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs new file mode 100644 index 000000000000..9498488a311b --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs @@ -0,0 +1,654 @@ +using global::System.Buffers; +using global::System.Runtime.CompilerServices; +#if !NET6_0_OR_GREATER +using global::System.Text; +#endif + +namespace SeedApi.Core; + +/// +/// High-performance query string builder with RFC 3986 compliant percent-encoding. +/// Uses span-based APIs on .NET 6+ and StringBuilder fallback for older targets. +/// +/// RFC 3986 defines the following relevant productions: +/// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +/// query = *( pchar / "/" / "?" ) +/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +/// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +/// +/// Three encoding contexts are distinguished: +/// Path segment (pchar): unreserved + sub-delims + ":" + "@" +/// Query key: query chars minus "&", "=", "+", "#" +/// Query value: query chars minus "&", "+", "#" +/// +internal static class QueryStringBuilder +{ + // ────────────────────────────────────────────────────────────────────── + // RFC 3986 character sets + // + // Query key safe: unreserved + (sub-delims \ {& = +}) + : @ / ? + // Query value safe: unreserved + (sub-delims \ {& +}) + : @ / ? + // Path segment safe: unreserved + sub-delims + : @ + // ────────────────────────────────────────────────────────────────────── + +#if NET8_0_OR_GREATER + private static readonly SearchValues SafeQueryKeyChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?" + ); + + private static readonly SearchValues SafeQueryValueChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?" + ); + + private static readonly SearchValues SafePathChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@" + ); +#else + private const string SafeQueryKeyChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?"; + + private const string SafeQueryValueChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?"; + + private const string SafePathChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@"; +#endif + +#if NET7_0_OR_GREATER + private static ReadOnlySpan UpperHexChars => "0123456789ABCDEF"u8; +#else + private static readonly byte[] UpperHexChars = + { + (byte)'0', + (byte)'1', + (byte)'2', + (byte)'3', + (byte)'4', + (byte)'5', + (byte)'6', + (byte)'7', + (byte)'8', + (byte)'9', + (byte)'A', + (byte)'B', + (byte)'C', + (byte)'D', + (byte)'E', + (byte)'F', + }; +#endif + + private enum EncodingContext + { + QueryKey, + QueryValue, + Path, + } + + /// + /// Percent-encodes a path segment value per RFC 3986 section 3.3 (pchar). + /// Allowed unencoded: unreserved / sub-delims / ":" / "@" + /// + public static string EncodePathSegment(string value) + { + if (string.IsNullOrEmpty(value)) + return value; + +#if NET6_0_OR_GREATER + if (!NeedsEncoding(value.AsSpan(), EncodingContext.Path)) + return value; + + var buffer = ArrayPool.Shared.Rent(value.Length * 3); + try + { + var written = EncodeSlow(value.AsSpan(), buffer.AsSpan(), EncodingContext.Path); + return new string(buffer.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } +#else + var sb = new StringBuilder(value.Length); + AppendEncoded(sb, value, EncodingContext.Path); + return sb.ToString(); +#endif + } + + /// + /// Builds a query string from the provided parameters. + /// +#if NET6_0_OR_GREATER + public static string Build(ReadOnlySpan> parameters) + { + if (parameters.IsEmpty) + return string.Empty; + + var estimatedLength = EstimateLength(parameters); + if (estimatedLength == 0) + return string.Empty; + + var bufferSize = Math.Min(estimatedLength * 3, 8192); + var buffer = ArrayPool.Shared.Rent(bufferSize); + + try + { + var written = BuildCore(parameters, buffer); + return new string(buffer.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + private static int EstimateLength(ReadOnlySpan> parameters) + { + var estimatedLength = 0; + foreach (var kvp in parameters) + { + estimatedLength += kvp.Key.Length + kvp.Value.Length + 2; + } + return estimatedLength; + } +#endif + + /// + /// Builds a query string from the provided parameters. + /// + public static string Build(IEnumerable> parameters) + { +#if NET6_0_OR_GREATER + // Try to get span access for collections that support it + if (parameters is ICollection> collection) + { + if (collection.Count == 0) + return string.Empty; + + var array = ArrayPool>.Shared.Rent(collection.Count); + try + { + collection.CopyTo(array, 0); + return Build(array.AsSpan(0, collection.Count)); + } + finally + { + ArrayPool>.Shared.Return(array); + } + } + + // Fallback for non-collection enumerables + using var enumerator = parameters.GetEnumerator(); + if (!enumerator.MoveNext()) + return string.Empty; + + var buffer = ArrayPool.Shared.Rent(4096); + try + { + var position = 0; + var first = true; + + do + { + var kvp = enumerator.Current; + + // Ensure capacity (worst case: 3x for encoding + separators) + var required = (kvp.Key.Length + kvp.Value.Length + 2) * 3; + if (position + required > buffer.Length) + { + var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); + buffer.AsSpan(0, position).CopyTo(newBuffer); + ArrayPool.Shared.Return(buffer); + buffer = newBuffer; + } + + buffer[position++] = first ? '?' : '&'; + first = false; + + position += EncodeWithCharSet( + kvp.Key.AsSpan(), + buffer.AsSpan(position), + EncodingContext.QueryKey + ); + buffer[position++] = '='; + position += EncodeWithCharSet( + kvp.Value.AsSpan(), + buffer.AsSpan(position), + EncodingContext.QueryValue + ); + } while (enumerator.MoveNext()); + + return first ? string.Empty : new string(buffer.AsSpan(0, position)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } +#else + // netstandard2.0 / net462 fallback using StringBuilder + var sb = new StringBuilder(); + var first = true; + + foreach (var kvp in parameters) + { + sb.Append(first ? '?' : '&'); + first = false; + + AppendEncoded(sb, kvp.Key, EncodingContext.QueryKey); + sb.Append('='); + AppendEncoded(sb, kvp.Value, EncodingContext.QueryValue); + } + + return sb.ToString(); +#endif + } + +#if NET6_0_OR_GREATER + private static int BuildCore( + ReadOnlySpan> parameters, + Span buffer + ) + { + var position = 0; + var first = true; + + foreach (var kvp in parameters) + { + buffer[position++] = first ? '?' : '&'; + first = false; + + position += EncodeWithCharSet( + kvp.Key.AsSpan(), + buffer.Slice(position), + EncodingContext.QueryKey + ); + buffer[position++] = '='; + position += EncodeWithCharSet( + kvp.Value.AsSpan(), + buffer.Slice(position), + EncodingContext.QueryValue + ); + } + + return position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EncodeWithCharSet( + ReadOnlySpan input, + Span output, + EncodingContext context + ) + { + if (!NeedsEncoding(input, context)) + { + input.CopyTo(output); + return input.Length; + } + + return EncodeSlow(input, output, context); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool NeedsEncoding(ReadOnlySpan value, EncodingContext context) + { + return context switch + { + EncodingContext.QueryKey => value.ContainsAnyExcept(SafeQueryKeyChars), + EncodingContext.QueryValue => value.ContainsAnyExcept(SafeQueryValueChars), + EncodingContext.Path => value.ContainsAnyExcept(SafePathChars), + _ => true, + }; + } + + private static int EncodeSlow( + ReadOnlySpan input, + Span output, + EncodingContext context + ) + { + var position = 0; + + foreach (var c in input) + { + if (IsSafeChar(c, context)) + { + output[position++] = c; + } + else if (c == ' ') + { + output[position++] = '%'; + output[position++] = '2'; + output[position++] = '0'; + } + else if (char.IsAscii(c)) + { + position += EncodeAscii((byte)c, output.Slice(position)); + } + else + { + position += EncodeUtf8(c, output.Slice(position)); + } + } + + return position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EncodeAscii(byte value, Span output) + { + output[0] = '%'; + output[1] = (char)UpperHexChars[value >> 4]; + output[2] = (char)UpperHexChars[value & 0xF]; + return 3; + } + + private static int EncodeUtf8(char c, Span output) + { + Span utf8Bytes = stackalloc byte[4]; + Span singleChar = stackalloc char[1] { c }; + var byteCount = global::System.Text.Encoding.UTF8.GetBytes(singleChar, utf8Bytes); + + var position = 0; + for (var i = 0; i < byteCount; i++) + { + output[position++] = '%'; + output[position++] = (char)UpperHexChars[utf8Bytes[i] >> 4]; + output[position++] = (char)UpperHexChars[utf8Bytes[i] & 0xF]; + } + + return position; + } +#else + // netstandard2.0 / net462 StringBuilder-based encoding + private static void AppendEncoded(StringBuilder sb, string value, EncodingContext context) + { + foreach (var c in value) + { + if (IsSafeChar(c, context)) + { + sb.Append(c); + } + else if (c == ' ') + { + sb.Append("%20"); + } + else if (c <= 127) + { + AppendPercentEncoded(sb, (byte)c); + } + else + { + var bytes = Encoding.UTF8.GetBytes(new[] { c }); + foreach (var b in bytes) + { + AppendPercentEncoded(sb, b); + } + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendPercentEncoded(StringBuilder sb, byte value) + { + sb.Append('%'); + sb.Append((char)UpperHexChars[value >> 4]); + sb.Append((char)UpperHexChars[value & 0xF]); + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeChar(char c, EncodingContext context) + { + return context switch + { + EncodingContext.QueryKey => IsSafeQueryKeyChar(c), + EncodingContext.QueryValue => IsSafeQueryValueChar(c), + EncodingContext.Path => IsSafePathChar(c), + _ => false, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeQueryKeyChar(char c) + { +#if NET8_0_OR_GREATER + return SafeQueryKeyChars.Contains(c); +#else + // query = *( pchar / "/" / "?" ) minus "&", "=", "+", "#" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == ',' + || c == ';' + || c == ':' + || c == '@' + || c == '/' + || c == '?'; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeQueryValueChar(char c) + { +#if NET8_0_OR_GREATER + return SafeQueryValueChars.Contains(c); +#else + // query = *( pchar / "/" / "?" ) minus "&", "+", "#" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == ',' + || c == ';' + || c == '=' + || c == ':' + || c == '@' + || c == '/' + || c == '?'; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafePathChar(char c) + { +#if NET8_0_OR_GREATER + return SafePathChars.Contains(c); +#else + // pchar = unreserved / sub-delims / ":" / "@" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == '&' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == '+' + || c == ',' + || c == ';' + || c == '=' + || c == ':' + || c == '@'; +#endif + } + + /// + /// Fluent builder for constructing query strings with support for simple parameters and deep object notation. + /// + public sealed class Builder + { + private readonly List> _params; + + /// + /// Initializes a new instance with default capacity. + /// + public Builder() + { + _params = new List>(); + } + + /// + /// Initializes a new instance with the specified initial capacity. + /// + public Builder(int capacity) + { + _params = new List>(capacity); + } + + /// + /// Adds a simple parameter. For collections, adds multiple key-value pairs (one per element). + /// + public Builder Add(string key, object? value) + { + if (value is null) + { + return this; + } + + // Handle string separately since it implements IEnumerable + if (value is string stringValue) + { + _params.Add(new KeyValuePair(key, stringValue)); + return this; + } + + // Handle collections (arrays, lists, etc.) - add each element as a separate key-value pair + if ( + value + is global::System.Collections.IEnumerable enumerable + and not global::System.Collections.IDictionary + ) + { + foreach (var item in enumerable) + { + if (item is not null) + { + _params.Add( + new KeyValuePair( + key, + ValueConvert.ToQueryStringValue(item) + ) + ); + } + } + return this; + } + + // Handle scalar values + _params.Add( + new KeyValuePair(key, ValueConvert.ToQueryStringValue(value)) + ); + return this; + } + + /// + /// Sets a parameter, removing any existing parameters with the same key before adding the new value. + /// For collections, removes all existing parameters with the key, then adds multiple key-value pairs (one per element). + /// This allows overriding parameters set earlier in the builder. + /// + public Builder Set(string key, object? value) + { + // Remove all existing parameters with this key + _params.RemoveAll(kv => kv.Key == key); + + // Add the new value(s) + return Add(key, value); + } + + /// + /// Merges additional query parameters with override semantics. + /// Groups parameters by key and calls Set() once per unique key. + /// This ensures that parameters with the same key are properly merged: + /// - If a key appears once, it's added as a single value + /// - If a key appears multiple times, all values are added as an array + /// - All parameters override any existing parameters with the same key + /// + public Builder MergeAdditional( + global::System.Collections.Generic.IEnumerable>? additionalParameters + ) + { + if (additionalParameters is null) + { + return this; + } + + // Group by key to handle multiple values for the same key correctly + var grouped = additionalParameters + .GroupBy(kv => kv.Key) + .Select(g => new global::System.Collections.Generic.KeyValuePair( + g.Key, + g.Count() == 1 ? (object)g.First().Value : g.Select(kv => kv.Value).ToArray() + )); + + foreach (var param in grouped) + { + Set(param.Key, param.Value); + } + + return this; + } + + /// + /// Adds a complex object using deep object notation with a prefix. + /// Deep object notation nests properties with brackets: prefix[key][nested]=value + /// + public Builder AddDeepObject(string prefix, object? value) + { + if (value is not null) + { + _params.AddRange(QueryStringConverter.ToDeepObject(prefix, value)); + } + return this; + } + + /// + /// Adds a complex object using exploded form notation with an optional prefix. + /// Exploded form flattens properties: prefix[key]=value (no deep nesting). + /// + public Builder AddExploded(string prefix, object? value) + { + if (value is not null) + { + _params.AddRange(QueryStringConverter.ToExplodedForm(prefix, value)); + } + return this; + } + + /// + /// Builds the final query string. + /// + public string Build() + { + return QueryStringBuilder.Build(_params); + } + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs new file mode 100644 index 000000000000..be39e2f51aa4 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs @@ -0,0 +1,259 @@ +using global::System.Text.Json; + +namespace SeedApi.Core; + +/// +/// Converts an object into a query string collection. +/// +internal static class QueryStringConverter +{ + /// + /// Converts an object into a query string collection using Deep Object notation with a prefix. + /// + /// The prefix to prepend to all keys (e.g., "session_settings"). Pass empty string for no prefix. + /// Object to form URL-encode. Can be an object, array of objects, or dictionary. + /// Throws when passing in a string or primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToDeepObject( + string prefix, + object value + ) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + JsonToDeepObject(json, prefix, queryCollection); + return queryCollection; + } + + /// + /// Converts an object into a query string collection using Deep Object notation. + /// + /// Object to form URL-encode. Can be an object, array of objects, or dictionary. + /// Throws when passing in a string or primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToDeepObject(object value) + { + return ToDeepObject("", value); + } + + /// + /// Converts an object into a query string collection using Exploded Form notation with a prefix. + /// + /// The prefix to prepend to all keys. Pass empty string for no prefix. + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToExplodedForm( + string prefix, + object value + ) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + AssertRootJson(json); + JsonToFormExploded(json, prefix, queryCollection); + return queryCollection; + } + + /// + /// Converts an object into a query string collection using Exploded Form notation. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToExplodedForm(object value) + { + return ToExplodedForm("", value); + } + + /// + /// Converts an object into a query string collection using Form notation without exploding parameters. + /// + /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. + /// Throws when passing in a list, a string, or a primitive value. + /// A collection of key value pairs. The keys and values are not URL encoded. + internal static IEnumerable> ToForm(object value) + { + var queryCollection = new List>(); + var json = JsonUtils.SerializeToElement(value); + AssertRootJson(json); + JsonToForm(json, "", queryCollection); + return queryCollection; + } + + private static void AssertRootJson(JsonElement json) + { + switch (json.ValueKind) + { + case JsonValueKind.Object: + break; + case JsonValueKind.Array: + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + default: + throw new global::System.Exception( + $"Only objects can be converted to query string collections. Given type is {json.ValueKind}." + ); + } + } + + private static void JsonToForm( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToForm(property.Value, newPrefix, parameters); + } + break; + case JsonValueKind.Array: + var arrayValues = element.EnumerateArray().Select(ValueToString).ToArray(); + parameters.Add( + new KeyValuePair(prefix, string.Join(",", arrayValues)) + ); + break; + case JsonValueKind.Null: + break; + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static void JsonToFormExploded( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToFormExploded(property.Value, newPrefix, parameters); + } + + break; + case JsonValueKind.Array: + foreach (var item in element.EnumerateArray()) + { + if ( + item.ValueKind != JsonValueKind.Object + && item.ValueKind != JsonValueKind.Array + ) + { + parameters.Add( + new KeyValuePair(prefix, ValueToString(item)) + ); + } + else + { + JsonToFormExploded(item, prefix, parameters); + } + } + + break; + case JsonValueKind.Null: + break; + case JsonValueKind.Undefined: + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static void JsonToDeepObject( + JsonElement element, + string prefix, + List> parameters + ) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + var newPrefix = string.IsNullOrEmpty(prefix) + ? property.Name + : $"{prefix}[{property.Name}]"; + + JsonToDeepObject(property.Value, newPrefix, parameters); + } + + break; + case JsonValueKind.Array: + var index = 0; + foreach (var item in element.EnumerateArray()) + { + var newPrefix = $"{prefix}[{index++}]"; + + if ( + item.ValueKind != JsonValueKind.Object + && item.ValueKind != JsonValueKind.Array + ) + { + parameters.Add( + new KeyValuePair(newPrefix, ValueToString(item)) + ); + } + else + { + JsonToDeepObject(item, newPrefix, parameters); + } + } + + break; + case JsonValueKind.Null: + case JsonValueKind.Undefined: + // Skip null and undefined values - don't add parameters for them + break; + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + default: + parameters.Add(new KeyValuePair(prefix, ValueToString(element))); + break; + } + } + + private static string ValueToString(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString() ?? "", + JsonValueKind.Number => element.GetRawText(), + JsonValueKind.True => "true", + JsonValueKind.False => "false", + JsonValueKind.Null => "", + _ => element.GetRawText(), + }; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs new file mode 100644 index 000000000000..edf973008a15 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs @@ -0,0 +1,343 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; +using global::System.Text; +using SystemTask = global::System.Threading.Tasks.Task; + +namespace SeedApi.Core; + +/// +/// Utility class for making raw HTTP requests to the API. +/// +internal partial class RawClient(ClientOptions clientOptions) +{ + private const int MaxRetryDelayMs = 60000; + private const double JitterFactor = 0.2; +#if NET6_0_OR_GREATER + // Use Random.Shared for thread-safe random number generation on .NET 6+ +#else + private static readonly object JitterLock = new(); + private static readonly Random JitterRandom = new(); +#endif + internal int BaseRetryDelay { get; set; } = 1000; + + /// + /// The client options applied on every request. + /// + internal readonly ClientOptions Options = clientOptions; + + internal async global::System.Threading.Tasks.Task SendRequestAsync( + global::SeedApi.Core.BaseRequest request, + CancellationToken cancellationToken = default + ) + { + // Apply the request timeout. + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = request.Options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); + + var httpRequest = await CreateHttpRequestAsync(request).ConfigureAwait(false); + // Send the request. + return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token) + .ConfigureAwait(false); + } + + internal async global::System.Threading.Tasks.Task SendRequestAsync( + HttpRequestMessage request, + IRequestOptions? options, + CancellationToken cancellationToken = default + ) + { + // Apply the request timeout. + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); + + // Send the request. + return await SendWithRetriesAsync(request, options, cts.Token).ConfigureAwait(false); + } + + private static async global::System.Threading.Tasks.Task CloneRequestAsync( + HttpRequestMessage request + ) + { + var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri); + clonedRequest.Version = request.Version; + + if (request.Content != null) + { + switch (request.Content) + { + case MultipartContent oldMultipartFormContent: + var originalBoundary = + oldMultipartFormContent + .Headers.ContentType?.Parameters.First(p => + p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) + ) + .Value?.Trim('"') ?? Guid.NewGuid().ToString(); + var newMultipartContent = oldMultipartFormContent switch + { + MultipartFormDataContent => new MultipartFormDataContent(originalBoundary), + _ => new MultipartContent(), + }; + foreach (var content in oldMultipartFormContent) + { + var ms = new MemoryStream(); + await content.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + var newPart = new StreamContent(ms); + foreach (var header in content.Headers) + { + newPart.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + newMultipartContent.Add(newPart); + } + + clonedRequest.Content = newMultipartContent; + break; + default: + var bodyStream = new MemoryStream(); + await request.Content.CopyToAsync(bodyStream).ConfigureAwait(false); + bodyStream.Position = 0; + var clonedContent = new StreamContent(bodyStream); + foreach (var header in request.Content.Headers) + { + clonedContent.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + clonedRequest.Content = clonedContent; + break; + } + } + + foreach (var header in request.Headers) + { + clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return clonedRequest; + } + + /// + /// Sends the request with retries, unless the request content is not retryable, + /// such as stream requests and multipart form data with stream content. + /// + private async global::System.Threading.Tasks.Task SendWithRetriesAsync( + HttpRequestMessage request, + IRequestOptions? options, + CancellationToken cancellationToken + ) + { + var httpClient = options?.HttpClient ?? Options.HttpClient; + var maxRetries = options?.MaxRetries ?? Options.MaxRetries; + var response = await httpClient + .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + var isRetryableContent = IsRetryableContent(request); + + if (!isRetryableContent) + { + return new global::SeedApi.Core.ApiResponse + { + StatusCode = (int)response.StatusCode, + Raw = response, + }; + } + + for (var i = 0; i < maxRetries; i++) + { + if (!ShouldRetry(response)) + { + break; + } + + var delayMs = GetRetryDelayFromHeaders(response, i); + await SystemTask.Delay(delayMs, cancellationToken).ConfigureAwait(false); + using var retryRequest = await CloneRequestAsync(request).ConfigureAwait(false); + response = await httpClient + .SendAsync( + retryRequest, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken + ) + .ConfigureAwait(false); + } + + return new global::SeedApi.Core.ApiResponse + { + StatusCode = (int)response.StatusCode, + Raw = response, + }; + } + + private static bool ShouldRetry(HttpResponseMessage response) + { + var statusCode = (int)response.StatusCode; + return statusCode is 408 or 429 or >= 500; + } + + private static int AddPositiveJitter(int delayMs) + { +#if NET6_0_OR_GREATER + var random = Random.Shared.NextDouble(); +#else + double random; + lock (JitterLock) + { + random = JitterRandom.NextDouble(); + } +#endif + var jitterMultiplier = 1 + random * JitterFactor; + return (int)(delayMs * jitterMultiplier); + } + + private static int AddSymmetricJitter(int delayMs) + { +#if NET6_0_OR_GREATER + var random = Random.Shared.NextDouble(); +#else + double random; + lock (JitterLock) + { + random = JitterRandom.NextDouble(); + } +#endif + var jitterMultiplier = 1 + (random - 0.5) * JitterFactor; + return (int)(delayMs * jitterMultiplier); + } + + private int GetRetryDelayFromHeaders(HttpResponseMessage response, int retryAttempt) + { + if (response.Headers.TryGetValues("Retry-After", out var retryAfterValues)) + { + var retryAfter = retryAfterValues.FirstOrDefault(); + if (!string.IsNullOrEmpty(retryAfter)) + { + if (int.TryParse(retryAfter, out var retryAfterSeconds) && retryAfterSeconds > 0) + { + return Math.Min(retryAfterSeconds * 1000, MaxRetryDelayMs); + } + + if (DateTimeOffset.TryParse(retryAfter, out var retryAfterDate)) + { + var delay = (int)(retryAfterDate - DateTimeOffset.UtcNow).TotalMilliseconds; + if (delay > 0) + { + return Math.Min(delay, MaxRetryDelayMs); + } + } + } + } + + if (response.Headers.TryGetValues("X-RateLimit-Reset", out var rateLimitResetValues)) + { + var rateLimitReset = rateLimitResetValues.FirstOrDefault(); + if ( + !string.IsNullOrEmpty(rateLimitReset) + && long.TryParse(rateLimitReset, out var resetTime) + ) + { + var resetDateTime = DateTimeOffset.FromUnixTimeSeconds(resetTime); + var delay = (int)(resetDateTime - DateTimeOffset.UtcNow).TotalMilliseconds; + if (delay > 0) + { + return AddPositiveJitter(Math.Min(delay, MaxRetryDelayMs)); + } + } + } + + var exponentialDelay = Math.Min(BaseRetryDelay * (1 << retryAttempt), MaxRetryDelayMs); + return AddSymmetricJitter(exponentialDelay); + } + + private static bool IsRetryableContent(HttpRequestMessage request) + { + return request.Content switch + { + IIsRetryableContent c => c.IsRetryable, + StreamContent => false, + MultipartContent content => !content.Any(c => c is StreamContent), + _ => true, + }; + } + + internal async global::System.Threading.Tasks.Task CreateHttpRequestAsync( + global::SeedApi.Core.BaseRequest request + ) + { + var url = BuildUrl(request); + var httpRequest = new HttpRequestMessage(request.Method, url); + httpRequest.Content = request.CreateContent(); + SetHeaders(httpRequest, request.Headers); + + return httpRequest; + } + + private string BuildUrl(global::SeedApi.Core.BaseRequest request) + { + var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl ?? Options.BaseUrl; + + var trimmedBaseUrl = baseUrl.TrimEnd('/'); + var trimmedBasePath = request.Path.TrimStart('/'); + var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; + + // Append query string if present + if (!string.IsNullOrEmpty(request.QueryString)) + { + return url + request.QueryString; + } + + return url; + } + + private void SetHeaders(HttpRequestMessage httpRequest, Dictionary? headers) + { + if (headers is null) + { + return; + } + + foreach (var kv in headers) + { + if (kv.Value is null) + { + continue; + } + + httpRequest.Headers.TryAddWithoutValidation(kv.Key, kv.Value); + } + } + + private static (Encoding encoding, string? charset, string mediaType) ParseContentTypeOrDefault( + string? contentType, + Encoding encodingFallback, + string mediaTypeFallback + ) + { + var encoding = encodingFallback; + var mediaType = mediaTypeFallback; + string? charset = null; + if (string.IsNullOrEmpty(contentType)) + { + return (encoding, charset, mediaType); + } + + if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) + { + return (encoding, charset, mediaType); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) + { + charset = mediaTypeHeaderValue.CharSet; + encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); + } + + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) + { + mediaType = mediaTypeHeaderValue.MediaType; + } + + return (encoding, charset, mediaType); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs new file mode 100644 index 000000000000..5fc790dcc6f1 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs @@ -0,0 +1,24 @@ +using global::System.Net; + +namespace SeedApi.Core; + +/// +/// Contains HTTP response metadata including status code, URL, and headers. +/// +public record RawResponse +{ + /// + /// The HTTP status code of the response. + /// + public required HttpStatusCode StatusCode { get; init; } + + /// + /// The request URL that generated this response. + /// + public required Uri Url { get; init; } + + /// + /// The HTTP response headers. + /// + public required Core.ResponseHeaders Headers { get; init; } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs new file mode 100644 index 000000000000..2c394dd9a7a9 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs @@ -0,0 +1,108 @@ +using global::System.Collections; +using global::System.Net.Http.Headers; + +namespace SeedApi.Core; + +/// +/// Represents HTTP response headers with case-insensitive lookup. +/// +public readonly struct ResponseHeaders : IEnumerable +{ + private readonly HttpResponseHeaders? _headers; + private readonly HttpContentHeaders? _contentHeaders; + + private ResponseHeaders(HttpResponseHeaders headers, HttpContentHeaders? contentHeaders) + { + _headers = headers; + _contentHeaders = contentHeaders; + } + + /// + /// Gets the Content-Type header value, if present. + /// + public string? ContentType => _contentHeaders?.ContentType?.ToString(); + + /// + /// Gets the Content-Length header value, if present. + /// + public long? ContentLength => _contentHeaders?.ContentLength; + + /// + /// Creates a ResponseHeaders instance from an HttpResponseMessage. + /// + public static ResponseHeaders FromHttpResponseMessage(HttpResponseMessage response) + { + return new ResponseHeaders(response.Headers, response.Content?.Headers); + } + + /// + /// Tries to get a single header value. Returns the first value if multiple values exist. + /// + public bool TryGetValue(string name, out string? value) + { + if (TryGetValues(name, out var values) && values is not null) + { + value = values.FirstOrDefault(); + return true; + } + + value = null; + return false; + } + + /// + /// Tries to get all values for a header. + /// + public bool TryGetValues(string name, out IEnumerable? values) + { + if (_headers?.TryGetValues(name, out values) == true) + { + return true; + } + + if (_contentHeaders?.TryGetValues(name, out values) == true) + { + return true; + } + + values = null; + return false; + } + + /// + /// Checks if the headers contain a specific header name. + /// + public bool Contains(string name) + { + return _headers?.Contains(name) == true || _contentHeaders?.Contains(name) == true; + } + + /// + /// Gets an enumerator for all headers. + /// + public IEnumerator GetEnumerator() + { + if (_headers is not null) + { + foreach (var header in _headers) + { + yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); + } + } + + if (_contentHeaders is not null) + { + foreach (var header in _contentHeaders) + { + yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +/// +/// Represents a single HTTP header. +/// +public readonly record struct HttpHeader(string Name, string Value); diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs new file mode 100644 index 000000000000..54076b686602 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs @@ -0,0 +1,29 @@ +using global::System.Net.Http; +using global::System.Net.Http.Headers; + +namespace SeedApi.Core; + +/// +/// The request object to be sent for streaming uploads. +/// +internal record StreamRequest : BaseRequest +{ + internal Stream? Body { get; init; } + + internal override HttpContent? CreateContent() + { + if (Body is null) + { + return null; + } + + var content = new StreamContent(Body) + { + Headers = + { + ContentType = MediaTypeHeaderValue.Parse(ContentType ?? "application/octet-stream"), + }, + }; + return content; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs new file mode 100644 index 000000000000..9f1f4a1c1181 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +public interface IStringEnum : IEquatable +{ + public string Value { get; } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs new file mode 100644 index 000000000000..704cb6836ab8 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +internal static class StringEnumExtensions +{ + public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs new file mode 100644 index 000000000000..ba1f818399e2 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs @@ -0,0 +1,115 @@ +using global::System.Globalization; + +namespace SeedApi.Core; + +/// +/// Convert values to string for path and query parameters. +/// +public static class ValueConvert +{ + internal static string ToPathParameterString(T value) => ToString(value); + + internal static string ToPathParameterString(bool v) => ToString(v); + + internal static string ToPathParameterString(int v) => ToString(v); + + internal static string ToPathParameterString(long v) => ToString(v); + + internal static string ToPathParameterString(float v) => ToString(v); + + internal static string ToPathParameterString(double v) => ToString(v); + + internal static string ToPathParameterString(decimal v) => ToString(v); + + internal static string ToPathParameterString(short v) => ToString(v); + + internal static string ToPathParameterString(ushort v) => ToString(v); + + internal static string ToPathParameterString(uint v) => ToString(v); + + internal static string ToPathParameterString(ulong v) => ToString(v); + + internal static string ToPathParameterString(string v) => + QueryStringBuilder.EncodePathSegment(v); + + internal static string ToPathParameterString(char v) => ToString(v); + + internal static string ToPathParameterString(Guid v) => ToString(v); + + internal static string ToQueryStringValue(T value) => value is null ? "" : ToString(value); + + internal static string ToQueryStringValue(bool v) => ToString(v); + + internal static string ToQueryStringValue(int v) => ToString(v); + + internal static string ToQueryStringValue(long v) => ToString(v); + + internal static string ToQueryStringValue(float v) => ToString(v); + + internal static string ToQueryStringValue(double v) => ToString(v); + + internal static string ToQueryStringValue(decimal v) => ToString(v); + + internal static string ToQueryStringValue(short v) => ToString(v); + + internal static string ToQueryStringValue(ushort v) => ToString(v); + + internal static string ToQueryStringValue(uint v) => ToString(v); + + internal static string ToQueryStringValue(ulong v) => ToString(v); + + internal static string ToQueryStringValue(string v) => v is null ? "" : v; + + internal static string ToQueryStringValue(char v) => ToString(v); + + internal static string ToQueryStringValue(Guid v) => ToString(v); + + internal static string ToString(T value) + { + return value switch + { + null => "null", + string str => str, + true => "true", + false => "false", + int i => ToString(i), + long l => ToString(l), + float f => ToString(f), + double d => ToString(d), + decimal dec => ToString(dec), + short s => ToString(s), + ushort u => ToString(u), + uint u => ToString(u), + ulong u => ToString(u), + char c => ToString(c), + Guid guid => ToString(guid), + _ => JsonUtils.SerializeRelaxedEscaping(value, value.GetType()).Trim('"'), + }; + } + + internal static string ToString(bool v) => v ? "true" : "false"; + + internal static string ToString(int v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(long v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(float v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(double v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(decimal v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(short v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(ushort v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(uint v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(ulong v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(char v) => v.ToString(CultureInfo.InvariantCulture); + + internal static string ToString(string v) => v; + + internal static string ToString(Guid v) => v.ToString("D"); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs b/seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs new file mode 100644 index 000000000000..4b1eb3e650c8 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs @@ -0,0 +1,31 @@ +namespace SeedApi; + +public partial interface ISeedApiClient +{ + WithRawResponseTask SearchRuleTypesAsync( + SearchRuleTypesRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask CreateRuleAsync( + RuleCreateRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask ListUsersAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask GetEntityAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); + + WithRawResponseTask GetOrganizationAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ); +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs new file mode 100644 index 000000000000..6437574c29a8 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs @@ -0,0 +1,20 @@ +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleCreateRequest +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("executionContext")] + public required RuleExecutionContext ExecutionContext { get; set; } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs new file mode 100644 index 000000000000..3c1562150a13 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs @@ -0,0 +1,17 @@ +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record SearchRuleTypesRequest +{ + [JsonIgnore] + public string? Query { get; set; } + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props b/seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props new file mode 100644 index 000000000000..17a84cada530 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props @@ -0,0 +1,20 @@ + + + + diff --git a/seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj b/seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj new file mode 100644 index 000000000000..59a0ea188a9c --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj @@ -0,0 +1,56 @@ + + + net462;net8.0;net9.0;netstandard2.0 + enable + 12 + enable + 0.0.1 + $(Version) + $(Version) + README.md + https://github.com/allof/fern + true + + + false + + + $(DefineConstants);USE_PORTABLE_DATE_ONLY + true + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + <_Parameter1>SeedApi.Test + + + + diff --git a/seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs b/seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs new file mode 100644 index 000000000000..16920940ab90 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs @@ -0,0 +1,429 @@ +using global::System.Text.Json; +using SeedApi.Core; + +namespace SeedApi; + +public partial class SeedApiClient : ISeedApiClient +{ + private readonly RawClient _client; + + public SeedApiClient(ClientOptions? clientOptions = null) + { + clientOptions ??= new ClientOptions(); + var platformHeaders = new Headers( + new Dictionary() + { + { "X-Fern-Language", "C#" }, + { "X-Fern-SDK-Name", "SeedApi" }, + { "X-Fern-SDK-Version", Version.Current }, + { "User-Agent", "Fernallof/0.0.1" }, + } + ); + foreach (var header in platformHeaders) + { + if (!clientOptions.Headers.ContainsKey(header.Key)) + { + clientOptions.Headers[header.Key] = header.Value; + } + } + _client = new RawClient(clientOptions); + } + + private async Task> SearchRuleTypesAsyncCore( + SearchRuleTypesRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _queryString = new SeedApi.Core.QueryStringBuilder.Builder(capacity: 1) + .Add("query", request.Query) + .MergeAdditional(options?.AdditionalQueryParameters) + .Build(); + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "rule-types", + QueryString = _queryString, + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> CreateRuleAsyncCore( + RuleCreateRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Post, + Path = "rules", + Body = request, + Headers = _headers, + ContentType = "application/json", + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> ListUsersAsyncCore( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "users", + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> GetEntityAsyncCore( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "entities", + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + private async Task> GetOrganizationAsyncCore( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var _headers = await new SeedApi.Core.HeadersBuilder.Builder() + .Add(_client.Options.Headers) + .Add(_client.Options.AdditionalHeaders) + .Add(options?.AdditionalHeaders) + .BuildAsync() + .ConfigureAwait(false); + var response = await _client + .SendRequestAsync( + new JsonRequest + { + Method = HttpMethod.Get, + Path = "organizations", + Headers = _headers, + Options = options, + }, + cancellationToken + ) + .ConfigureAwait(false); + if (response.StatusCode is >= 200 and < 400) + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + try + { + var responseData = JsonUtils.Deserialize(responseBody)!; + return new WithRawResponse() + { + Data = responseData, + RawResponse = new RawResponse() + { + StatusCode = response.Raw.StatusCode, + Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), + Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), + }, + }; + } + catch (JsonException e) + { + throw new SeedApiApiException( + "Failed to deserialize response", + response.StatusCode, + responseBody, + e + ); + } + } + { + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + } + + /// + /// await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); + /// + public WithRawResponseTask SearchRuleTypesAsync( + SearchRuleTypesRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + SearchRuleTypesAsyncCore(request, options, cancellationToken) + ); + } + + /// + /// await client.CreateRuleAsync( + /// new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } + /// ); + /// + public WithRawResponseTask CreateRuleAsync( + RuleCreateRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + CreateRuleAsyncCore(request, options, cancellationToken) + ); + } + + /// + /// await client.ListUsersAsync(); + /// + public WithRawResponseTask ListUsersAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + ListUsersAsyncCore(options, cancellationToken) + ); + } + + /// + /// await client.GetEntityAsync(); + /// + public WithRawResponseTask GetEntityAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + GetEntityAsyncCore(options, cancellationToken) + ); + } + + /// + /// await client.GetOrganizationAsync(); + /// + public WithRawResponseTask GetOrganizationAsync( + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + return new WithRawResponseTask( + GetOrganizationAsyncCore(options, cancellationToken) + ); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs new file mode 100644 index 000000000000..c0d843281678 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs @@ -0,0 +1,56 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +/// +/// Common audit metadata. +/// +[Serializable] +public record AuditInfo : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs new file mode 100644 index 000000000000..eb944d0030da --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public BaseOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs new file mode 100644 index 000000000000..fcb0efec5fea --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from BaseOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Subscription tier. + /// + [JsonPropertyName("tier")] + public string? Tier { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs new file mode 100644 index 000000000000..757711bdb703 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs @@ -0,0 +1,46 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record CombinedEntity : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("status")] + public required CombinedEntityStatus Status { get; set; } + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Identifiable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs new file mode 100644 index 000000000000..0ab2467f6bd7 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs @@ -0,0 +1,115 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] +[Serializable] +public readonly record struct CombinedEntityStatus : IStringEnum +{ + public static readonly CombinedEntityStatus Active = new(Values.Active); + + public static readonly CombinedEntityStatus Archived = new(Values.Archived); + + public CombinedEntityStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static CombinedEntityStatus FromCustom(string value) + { + return new CombinedEntityStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(CombinedEntityStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(CombinedEntityStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(CombinedEntityStatus value) => value.Value; + + public static explicit operator CombinedEntityStatus(string value) => new(value); + + internal class CombinedEntityStatusSerializer : JsonConverter + { + public override CombinedEntityStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override CombinedEntityStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Archived = "archived"; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs new file mode 100644 index 000000000000..f521e9082be7 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Describable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Display name from Describable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs new file mode 100644 index 000000000000..7bc064682236 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs @@ -0,0 +1,28 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("metadata")] + public DetailedOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs new file mode 100644 index 000000000000..798903997238 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from DetailedOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Custom domain name. + /// + [JsonPropertyName("domain")] + public string? Domain { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs new file mode 100644 index 000000000000..05b3808b610b --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Identifiable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Identifiable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs new file mode 100644 index 000000000000..e90644924f99 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Organization : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public BaseOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs new file mode 100644 index 000000000000..9f4b1b5c0820 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PaginatedResult : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable Results { get; set; } = new List(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs new file mode 100644 index 000000000000..b1f0001a4733 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PagingCursors : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Cursor for the next page of results. + /// + [JsonPropertyName("next")] + public required string Next { get; set; } + + /// + /// Cursor for the previous page of results. + /// + [JsonPropertyName("previous")] + public string? Previous { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs new file mode 100644 index 000000000000..d22a26079f4e --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] +[Serializable] +public readonly record struct RuleExecutionContext : IStringEnum +{ + public static readonly RuleExecutionContext Prod = new(Values.Prod); + + public static readonly RuleExecutionContext Staging = new(Values.Staging); + + public static readonly RuleExecutionContext Dev = new(Values.Dev); + + public RuleExecutionContext(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleExecutionContext FromCustom(string value) + { + return new RuleExecutionContext(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleExecutionContext value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleExecutionContext value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleExecutionContext value) => value.Value; + + public static explicit operator RuleExecutionContext(string value) => new(value); + + internal class RuleExecutionContextSerializer : JsonConverter + { + public override RuleExecutionContext Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleExecutionContext ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Prod = "prod"; + + public const string Staging = "staging"; + + public const string Dev = "dev"; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs new file mode 100644 index 000000000000..6459faa88dfd --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs @@ -0,0 +1,65 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("status")] + public required RuleResponseStatus Status { get; set; } + + [JsonPropertyName("executionContext")] + public RuleExecutionContext? ExecutionContext { get; set; } + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs new file mode 100644 index 000000000000..9b587cdbcba0 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] +[Serializable] +public readonly record struct RuleResponseStatus : IStringEnum +{ + public static readonly RuleResponseStatus Active = new(Values.Active); + + public static readonly RuleResponseStatus Inactive = new(Values.Inactive); + + public static readonly RuleResponseStatus Draft = new(Values.Draft); + + public RuleResponseStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleResponseStatus FromCustom(string value) + { + return new RuleResponseStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleResponseStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleResponseStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleResponseStatus value) => value.Value; + + public static explicit operator RuleResponseStatus(string value) => new(value); + + internal class RuleResponseStatusSerializer : JsonConverter + { + public override RuleResponseStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleResponseStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Inactive = "inactive"; + + public const string Draft = "draft"; + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs new file mode 100644 index 000000000000..578b90315dde --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleType : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs new file mode 100644 index 000000000000..fa14fff1bde0 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleTypeSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/User.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/User.cs new file mode 100644 index 000000000000..abc389c0a6b6 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/User.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record User : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("email")] + public required string Email { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs new file mode 100644 index 000000000000..942b822f3dc4 --- /dev/null +++ b/seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record UserSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/seed.yml b/seed/csharp-sdk/seed.yml index cf465b5f48b3..9f5b038933db 100644 --- a/seed/csharp-sdk/seed.yml +++ b/seed/csharp-sdk/seed.yml @@ -398,6 +398,8 @@ fixtures: redact-response-body-on-error: true outputFolder: redact-response-body-on-error allowedFailures: + - allof + - allof-inline - schemaless-request-body-examples - bytes-upload - enum:forward-compatible-enums diff --git a/seed/go-sdk/allof-inline/.fern/metadata.json b/seed/go-sdk/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..1e0f7d8e54e4 --- /dev/null +++ b/seed/go-sdk/allof-inline/.fern/metadata.json @@ -0,0 +1,10 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-go-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "enableWireTests": false + }, + "originGitCommit": "DUMMY", + "sdkVersion": "v0.0.1" +} \ No newline at end of file diff --git a/seed/go-sdk/allof-inline/.github/workflows/ci.yml b/seed/go-sdk/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..1097e6a18acc --- /dev/null +++ b/seed/go-sdk/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.10.1 + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Setup wiremock server + run: | + PROJECT_NAME="wiremock-$(basename $(dirname $(pwd)) | tr -d '.')" + echo "PROJECT_NAME=$PROJECT_NAME" >> $GITHUB_ENV + if [ -f wiremock/docker-compose.test.yml ]; then + docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down + docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml up -d + WIREMOCK_PORT=$(docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml port wiremock 8080 | cut -d: -f2) + echo "WIREMOCK_URL=http://localhost:$WIREMOCK_PORT" >> $GITHUB_ENV + fi + + - name: Test + run: go test ./... + + - name: Teardown wiremock server + if: always() + run: | + if [ -f wiremock/docker-compose.test.yml ]; then + docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down + fi diff --git a/seed/go-sdk/allof-inline/README.md b/seed/go-sdk/allof-inline/README.md new file mode 100644 index 000000000000..b38d709447cd --- /dev/null +++ b/seed/go-sdk/allof-inline/README.md @@ -0,0 +1,195 @@ +# Seed Go Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FGo) + +The Seed Go library provides convenient access to the Seed APIs from Go. + +## Table of Contents + +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Request Options](#request-options) +- [Advanced](#advanced) + - [Response Headers](#response-headers) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Explicit Null](#explicit-null) +- [Contributing](#contributing) + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```go +package example + +import ( + context "context" + + fern "github.com/allof-inline/fern" + client "github.com/allof-inline/fern/client" +) + +func do() { + client := client.NewClient() + request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } + client.CreateRule( + context.TODO(), + request, + ) +} +``` + +## Environments + +You can choose between different environments by using the `option.WithBaseURL` option. You can configure any arbitrary base +URL, which is particularly useful in test environments. + +```go +client := client.NewClient( + option.WithBaseURL(api.Environments.Default), +) +``` + +## Errors + +Structured error types are returned from API calls that return non-success status codes. These errors are compatible +with the `errors.Is` and `errors.As` APIs, so you can access the error like so: + +```go +response, err := client.CreateRule(...) +if err != nil { + var apiError *core.APIError + if errors.As(err, apiError) { + // Do something with the API error ... + } + return err +} +``` + +## Request Options + +A variety of request options are included to adapt the behavior of the library, which includes configuring +authorization tokens, or providing your own instrumented `*http.Client`. + +These request options can either be +specified on the client so that they're applied on every request, or for an individual request, like so: + +> Providing your own `*http.Client` is recommended. Otherwise, the `http.DefaultClient` will be used, +> and your client will wait indefinitely for a response (unless the per-request, context-based timeout +> is used). + +```go +// Specify default options applied on every request. +client := client.NewClient( + option.WithToken(""), + option.WithHTTPClient( + &http.Client{ + Timeout: 5 * time.Second, + }, + ), +) + +// Specify options for an individual request. +response, err := client.CreateRule( + ..., + option.WithToken(""), +) +``` + +## Advanced + +### Response Headers + +You can access the raw HTTP response data by using the `WithRawResponse` field on the client. This is useful +when you need to examine the response headers received from the API call. (When the endpoint is paginated, +the raw HTTP response data will be included automatically in the Page response object.) + +```go +response, err := client.WithRawResponse.CreateRule(...) +if err != nil { + return err +} +fmt.Printf("Got response headers: %v", response.Header) +fmt.Printf("Got status code: %d", response.StatusCode) +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +If the `Retry-After` header is present in the response, the SDK will prioritize respecting its value exactly +over the default exponential backoff. + +Use the `option.WithMaxAttempts` option to configure this behavior for the entire client or an individual request: + +```go +client := client.NewClient( + option.WithMaxAttempts(1), +) + +response, err := client.CreateRule( + ..., + option.WithMaxAttempts(1), +) +``` + +### Timeouts + +Setting a timeout for each individual request is as simple as using the standard context library. Setting a one second timeout for an individual API call looks like the following: + +```go +ctx, cancel := context.WithTimeout(ctx, time.Second) +defer cancel() + +response, err := client.CreateRule(ctx, ...) +``` + +### Explicit Null + +If you want to send the explicit `null` JSON value through an optional parameter, you can use the setters\ +that come with every object. Calling a setter method for a property will flip a bit in the `explicitFields` +bitfield for that setter's object; during serialization, any property with a flipped bit will have its +omittable status stripped, so zero or `nil` values will be sent explicitly rather than omitted altogether: + +```go +type ExampleRequest struct { + // An optional string parameter. + Name *string `json:"name,omitempty" url:"-"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` +} + +request := &ExampleRequest{} +request.SetName(nil) + +response, err := client.CreateRule(ctx, request, ...) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/go-sdk/allof-inline/client/client.go b/seed/go-sdk/allof-inline/client/client.go new file mode 100644 index 000000000000..ced3e187e81b --- /dev/null +++ b/seed/go-sdk/allof-inline/client/client.go @@ -0,0 +1,109 @@ +// Code generated by Fern. DO NOT EDIT. + +package client + +import ( + context "context" + + fern "github.com/allof-inline/fern" + core "github.com/allof-inline/fern/core" + internal "github.com/allof-inline/fern/internal" + option "github.com/allof-inline/fern/option" +) + +type Client struct { + WithRawResponse *RawClient + + options *core.RequestOptions + baseURL string + caller *internal.Caller +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + WithRawResponse: NewRawClient(options), + options: options, + baseURL: options.BaseURL, + caller: internal.NewCaller( + &internal.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + } +} + +func (c *Client) SearchRuleTypes( + ctx context.Context, + request *fern.SearchRuleTypesRequest, + opts ...option.RequestOption, +) (*fern.RuleTypeSearchResponse, error) { + response, err := c.WithRawResponse.SearchRuleTypes( + ctx, + request, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) CreateRule( + ctx context.Context, + request *fern.RuleCreateRequest, + opts ...option.RequestOption, +) (*fern.RuleResponse, error) { + response, err := c.WithRawResponse.CreateRule( + ctx, + request, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) ListUsers( + ctx context.Context, + opts ...option.RequestOption, +) (*fern.UserSearchResponse, error) { + response, err := c.WithRawResponse.ListUsers( + ctx, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) GetEntity( + ctx context.Context, + opts ...option.RequestOption, +) (*fern.CombinedEntity, error) { + response, err := c.WithRawResponse.GetEntity( + ctx, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) GetOrganization( + ctx context.Context, + opts ...option.RequestOption, +) (*fern.Organization, error) { + response, err := c.WithRawResponse.GetOrganization( + ctx, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} diff --git a/seed/go-sdk/allof-inline/client/client_test.go b/seed/go-sdk/allof-inline/client/client_test.go new file mode 100644 index 000000000000..a7a872235f09 --- /dev/null +++ b/seed/go-sdk/allof-inline/client/client_test.go @@ -0,0 +1,45 @@ +// Code generated by Fern. DO NOT EDIT. + +package client + +import ( + option "github.com/allof-inline/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.options.HTTPHeader.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/allof-inline/client/raw_client.go b/seed/go-sdk/allof-inline/client/raw_client.go new file mode 100644 index 000000000000..79dc560be0a7 --- /dev/null +++ b/seed/go-sdk/allof-inline/client/raw_client.go @@ -0,0 +1,238 @@ +// Code generated by Fern. DO NOT EDIT. + +package client + +import ( + context "context" + http "net/http" + + fern "github.com/allof-inline/fern" + core "github.com/allof-inline/fern/core" + internal "github.com/allof-inline/fern/internal" + option "github.com/allof-inline/fern/option" +) + +type RawClient struct { + baseURL string + caller *internal.Caller + options *core.RequestOptions +} + +func NewRawClient(options *core.RequestOptions) *RawClient { + return &RawClient{ + options: options, + baseURL: options.BaseURL, + caller: internal.NewCaller( + &internal.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + } +} + +func (r *RawClient) SearchRuleTypes( + ctx context.Context, + request *fern.SearchRuleTypesRequest, + opts ...option.RequestOption, +) (*core.Response[*fern.RuleTypeSearchResponse], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/rule-types" + queryParams, err := internal.QueryValues(request) + if err != nil { + return nil, err + } + if len(queryParams) > 0 { + endpointURL += "?" + queryParams.Encode() + } + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.RuleTypeSearchResponse + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.RuleTypeSearchResponse]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) CreateRule( + ctx context.Context, + request *fern.RuleCreateRequest, + opts ...option.RequestOption, +) (*core.Response[*fern.RuleResponse], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/rules" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + headers.Add("Content-Type", "application/json") + var response *fern.RuleResponse + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.RuleResponse]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) ListUsers( + ctx context.Context, + opts ...option.RequestOption, +) (*core.Response[*fern.UserSearchResponse], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/users" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.UserSearchResponse + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.UserSearchResponse]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) GetEntity( + ctx context.Context, + opts ...option.RequestOption, +) (*core.Response[*fern.CombinedEntity], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/entities" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.CombinedEntity + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.CombinedEntity]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) GetOrganization( + ctx context.Context, + opts ...option.RequestOption, +) (*core.Response[*fern.Organization], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/organizations" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.Organization + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.Organization]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} diff --git a/seed/go-sdk/allof-inline/core/api_error.go b/seed/go-sdk/allof-inline/core/api_error.go new file mode 100644 index 000000000000..6168388541b4 --- /dev/null +++ b/seed/go-sdk/allof-inline/core/api_error.go @@ -0,0 +1,47 @@ +package core + +import ( + "fmt" + "net/http" +) + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` + Header http.Header `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, header http.Header, err error) *APIError { + return &APIError{ + err: err, + Header: header, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} diff --git a/seed/go-sdk/allof-inline/core/http.go b/seed/go-sdk/allof-inline/core/http.go new file mode 100644 index 000000000000..92c435692940 --- /dev/null +++ b/seed/go-sdk/allof-inline/core/http.go @@ -0,0 +1,15 @@ +package core + +import "net/http" + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// Response is an HTTP response from an HTTP client. +type Response[T any] struct { + StatusCode int + Header http.Header + Body T +} diff --git a/seed/go-sdk/allof-inline/core/request_option.go b/seed/go-sdk/allof-inline/core/request_option.go new file mode 100644 index 000000000000..2ae425289a2a --- /dev/null +++ b/seed/go-sdk/allof-inline/core/request_option.go @@ -0,0 +1,119 @@ +// Code generated by Fern. DO NOT EDIT. + +package core + +import ( + http "net/http" + url "net/url" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + BodyProperties map[string]interface{} + QueryParameters url.Values + MaxAttempts uint + MaxBufSize int +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + BodyProperties: make(map[string]interface{}), + QueryParameters: make(url.Values), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/allof-inline/fern") + headers.Set("X-Fern-SDK-Version", "v0.0.1") + headers.Set("User-Agent", "github.com/allof-inline/fern/0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// BodyPropertiesOption implements the RequestOption interface. +type BodyPropertiesOption struct { + BodyProperties map[string]interface{} +} + +func (b *BodyPropertiesOption) applyRequestOptions(opts *RequestOptions) { + opts.BodyProperties = b.BodyProperties +} + +// QueryParametersOption implements the RequestOption interface. +type QueryParametersOption struct { + QueryParameters url.Values +} + +func (q *QueryParametersOption) applyRequestOptions(opts *RequestOptions) { + opts.QueryParameters = q.QueryParameters +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// MaxBufSizeOption implements the RequestOption interface. +type MaxBufSizeOption struct { + MaxBufSize int +} + +func (m *MaxBufSizeOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxBufSize = m.MaxBufSize +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go new file mode 100644 index 000000000000..62bb212116c0 --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go @@ -0,0 +1,22 @@ +package example + +import ( + context "context" + + fern "github.com/allof-inline/fern" + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.SearchRuleTypesRequest{} + client.SearchRuleTypes( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go new file mode 100644 index 000000000000..29f0a4cc1a87 --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go @@ -0,0 +1,26 @@ +package example + +import ( + context "context" + + fern "github.com/allof-inline/fern" + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.SearchRuleTypesRequest{ + Query: fern.String( + "query", + ), + } + client.SearchRuleTypes( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go new file mode 100644 index 000000000000..ead43f2a6d2f --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go @@ -0,0 +1,25 @@ +package example + +import ( + context "context" + + fern "github.com/allof-inline/fern" + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } + client.CreateRule( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go new file mode 100644 index 000000000000..ead43f2a6d2f --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go @@ -0,0 +1,25 @@ +package example + +import ( + context "context" + + fern "github.com/allof-inline/fern" + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } + client.CreateRule( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go new file mode 100644 index 000000000000..db43c60a2916 --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.ListUsers( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go new file mode 100644 index 000000000000..db43c60a2916 --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.ListUsers( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go new file mode 100644 index 000000000000..0f87de8dc18f --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetEntity( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go new file mode 100644 index 000000000000..0f87de8dc18f --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetEntity( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go new file mode 100644 index 000000000000..49494c3c0576 --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetOrganization( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go new file mode 100644 index 000000000000..49494c3c0576 --- /dev/null +++ b/seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof-inline/fern/client" + option "github.com/allof-inline/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetOrganization( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof-inline/environments.go b/seed/go-sdk/allof-inline/environments.go new file mode 100644 index 000000000000..27941a670e97 --- /dev/null +++ b/seed/go-sdk/allof-inline/environments.go @@ -0,0 +1,13 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +// Environments defines all of the API environments. +// These values can be used with the WithBaseURL +// RequestOption to override the client's default environment, +// if any. +var Environments = struct { + Default string +}{ + Default: "https://api.example.com", +} diff --git a/seed/go-sdk/allof-inline/error_codes.go b/seed/go-sdk/allof-inline/error_codes.go new file mode 100644 index 000000000000..f8da5275cc1d --- /dev/null +++ b/seed/go-sdk/allof-inline/error_codes.go @@ -0,0 +1,9 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + internal "github.com/allof-inline/fern/internal" +) + +var ErrorCodes internal.ErrorCodes = internal.ErrorCodes{} diff --git a/seed/go-sdk/allof-inline/file_param.go b/seed/go-sdk/allof-inline/file_param.go new file mode 100644 index 000000000000..737abb97c837 --- /dev/null +++ b/seed/go-sdk/allof-inline/file_param.go @@ -0,0 +1,41 @@ +package api + +import ( + "io" +) + +// FileParam is a file type suitable for multipart/form-data uploads. +type FileParam struct { + io.Reader + filename string + contentType string +} + +// FileParamOption adapts the behavior of the FileParam. No options are +// implemented yet, but this interface allows for future extensibility. +type FileParamOption interface { + apply() +} + +// NewFileParam returns a *FileParam type suitable for multipart/form-data uploads. All file +// upload endpoints accept a simple io.Reader, which is usually created by opening a file +// via os.Open. +// +// However, some endpoints require additional metadata about the file such as a specific +// Content-Type or custom filename. FileParam makes it easier to create the correct type +// signature for these endpoints. +func NewFileParam( + reader io.Reader, + filename string, + contentType string, + opts ...FileParamOption, +) *FileParam { + return &FileParam{ + Reader: reader, + filename: filename, + contentType: contentType, + } +} + +func (f *FileParam) Name() string { return f.filename } +func (f *FileParam) ContentType() string { return f.contentType } diff --git a/seed/go-sdk/allof-inline/go.mod b/seed/go-sdk/allof-inline/go.mod new file mode 100644 index 000000000000..d1ba4957a004 --- /dev/null +++ b/seed/go-sdk/allof-inline/go.mod @@ -0,0 +1,16 @@ +module github.com/allof-inline/fern + +go 1.21 + +toolchain go1.23.8 + +require github.com/google/uuid v1.6.0 + +require github.com/stretchr/testify v1.8.4 + +require gopkg.in/yaml.v3 v3.0.1 // indirect + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/seed/go-sdk/allof-inline/go.sum b/seed/go-sdk/allof-inline/go.sum new file mode 100644 index 000000000000..fcca6d128057 --- /dev/null +++ b/seed/go-sdk/allof-inline/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/allof-inline/internal/caller.go b/seed/go-sdk/allof-inline/internal/caller.go new file mode 100644 index 000000000000..e9b32b76af32 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/caller.go @@ -0,0 +1,311 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "reflect" + "strings" + + "github.com/allof-inline/fern/core" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" + contentTypeFormURLEncoded = "application/x-www-form-urlencoded" +) + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client core.HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client core.HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient core.HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + BodyProperties map[string]interface{} + QueryParameters url.Values + Client core.HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// CallResponse is a parsed HTTP response from an API call. +type CallResponse struct { + StatusCode int + Header http.Header +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) (*CallResponse, error) { + url := buildURL(params.URL, params.QueryParameters) + req, err := newRequest( + ctx, + url, + params.Method, + params.Headers, + params.Request, + params.BodyProperties, + ) + if err != nil { + return nil, err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return nil, err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return nil, err + } + + // Close the response body after we're done. + defer func() { _ = resp.Body.Close() }() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return &CallResponse{ + StatusCode: resp.StatusCode, + Header: resp.Header, + }, nil + } + return nil, fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return nil, err + } + } + + return &CallResponse{ + StatusCode: resp.StatusCode, + Header: resp.Header, + }, nil +} + +// buildURL constructs the final URL by appending the given query parameters (if any). +func buildURL( + url string, + queryParameters url.Values, +) string { + if len(queryParameters) == 0 { + return url + } + if strings.ContainsRune(url, '?') { + url += "&" + } else { + url += "?" + } + url += queryParameters.Encode() + return url +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, + bodyProperties map[string]interface{}, +) (*http.Request, error) { + // Determine the content type from headers, defaulting to JSON. + reqContentType := contentType + if endpointHeaders != nil { + if ct := endpointHeaders.Get(contentTypeHeader); ct != "" { + reqContentType = ct + } + } + requestBody, err := newRequestBody(request, bodyProperties, reqContentType) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req.Header.Set(contentTypeHeader, reqContentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}, bodyProperties map[string]interface{}, reqContentType string) (io.Reader, error) { + if isNil(request) { + if len(bodyProperties) == 0 { + return nil, nil + } + if reqContentType == contentTypeFormURLEncoded { + return newFormURLEncodedBody(bodyProperties), nil + } + requestBytes, err := json.Marshal(bodyProperties) + if err != nil { + return nil, err + } + return bytes.NewReader(requestBytes), nil + } + if body, ok := request.(io.Reader); ok { + return body, nil + } + // Handle form URL encoded content type. + if reqContentType == contentTypeFormURLEncoded { + return newFormURLEncodedRequestBody(request, bodyProperties) + } + requestBytes, err := MarshalJSONWithExtraProperties(request, bodyProperties) + if err != nil { + return nil, err + } + return bytes.NewReader(requestBytes), nil +} + +// newFormURLEncodedBody returns a new io.Reader that represents a form URL encoded body +// from the given body properties map. +func newFormURLEncodedBody(bodyProperties map[string]interface{}) io.Reader { + values := url.Values{} + for key, val := range bodyProperties { + values.Set(key, fmt.Sprintf("%v", val)) + } + return strings.NewReader(values.Encode()) +} + +// newFormURLEncodedRequestBody returns a new io.Reader that represents a form URL encoded body +// from the given request struct and body properties. +func newFormURLEncodedRequestBody(request interface{}, bodyProperties map[string]interface{}) (io.Reader, error) { + values := url.Values{} + // Marshal the request to JSON first to respect any custom MarshalJSON methods, + // then unmarshal into a map to extract the field values. + jsonBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + var jsonMap map[string]interface{} + if err := json.Unmarshal(jsonBytes, &jsonMap); err != nil { + return nil, err + } + // Convert the JSON map to form URL encoded values. + for key, val := range jsonMap { + if val == nil { + continue + } + values.Set(key, fmt.Sprintf("%v", val)) + } + // Add any extra body properties. + for key, val := range bodyProperties { + values.Set(key, fmt.Sprintf("%v", val)) + } + return strings.NewReader(values.Encode()), nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Header, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return core.NewAPIError(response.StatusCode, response.Header, nil) + } + return core.NewAPIError(response.StatusCode, response.Header, errors.New(string(bytes))) +} + +// isNil is used to determine if the request value is equal to nil (i.e. an interface +// value that holds a nil concrete value is itself non-nil). +func isNil(value interface{}) bool { + if value == nil { + return true + } + v := reflect.ValueOf(value) + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: + return v.IsNil() + default: + return false + } +} diff --git a/seed/go-sdk/allof-inline/internal/caller_test.go b/seed/go-sdk/allof-inline/internal/caller_test.go new file mode 100644 index 000000000000..efc29b145798 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/caller_test.go @@ -0,0 +1,705 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "strings" + "testing" + + "github.com/allof-inline/fern/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// InternalTestCase represents a single test case. +type InternalTestCase struct { + description string + + // Server-side assertions. + givePathSuffix string + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *InternalTestRequest + giveQueryParams url.Values + giveBodyProperties map[string]interface{} + + // Client-side assertions. + wantResponse *InternalTestResponse + wantError error +} + +// InternalTestRequest a simple request body. +type InternalTestRequest struct { + Id string `json:"id"` +} + +// InternalTestResponse a simple response body. +type InternalTestResponse struct { + Id string `json:"id"` + ExtraBodyProperties map[string]interface{} `json:"extraBodyProperties,omitempty"` + QueryParameters url.Values `json:"queryParameters,omitempty"` +} + +// InternalTestNotFoundError represents a 404. +type InternalTestNotFoundError struct { + *core.APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*InternalTestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + wantResponse: &InternalTestResponse{ + Id: "123", + }, + }, + { + description: "GET success with query", + givePathSuffix: "?limit=1", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + wantResponse: &InternalTestResponse{ + Id: "123", + QueryParameters: url.Values{ + "limit": []string{"1"}, + }, + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &InternalTestRequest{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &InternalTestNotFoundError{ + APIError: core.NewAPIError( + http.StatusNotFound, + http.Header{}, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST empty body", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: nil, + wantError: core.NewAPIError( + http.StatusBadRequest, + http.Header{}, + errors.New("invalid request"), + ), + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &InternalTestRequest{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: core.NewAPIError( + http.StatusInternalServerError, + http.Header{}, + errors.New("failed to process request"), + ), + }, + { + description: "POST extra properties", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: new(InternalTestRequest), + giveBodyProperties: map[string]interface{}{ + "key": "value", + }, + wantResponse: &InternalTestResponse{ + ExtraBodyProperties: map[string]interface{}{ + "key": "value", + }, + }, + }, + { + description: "GET extra query parameters", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveQueryParams: url.Values{ + "extra": []string{"true"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + wantResponse: &InternalTestResponse{ + Id: "123", + QueryParameters: url.Values{ + "extra": []string{"true"}, + }, + }, + }, + { + description: "GET merge extra query parameters", + givePathSuffix: "?limit=1", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + giveQueryParams: url.Values{ + "extra": []string{"true"}, + }, + wantResponse: &InternalTestResponse{ + Id: "123", + QueryParameters: url.Values{ + "limit": []string{"1"}, + "extra": []string{"true"}, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL + test.givePathSuffix, + Method: test.giveMethod, + Headers: test.giveHeader, + BodyProperties: test.giveBodyProperties, + QueryParameters: test.giveQueryParams, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *InternalTestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + request := new(InternalTestRequest) + + bytes, err := io.ReadAll(r.Body) + if tc.giveRequest == nil { + require.Empty(t, bytes) + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("invalid request")) + require.NoError(t, err) + return + } + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &InternalTestNotFoundError{ + APIError: &core.APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + extraBodyProperties := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &extraBodyProperties)) + delete(extraBodyProperties, "id") + + response := &InternalTestResponse{ + Id: request.Id, + ExtraBodyProperties: extraBodyProperties, + QueryParameters: r.URL.Query(), + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +func TestIsNil(t *testing.T) { + t.Run("nil interface", func(t *testing.T) { + assert.True(t, isNil(nil)) + }) + + t.Run("nil pointer", func(t *testing.T) { + var ptr *string + assert.True(t, isNil(ptr)) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + s := "test" + assert.False(t, isNil(&s)) + }) + + t.Run("nil slice", func(t *testing.T) { + var slice []string + assert.True(t, isNil(slice)) + }) + + t.Run("non-nil slice", func(t *testing.T) { + slice := []string{} + assert.False(t, isNil(slice)) + }) + + t.Run("nil map", func(t *testing.T) { + var m map[string]string + assert.True(t, isNil(m)) + }) + + t.Run("non-nil map", func(t *testing.T) { + m := make(map[string]string) + assert.False(t, isNil(m)) + }) + + t.Run("string value", func(t *testing.T) { + assert.False(t, isNil("test")) + }) + + t.Run("empty string value", func(t *testing.T) { + assert.False(t, isNil("")) + }) + + t.Run("int value", func(t *testing.T) { + assert.False(t, isNil(42)) + }) + + t.Run("zero int value", func(t *testing.T) { + assert.False(t, isNil(0)) + }) + + t.Run("bool value", func(t *testing.T) { + assert.False(t, isNil(true)) + }) + + t.Run("false bool value", func(t *testing.T) { + assert.False(t, isNil(false)) + }) + + t.Run("struct value", func(t *testing.T) { + type testStruct struct { + Field string + } + assert.False(t, isNil(testStruct{Field: "test"})) + }) + + t.Run("empty struct value", func(t *testing.T) { + type testStruct struct { + Field string + } + assert.False(t, isNil(testStruct{})) + }) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, http.Header, io.Reader) error { + return func(statusCode int, header http.Header, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = core.NewAPIError(statusCode, header, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(InternalTestNotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} + +// FormURLEncodedTestRequest is a test struct for form URL encoding tests. +type FormURLEncodedTestRequest struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + GrantType string `json:"grant_type,omitempty"` + Scope *string `json:"scope,omitempty"` + NilPointer *string `json:"nil_pointer,omitempty"` +} + +func TestNewFormURLEncodedBody(t *testing.T) { + t.Run("simple key-value pairs", func(t *testing.T) { + bodyProperties := map[string]interface{}{ + "client_id": "test_client_id", + "client_secret": "test_client_secret", + "grant_type": "client_credentials", + } + reader := newFormURLEncodedBody(bodyProperties) + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Parse the body and verify values + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "client_credentials", values.Get("grant_type")) + + // Verify it's not JSON + bodyStr := string(body) + assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should not be JSON, got: %s", bodyStr) + }) + + t.Run("special characters requiring URL encoding", func(t *testing.T) { + bodyProperties := map[string]interface{}{ + "value_with_space": "hello world", + "value_with_ampersand": "a&b", + "value_with_equals": "a=b", + "value_with_plus": "a+b", + } + reader := newFormURLEncodedBody(bodyProperties) + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Parse the body and verify values are correctly decoded + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "hello world", values.Get("value_with_space")) + assert.Equal(t, "a&b", values.Get("value_with_ampersand")) + assert.Equal(t, "a=b", values.Get("value_with_equals")) + assert.Equal(t, "a+b", values.Get("value_with_plus")) + }) + + t.Run("empty map", func(t *testing.T) { + bodyProperties := map[string]interface{}{} + reader := newFormURLEncodedBody(bodyProperties) + body, err := io.ReadAll(reader) + require.NoError(t, err) + assert.Empty(t, string(body)) + }) +} + +func TestNewFormURLEncodedRequestBody(t *testing.T) { + t.Run("struct with json tags", func(t *testing.T) { + scope := "read write" + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + GrantType: "client_credentials", + Scope: &scope, + NilPointer: nil, + } + reader, err := newFormURLEncodedRequestBody(request, nil) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Parse the body and verify values + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "client_credentials", values.Get("grant_type")) + assert.Equal(t, "read write", values.Get("scope")) + // nil_pointer should not be present (nil pointer with omitempty) + assert.Empty(t, values.Get("nil_pointer")) + + // Verify it's not JSON + bodyStr := string(body) + assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should not be JSON, got: %s", bodyStr) + }) + + t.Run("struct with omitempty and zero values", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + GrantType: "", // empty string with omitempty should be omitted + Scope: nil, + NilPointer: nil, + } + reader, err := newFormURLEncodedRequestBody(request, nil) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + // grant_type should not be present (empty string with omitempty) + assert.Empty(t, values.Get("grant_type")) + assert.Empty(t, values.Get("scope")) + }) + + t.Run("struct with extra body properties", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + } + bodyProperties := map[string]interface{}{ + "extra_param": "extra_value", + } + reader, err := newFormURLEncodedRequestBody(request, bodyProperties) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "extra_value", values.Get("extra_param")) + }) + + t.Run("special characters in struct fields", func(t *testing.T) { + scope := "read&write=all+permissions" + request := &FormURLEncodedTestRequest{ + ClientID: "client with spaces", + ClientSecret: "secret&with=special+chars", + Scope: &scope, + } + reader, err := newFormURLEncodedRequestBody(request, nil) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "client with spaces", values.Get("client_id")) + assert.Equal(t, "secret&with=special+chars", values.Get("client_secret")) + assert.Equal(t, "read&write=all+permissions", values.Get("scope")) + }) +} + +func TestNewRequestBodyFormURLEncoded(t *testing.T) { + t.Run("selects form encoding when content-type is form-urlencoded", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + GrantType: "client_credentials", + } + reader, err := newRequestBody(request, nil, contentTypeFormURLEncoded) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Verify it's form-urlencoded, not JSON + bodyStr := string(body) + assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should not be JSON when Content-Type is form-urlencoded, got: %s", bodyStr) + + // Parse and verify values + values, err := url.ParseQuery(bodyStr) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "client_credentials", values.Get("grant_type")) + }) + + t.Run("selects JSON encoding when content-type is application/json", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + } + reader, err := newRequestBody(request, nil, contentType) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Verify it's JSON + bodyStr := string(body) + assert.True(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should be JSON when Content-Type is application/json, got: %s", bodyStr) + + // Parse and verify it's valid JSON + var parsed map[string]interface{} + err = json.Unmarshal(body, &parsed) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", parsed["client_id"]) + assert.Equal(t, "test_client_secret", parsed["client_secret"]) + }) + + t.Run("form encoding with body properties only (nil request)", func(t *testing.T) { + bodyProperties := map[string]interface{}{ + "client_id": "test_client_id", + "client_secret": "test_client_secret", + } + reader, err := newRequestBody(nil, bodyProperties, contentTypeFormURLEncoded) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + }) +} diff --git a/seed/go-sdk/allof-inline/internal/error_decoder.go b/seed/go-sdk/allof-inline/internal/error_decoder.go new file mode 100644 index 000000000000..a0358e5dd9ba --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/error_decoder.go @@ -0,0 +1,64 @@ +package internal + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/allof-inline/fern/core" +) + +// ErrorCodes maps HTTP status codes to error constructors. +type ErrorCodes map[int]func(*core.APIError) error + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *core.APIError). +type ErrorDecoder func(statusCode int, header http.Header, body io.Reader) error + +// NewErrorDecoder returns a new ErrorDecoder backed by the given error codes. +// errorCodesOverrides is optional and will be merged with the default error codes, +// with overrides taking precedence. +func NewErrorDecoder(errorCodes ErrorCodes, errorCodesOverrides ...ErrorCodes) ErrorDecoder { + // Merge default error codes with overrides + mergedErrorCodes := make(ErrorCodes) + + // Start with default error codes + for statusCode, errorFunc := range errorCodes { + mergedErrorCodes[statusCode] = errorFunc + } + + // Apply overrides if provided + if len(errorCodesOverrides) > 0 && errorCodesOverrides[0] != nil { + for statusCode, errorFunc := range errorCodesOverrides[0] { + mergedErrorCodes[statusCode] = errorFunc + } + } + + return func(statusCode int, header http.Header, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return fmt.Errorf("failed to read error from response body: %w", err) + } + apiError := core.NewAPIError( + statusCode, + header, + errors.New(string(raw)), + ) + newErrorFunc, ok := mergedErrorCodes[statusCode] + if !ok { + // This status code isn't recognized, so we return + // the API error as-is. + return apiError + } + customError := newErrorFunc(apiError) + if err := json.NewDecoder(bytes.NewReader(raw)).Decode(customError); err != nil { + // If we fail to decode the error, we return the + // API error as-is. + return apiError + } + return customError + } +} diff --git a/seed/go-sdk/allof-inline/internal/error_decoder_test.go b/seed/go-sdk/allof-inline/internal/error_decoder_test.go new file mode 100644 index 000000000000..88cbaeb9cd17 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/error_decoder_test.go @@ -0,0 +1,59 @@ +package internal + +import ( + "bytes" + "errors" + "net/http" + "testing" + + "github.com/allof-inline/fern/core" + "github.com/stretchr/testify/assert" +) + +func TestErrorDecoder(t *testing.T) { + decoder := NewErrorDecoder( + ErrorCodes{ + http.StatusNotFound: func(apiError *core.APIError) error { + return &InternalTestNotFoundError{APIError: apiError} + }, + }) + + tests := []struct { + description string + giveStatusCode int + giveHeader http.Header + giveBody string + wantError error + }{ + { + description: "unrecognized status code", + giveStatusCode: http.StatusInternalServerError, + giveHeader: http.Header{}, + giveBody: "Internal Server Error", + wantError: core.NewAPIError(http.StatusInternalServerError, http.Header{}, errors.New("Internal Server Error")), + }, + { + description: "not found with valid JSON", + giveStatusCode: http.StatusNotFound, + giveHeader: http.Header{}, + giveBody: `{"message": "Resource not found"}`, + wantError: &InternalTestNotFoundError{ + APIError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New(`{"message": "Resource not found"}`)), + Message: "Resource not found", + }, + }, + { + description: "not found with invalid JSON", + giveStatusCode: http.StatusNotFound, + giveHeader: http.Header{}, + giveBody: `Resource not found`, + wantError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New("Resource not found")), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + assert.Equal(t, tt.wantError, decoder(tt.giveStatusCode, tt.giveHeader, bytes.NewReader([]byte(tt.giveBody)))) + }) + } +} diff --git a/seed/go-sdk/allof-inline/internal/explicit_fields.go b/seed/go-sdk/allof-inline/internal/explicit_fields.go new file mode 100644 index 000000000000..4bdf34fc2b7c --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/explicit_fields.go @@ -0,0 +1,116 @@ +package internal + +import ( + "math/big" + "reflect" + "strings" +) + +// HandleExplicitFields processes a struct to remove `omitempty` from +// fields that have been explicitly set (as indicated by their corresponding bit in explicitFields). +// Note that `marshaler` should be an embedded struct to avoid infinite recursion. +// Returns an interface{} that can be passed to json.Marshal. +func HandleExplicitFields(marshaler interface{}, explicitFields *big.Int) interface{} { + val := reflect.ValueOf(marshaler) + typ := reflect.TypeOf(marshaler) + + // Handle pointer types + if val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil + } + val = val.Elem() + typ = typ.Elem() + } + + // Only handle struct types + if val.Kind() != reflect.Struct { + return marshaler + } + + // Handle embedded struct pattern + var sourceVal reflect.Value + var sourceType reflect.Type + + // Check if this is an embedded struct pattern + if typ.NumField() == 1 && typ.Field(0).Anonymous { + // This is likely an embedded struct, get the embedded value + embeddedField := val.Field(0) + sourceVal = embeddedField + sourceType = embeddedField.Type() + } else { + // Regular struct + sourceVal = val + sourceType = typ + } + + // If no explicit fields set, use standard marshaling + if explicitFields == nil || explicitFields.Sign() == 0 { + return marshaler + } + + // Create a new struct type with modified tags + fields := make([]reflect.StructField, 0, sourceType.NumField()) + + for i := 0; i < sourceType.NumField(); i++ { + field := sourceType.Field(i) + + // Skip unexported fields and the explicitFields field itself + if !field.IsExported() || field.Name == "explicitFields" { + continue + } + + // Check if this field has been explicitly set + fieldBit := big.NewInt(1) + fieldBit.Lsh(fieldBit, uint(i)) + if big.NewInt(0).And(explicitFields, fieldBit).Sign() != 0 { + // Remove omitempty from the json tag + tag := field.Tag.Get("json") + if tag != "" && tag != "-" { + // Parse the json tag, remove omitempty from options + parts := strings.Split(tag, ",") + if len(parts) > 1 { + var newParts []string + newParts = append(newParts, parts[0]) // Keep the field name + for _, part := range parts[1:] { + if strings.TrimSpace(part) != "omitempty" { + newParts = append(newParts, part) + } + } + tag = strings.Join(newParts, ",") + } + + // Reconstruct the struct tag + newTag := `json:"` + tag + `"` + if urlTag := field.Tag.Get("url"); urlTag != "" { + newTag += ` url:"` + urlTag + `"` + } + + field.Tag = reflect.StructTag(newTag) + } + } + + fields = append(fields, field) + } + + // Create new struct type with modified tags + newType := reflect.StructOf(fields) + newVal := reflect.New(newType).Elem() + + // Copy field values from original struct to new struct + fieldIndex := 0 + for i := 0; i < sourceType.NumField(); i++ { + originalField := sourceType.Field(i) + + // Skip unexported fields and the explicitFields field itself + if !originalField.IsExported() || originalField.Name == "explicitFields" { + continue + } + + originalValue := sourceVal.Field(i) + newVal.Field(fieldIndex).Set(originalValue) + fieldIndex++ + } + + return newVal.Interface() +} diff --git a/seed/go-sdk/allof-inline/internal/explicit_fields_test.go b/seed/go-sdk/allof-inline/internal/explicit_fields_test.go new file mode 100644 index 000000000000..f44beec447d6 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/explicit_fields_test.go @@ -0,0 +1,645 @@ +package internal + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testExplicitFieldsStruct struct { + Name *string `json:"name,omitempty"` + Code *string `json:"code,omitempty"` + Count *int `json:"count,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Tags []string `json:"tags,omitempty"` + unexported string `json:"-"` //nolint:unused + explicitFields *big.Int `json:"-"` +} + +var ( + testFieldName = big.NewInt(1 << 0) + testFieldCode = big.NewInt(1 << 1) + testFieldCount = big.NewInt(1 << 2) + testFieldEnabled = big.NewInt(1 << 3) + testFieldTags = big.NewInt(1 << 4) +) + +func (t *testExplicitFieldsStruct) require(field *big.Int) { + if t.explicitFields == nil { + t.explicitFields = big.NewInt(0) + } + t.explicitFields.Or(t.explicitFields, field) +} + +func (t *testExplicitFieldsStruct) SetName(name *string) { + t.Name = name + t.require(testFieldName) +} + +func (t *testExplicitFieldsStruct) SetCode(code *string) { + t.Code = code + t.require(testFieldCode) +} + +func (t *testExplicitFieldsStruct) SetCount(count *int) { + t.Count = count + t.require(testFieldCount) +} + +func (t *testExplicitFieldsStruct) SetEnabled(enabled *bool) { + t.Enabled = enabled + t.require(testFieldEnabled) +} + +func (t *testExplicitFieldsStruct) SetTags(tags []string) { + t.Tags = tags + t.require(testFieldTags) +} + +func (t *testExplicitFieldsStruct) MarshalJSON() ([]byte, error) { + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*t), + } + return json.Marshal(HandleExplicitFields(marshaler, t.explicitFields)) +} + +type testStructWithoutExplicitFields struct { + Name *string `json:"name,omitempty"` + Code *string `json:"code,omitempty"` +} + +func TestHandleExplicitFields(t *testing.T) { + tests := []struct { + desc string + giveInput interface{} + wantBytes []byte + wantError string + }{ + { + desc: "nil input", + giveInput: nil, + wantBytes: []byte(`null`), + }, + { + desc: "non-struct input", + giveInput: "string", + wantBytes: []byte(`"string"`), + }, + { + desc: "slice input", + giveInput: []string{"a", "b"}, + wantBytes: []byte(`["a","b"]`), + }, + { + desc: "map input", + giveInput: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "struct without explicitFields field", + giveInput: &testStructWithoutExplicitFields{ + Name: stringPtr("test"), + Code: nil, + }, + wantBytes: []byte(`{"name":"test"}`), + }, + { + desc: "struct with no explicit fields set", + giveInput: &testExplicitFieldsStruct{ + Name: stringPtr("test"), + Code: nil, + }, + wantBytes: []byte(`{"name":"test"}`), + }, + { + desc: "struct with explicit nil field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Name: stringPtr("test"), + } + s.SetCode(nil) + return s + }(), + wantBytes: []byte(`{"name":"test","code":null}`), + }, + { + desc: "struct with explicit non-nil field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{} + s.SetName(stringPtr("explicit")) + s.SetCode(stringPtr("also-explicit")) + return s + }(), + wantBytes: []byte(`{"name":"explicit","code":"also-explicit"}`), + }, + { + desc: "struct with mixed explicit and implicit fields", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Name: stringPtr("implicit"), + Count: intPtr(42), + } + s.SetCode(nil) // explicit nil + return s + }(), + wantBytes: []byte(`{"name":"implicit","code":null,"count":42}`), + }, + { + desc: "struct with multiple explicit nil fields", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Name: stringPtr("test"), + } + s.SetCode(nil) + s.SetCount(nil) + return s + }(), + wantBytes: []byte(`{"name":"test","code":null,"count":null}`), + }, + { + desc: "struct with slice field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Tags: []string{"tag1", "tag2"}, + } + s.SetTags(nil) // explicit nil slice + return s + }(), + wantBytes: []byte(`{"tags":null}`), + }, + { + desc: "struct with boolean field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{} + s.SetEnabled(boolPtr(false)) // explicit false + return s + }(), + wantBytes: []byte(`{"enabled":false}`), + }, + { + desc: "struct with all fields explicit", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{} + s.SetName(stringPtr("test")) + s.SetCode(nil) + s.SetCount(intPtr(0)) + s.SetEnabled(boolPtr(false)) + s.SetTags([]string{}) + return s + }(), + wantBytes: []byte(`{"name":"test","code":null,"count":0,"enabled":false,"tags":[]}`), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + var explicitFields *big.Int + if s, ok := tt.giveInput.(*testExplicitFieldsStruct); ok { + explicitFields = s.explicitFields + } + bytes, err := json.Marshal(HandleExplicitFields(tt.giveInput, explicitFields)) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.JSONEq(t, string(tt.wantBytes), string(bytes)) + + // Verify it's valid JSON + var value interface{} + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestHandleExplicitFieldsCustomMarshaler(t *testing.T) { + t.Run("custom marshaler with explicit fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + s.SetName(nil) + s.SetCode(stringPtr("test-code")) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) + }) + + t.Run("custom marshaler with no explicit fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("implicit"), + Code: stringPtr("also-implicit"), + } + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) + }) +} + +func TestHandleExplicitFieldsPointerHandling(t *testing.T) { + t.Run("nil pointer", func(t *testing.T) { + var s *testExplicitFieldsStruct + bytes, err := json.Marshal(HandleExplicitFields(s, nil)) + require.NoError(t, err) + assert.Equal(t, []byte(`null`), bytes) + }) + + t.Run("pointer to struct", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + s.SetName(nil) + + bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) + require.NoError(t, err) + assert.JSONEq(t, `{"name":null}`, string(bytes)) + }) +} + +func TestHandleExplicitFieldsEmbeddedStruct(t *testing.T) { + t.Run("embedded struct with explicit fields", func(t *testing.T) { + // Create a struct similar to what MarshalJSON creates + s := &testExplicitFieldsStruct{} + s.SetName(nil) + s.SetCode(stringPtr("test-code")) + + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*s), + } + + bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) + require.NoError(t, err) + // Should include both explicit fields (name as null, code as "test-code") + assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) + }) + + t.Run("embedded struct with no explicit fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("implicit"), + Code: stringPtr("also-implicit"), + } + + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*s), + } + + bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) + require.NoError(t, err) + // Should only include non-nil fields (omitempty behavior) + assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) + }) + + t.Run("embedded struct with mixed fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Count: intPtr(42), // implicit field + } + s.SetName(nil) // explicit nil + s.SetCode(stringPtr("explicit")) // explicit value + + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*s), + } + + bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) + require.NoError(t, err) + // Should include explicit null, explicit value, and implicit value + assert.JSONEq(t, `{"name":null,"code":"explicit","count":42}`, string(bytes)) + }) +} + +func TestHandleExplicitFieldsTagHandling(t *testing.T) { + type testStructWithComplexTags struct { + Field1 *string `json:"field1,omitempty" url:"field1,omitempty"` + Field2 *string `json:"field2,omitempty,string" url:"field2"` + Field3 *string `json:"-"` + Field4 *string `json:"field4"` + explicitFields *big.Int `json:"-"` + } + + s := &testStructWithComplexTags{ + Field1: stringPtr("test1"), + Field4: stringPtr("test4"), + explicitFields: big.NewInt(1), // Only first field is explicit + } + + bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) + require.NoError(t, err) + + // Field1 should have omitempty removed, Field2 should keep omitempty, Field4 should be included + assert.JSONEq(t, `{"field1":"test1","field4":"test4"}`, string(bytes)) +} + +// Test types for nested struct explicit fields testing +type testNestedStruct struct { + NestedName *string `json:"nested_name,omitempty"` + NestedCode *string `json:"nested_code,omitempty"` + explicitFields *big.Int `json:"-"` +} + +type testParentStruct struct { + ParentName *string `json:"parent_name,omitempty"` + Nested *testNestedStruct `json:"nested,omitempty"` + explicitFields *big.Int `json:"-"` +} + +var ( + nestedFieldName = big.NewInt(1 << 0) + nestedFieldCode = big.NewInt(1 << 1) +) + +var ( + parentFieldName = big.NewInt(1 << 0) + parentFieldNested = big.NewInt(1 << 1) +) + +func (n *testNestedStruct) require(field *big.Int) { + if n.explicitFields == nil { + n.explicitFields = big.NewInt(0) + } + n.explicitFields.Or(n.explicitFields, field) +} + +func (n *testNestedStruct) SetNestedName(name *string) { + n.NestedName = name + n.require(nestedFieldName) +} + +func (n *testNestedStruct) SetNestedCode(code *string) { + n.NestedCode = code + n.require(nestedFieldCode) +} + +func (n *testNestedStruct) MarshalJSON() ([]byte, error) { + type embed testNestedStruct + var marshaler = struct { + embed + }{ + embed: embed(*n), + } + return json.Marshal(HandleExplicitFields(marshaler, n.explicitFields)) +} + +func (p *testParentStruct) require(field *big.Int) { + if p.explicitFields == nil { + p.explicitFields = big.NewInt(0) + } + p.explicitFields.Or(p.explicitFields, field) +} + +func (p *testParentStruct) SetParentName(name *string) { + p.ParentName = name + p.require(parentFieldName) +} + +func (p *testParentStruct) SetNested(nested *testNestedStruct) { + p.Nested = nested + p.require(parentFieldNested) +} + +func (p *testParentStruct) MarshalJSON() ([]byte, error) { + type embed testParentStruct + var marshaler = struct { + embed + }{ + embed: embed(*p), + } + return json.Marshal(HandleExplicitFields(marshaler, p.explicitFields)) +} + +func TestHandleExplicitFieldsNestedStruct(t *testing.T) { + tests := []struct { + desc string + setupFunc func() *testParentStruct + wantBytes []byte + }{ + { + desc: "nested struct with explicit nil in nested object", + setupFunc: func() *testParentStruct { + nested := &testNestedStruct{ + NestedName: stringPtr("implicit-nested"), + } + nested.SetNestedCode(nil) // explicit nil + + return &testParentStruct{ + ParentName: stringPtr("implicit-parent"), + Nested: nested, + } + }, + wantBytes: []byte(`{"parent_name":"implicit-parent","nested":{"nested_name":"implicit-nested","nested_code":null}}`), + }, + { + desc: "parent with explicit nil nested struct", + setupFunc: func() *testParentStruct { + parent := &testParentStruct{ + ParentName: stringPtr("implicit-parent"), + } + parent.SetNested(nil) // explicit nil nested struct + return parent + }, + wantBytes: []byte(`{"parent_name":"implicit-parent","nested":null}`), + }, + { + desc: "all explicit fields in nested structure", + setupFunc: func() *testParentStruct { + nested := &testNestedStruct{} + nested.SetNestedName(stringPtr("explicit-nested")) + nested.SetNestedCode(nil) // explicit nil + + parent := &testParentStruct{} + parent.SetParentName(nil) // explicit nil + parent.SetNested(nested) // explicit nested struct + + return parent + }, + wantBytes: []byte(`{"parent_name":null,"nested":{"nested_name":"explicit-nested","nested_code":null}}`), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + parent := tt.setupFunc() + bytes, err := parent.MarshalJSON() + require.NoError(t, err) + assert.JSONEq(t, string(tt.wantBytes), string(bytes)) + + // Verify it's valid JSON + var value interface{} + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +// Test for setter method documentation and behavior +func TestSetterMethodsDocumentation(t *testing.T) { + t.Run("setter prevents omitempty for nil values", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Use setter to explicitly set nil - this should prevent omitempty + s.SetName(nil) + s.SetCode(nil) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Both fields should be included as null, not omitted + assert.JSONEq(t, `{"name":null,"code":null}`, string(bytes)) + }) + + t.Run("setter prevents omitempty for empty slice", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Use setter to explicitly set empty slice + s.SetTags([]string{}) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Empty slice should be included as [], not omitted + assert.JSONEq(t, `{"tags":[]}`, string(bytes)) + }) + + t.Run("setter prevents omitempty for zero values", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Use setter to explicitly set zero values + s.SetCount(intPtr(0)) + s.SetEnabled(boolPtr(false)) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Zero values should be included, not omitted + assert.JSONEq(t, `{"count":0,"enabled":false}`, string(bytes)) + }) + + t.Run("direct assignment is omitted when nil", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: nil, // Direct assignment, not using setter + Code: nil, // Direct assignment, not using setter + } + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Fields not set via setter should be omitted when nil + assert.JSONEq(t, `{}`, string(bytes)) + }) + + t.Run("mix of setter and direct assignment", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("direct"), // Direct assignment + Count: intPtr(42), // Direct assignment + } + s.SetCode(nil) // Setter with nil + s.SetEnabled(boolPtr(false)) // Setter with zero value + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Direct assignments included if non-nil, setter fields always included + assert.JSONEq(t, `{"name":"direct","code":null,"count":42,"enabled":false}`, string(bytes)) + }) +} + +// Test for complex scenarios with multiple setters +func TestComplexSetterScenarios(t *testing.T) { + t.Run("multiple setter calls on same field", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Call setter multiple times - last one should win + s.SetName(stringPtr("first")) + s.SetName(stringPtr("second")) + s.SetName(nil) // Final value is nil + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Should serialize the last set value (nil) + assert.JSONEq(t, `{"name":null}`, string(bytes)) + }) + + t.Run("setter after direct assignment", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("direct"), + } + + // Override with setter + s.SetName(nil) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Setter should mark field as explicit, so nil is serialized + assert.JSONEq(t, `{"name":null}`, string(bytes)) + }) + + t.Run("all fields set via setters", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + s.SetName(nil) + s.SetCode(stringPtr("")) // Empty string + s.SetCount(intPtr(0)) // Zero + s.SetEnabled(boolPtr(false)) // False + s.SetTags(nil) // Nil slice + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // All fields should be present even with nil/zero values + assert.JSONEq(t, `{"name":null,"code":"","count":0,"enabled":false,"tags":null}`, string(bytes)) + }) +} + +// Test for backwards compatibility +func TestBackwardsCompatibility(t *testing.T) { + t.Run("struct without setters behaves normally", func(t *testing.T) { + s := &testStructWithoutExplicitFields{ + Name: stringPtr("test"), + Code: nil, // This should be omitted + } + + bytes, err := json.Marshal(s) + require.NoError(t, err) + + // Without setters, omitempty works normally + assert.JSONEq(t, `{"name":"test"}`, string(bytes)) + }) + + t.Run("struct with explicit fields works with standard json.Marshal", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("test"), + } + s.SetCode(nil) + + // Using the custom MarshalJSON + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + assert.JSONEq(t, `{"name":"test","code":null}`, string(bytes)) + }) +} + +// Helper functions +func stringPtr(s string) *string { + return &s +} + +func intPtr(i int) *int { + return &i +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/seed/go-sdk/allof-inline/internal/extra_properties.go b/seed/go-sdk/allof-inline/internal/extra_properties.go new file mode 100644 index 000000000000..540c3fd89eeb --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/extra_properties.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-sdk/allof-inline/internal/extra_properties_test.go b/seed/go-sdk/allof-inline/internal/extra_properties_test.go new file mode 100644 index 000000000000..aa2510ee5121 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/extra_properties_test.go @@ -0,0 +1,228 @@ +package internal + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-sdk/allof-inline/internal/http.go b/seed/go-sdk/allof-inline/internal/http.go new file mode 100644 index 000000000000..77863752bb58 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/http.go @@ -0,0 +1,71 @@ +package internal + +import ( + "fmt" + "net/http" + "net/url" + "reflect" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// ResolveBaseURL resolves the base URL from the given arguments, +// preferring the first non-empty value. +func ResolveBaseURL(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. Pointer arguments are dereferenced before processing. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + // Dereference the argument if it's a pointer + value := dereferenceArg(arg) + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", value))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// dereferenceArg dereferences a pointer argument if necessary, returning the underlying value. +// If the argument is not a pointer or is nil, it returns the argument as-is. +func dereferenceArg(arg interface{}) interface{} { + if arg == nil { + return arg + } + + v := reflect.ValueOf(arg) + + // Keep dereferencing until we get to a non-pointer value or hit nil + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil + } + v = v.Elem() + } + + return v.Interface() +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} diff --git a/seed/go-sdk/allof-inline/internal/query.go b/seed/go-sdk/allof-inline/internal/query.go new file mode 100644 index 000000000000..9b567f7a5563 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/query.go @@ -0,0 +1,358 @@ +package internal + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +// RFC3339Milli is a time format string for RFC 3339 with millisecond precision. +// Go's time.RFC3339 omits fractional seconds and time.RFC3339Nano trims trailing +// zeros, so neither produces the fixed ".000" millisecond suffix that many APIs expect. +const RFC3339Milli = "2006-01-02T15:04:05.000Z07:00" + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// prepareValue handles common validation and unwrapping logic for both functions +func prepareValue(v interface{}) (reflect.Value, url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return reflect.Value{}, values, nil + } + val = val.Elem() + } + + if v == nil { + return reflect.Value{}, values, nil + } + + if val.Kind() != reflect.Struct { + return reflect.Value{}, nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + if err != nil { + return reflect.Value{}, nil, err + } + + return val, values, nil +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + _, values, err := prepareValue(v) + return values, err +} + +// QueryValuesWithDefaults encodes url.Values from request objects +// and default values, merging the defaults into the request. +// It's expected that the values of defaults are wire names. +func QueryValuesWithDefaults(v interface{}, defaults map[string]interface{}) (url.Values, error) { + val, values, err := prepareValue(v) + if err != nil { + return values, err + } + if !val.IsValid() { + return values, nil + } + + // apply defaults to zero-value fields directly on the original struct + valType := val.Type() + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldType := valType.Field(i) + fieldName := fieldType.Name + + if fieldType.PkgPath != "" && !fieldType.Anonymous { + // Skip unexported fields. + continue + } + + // check if field is zero value and we have a default for it + if field.CanSet() && field.IsZero() { + tag := fieldType.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + wireName, _ := parseTag(tag) + if wireName == "" { + wireName = fieldName + } + if defaultVal, exists := defaults[wireName]; exists { + values.Set(wireName, valueString(reflect.ValueOf(defaultVal), tagOptions{}, reflect.StructField{})) + } + } + } + + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + value := sv.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), name); err != nil { + return err + } + } else { + values.Add(name, valueString(value, opts, sf)) + } + } + continue + } + + if sv.Kind() == reflect.Map { + if err := reflectMap(values, sv, name); err != nil { + return err + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// reflectMap handles map types specifically, generating query parameters in the format key[mapkey]=value +func reflectMap(values url.Values, val reflect.Value, scope string) error { + if val.IsNil() { + return nil + } + + iter := val.MapRange() + for iter.Next() { + k := iter.Key() + v := iter.Value() + + key := fmt.Sprint(k.Interface()) + paramName := scope + "[" + key + "]" + + for v.Kind() == reflect.Ptr { + if v.IsNil() { + break + } + v = v.Elem() + } + + for v.Kind() == reflect.Interface { + v = v.Elem() + } + + if v.Kind() == reflect.Map { + if err := reflectMap(values, v, paramName); err != nil { + return err + } + continue + } + + if v.Kind() == reflect.Struct { + if err := reflectValue(values, v, paramName); err != nil { + return err + } + continue + } + + if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { + if v.Len() == 0 { + continue + } + for i := 0; i < v.Len(); i++ { + value := v.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), paramName); err != nil { + return err + } + } else { + values.Add(paramName, valueString(value, tagOptions{}, reflect.StructField{})) + } + } + continue + } + + values.Add(paramName, valueString(v, tagOptions{}, reflect.StructField{})) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(RFC3339Milli) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsZero() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// isStructPointer returns true if the given reflect.Value is a pointer to a struct. +func isStructPointer(v reflect.Value) bool { + return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/allof-inline/internal/query_test.go b/seed/go-sdk/allof-inline/internal/query_test.go new file mode 100644 index 000000000000..5b463e297350 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/query_test.go @@ -0,0 +1,395 @@ +package internal + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56.000Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("omitempty with non-pointer zero value", func(t *testing.T) { + type enum string + + type example struct { + Enum enum `json:"enum,omitempty" url:"enum,omitempty"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("object array", func(t *testing.T) { + type object struct { + Key string `json:"key" url:"key"` + Value string `json:"value" url:"value"` + } + type example struct { + Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` + } + + values, err := QueryValues( + &example{ + Objects: []*object{ + { + Key: "hello", + Value: "world", + }, + { + Key: "foo", + Value: "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) + }) + + t.Run("map", func(t *testing.T) { + type request struct { + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + } + values, err := QueryValues( + &request{ + Metadata: map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "metadata%5Bbaz%5D=qux&metadata%5Bfoo%5D=bar", values.Encode()) + }) + + t.Run("nested map", func(t *testing.T) { + type request struct { + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + } + values, err := QueryValues( + &request{ + Metadata: map[string]interface{}{ + "inner": map[string]interface{}{ + "foo": "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "metadata%5Binner%5D%5Bfoo%5D=bar", values.Encode()) + }) + + t.Run("nested map array", func(t *testing.T) { + type request struct { + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + } + values, err := QueryValues( + &request{ + Metadata: map[string]interface{}{ + "inner": []string{ + "one", + "two", + "three", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "metadata%5Binner%5D=one&metadata%5Binner%5D=two&metadata%5Binner%5D=three", values.Encode()) + }) +} + +func TestQueryValuesWithDefaults(t *testing.T) { + t.Run("apply defaults to zero values", func(t *testing.T) { + type example struct { + Name string `json:"name" url:"name"` + Age int `json:"age" url:"age"` + Enabled bool `json:"enabled" url:"enabled"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "enabled": true, + } + + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) + }) + + t.Run("preserve non-zero values over defaults", func(t *testing.T) { + type example struct { + Name string `json:"name" url:"name"` + Age int `json:"age" url:"age"` + Enabled bool `json:"enabled" url:"enabled"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "enabled": true, + } + + values, err := QueryValuesWithDefaults(&example{ + Name: "actual-name", + Age: 30, + // Enabled remains false (zero value), should get default + }, defaults) + require.NoError(t, err) + assert.Equal(t, "age=30&enabled=true&name=actual-name", values.Encode()) + }) + + t.Run("ignore defaults for fields not in struct", func(t *testing.T) { + type example struct { + Name string `json:"name" url:"name"` + Age int `json:"age" url:"age"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "nonexistent": "should-be-ignored", + } + + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "age=25&name=default-name", values.Encode()) + }) + + t.Run("type conversion for compatible defaults", func(t *testing.T) { + type example struct { + Count int64 `json:"count" url:"count"` + Rate float64 `json:"rate" url:"rate"` + Message string `json:"message" url:"message"` + } + + defaults := map[string]interface{}{ + "count": int(100), // int -> int64 conversion + "rate": float32(2.5), // float32 -> float64 conversion + "message": "hello", // string -> string (no conversion needed) + } + + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "count=100&message=hello&rate=2.5", values.Encode()) + }) + + t.Run("mixed with pointer fields and omitempty", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + Optional *string `json:"optional,omitempty" url:"optional,omitempty"` + Count int `json:"count,omitempty" url:"count,omitempty"` + } + + defaultOptional := "default-optional" + defaults := map[string]interface{}{ + "required": "default-required", + "optional": &defaultOptional, // pointer type + "count": 42, + } + + values, err := QueryValuesWithDefaults(&example{ + Required: "custom-required", // should override default + // Optional is nil, should get default + // Count is 0, should get default + }, defaults) + require.NoError(t, err) + assert.Equal(t, "count=42&optional=default-optional&required=custom-required", values.Encode()) + }) + + t.Run("override non-zero defaults with explicit zero values", func(t *testing.T) { + type example struct { + Name *string `json:"name" url:"name"` + Age *int `json:"age" url:"age"` + Enabled *bool `json:"enabled" url:"enabled"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "enabled": true, + } + + // first, test that a properly empty request is overridden: + { + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) + } + + // second, test that a request that contains zeros is not overridden: + var ( + name = "" + age = 0 + enabled = false + ) + values, err := QueryValuesWithDefaults(&example{ + Name: &name, // explicit empty string should override default + Age: &age, // explicit zero should override default + Enabled: &enabled, // explicit false should override default + }, defaults) + require.NoError(t, err) + assert.Equal(t, "age=0&enabled=false&name=", values.Encode()) + }) + + t.Run("nil input returns empty values", func(t *testing.T) { + defaults := map[string]any{ + "name": "default-name", + "age": 25, + } + + // Test with nil + values, err := QueryValuesWithDefaults(nil, defaults) + require.NoError(t, err) + assert.Empty(t, values) + + // Test with nil pointer + type example struct { + Name string `json:"name" url:"name"` + } + var nilPtr *example + values, err = QueryValuesWithDefaults(nilPtr, defaults) + require.NoError(t, err) + assert.Empty(t, values) + }) +} diff --git a/seed/go-sdk/allof-inline/internal/retrier.go b/seed/go-sdk/allof-inline/internal/retrier.go new file mode 100644 index 000000000000..02fd1fb7d3f1 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/retrier.go @@ -0,0 +1,239 @@ +package internal + +import ( + "crypto/rand" + "math/big" + "net/http" + "strconv" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 1000 * time.Millisecond + maxRetryDelay = 60000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retryable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retryable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + // Reset the request body for retries since the body may have already been read. + if retryAttempt > 0 && request.GetBody != nil { + requestBody, err := request.GetBody() + if err != nil { + return nil, err + } + request.Body = requestBody + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer func() { _ = response.Body.Close() }() + + delay, err := r.retryDelay(response, retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time based on response headers, +// falling back to exponential backoff if no headers are present. +func (r *Retrier) retryDelay(response *http.Response, retryAttempt uint) (time.Duration, error) { + // Check for Retry-After header first (RFC 7231), applying no jitter + if retryAfter := response.Header.Get("Retry-After"); retryAfter != "" { + // Parse as number of seconds... + if seconds, err := strconv.Atoi(retryAfter); err == nil { + delay := time.Duration(seconds) * time.Second + if delay > 0 { + if delay > maxRetryDelay { + delay = maxRetryDelay + } + return delay, nil + } + } + + // ...or as an HTTP date; both are valid + if retryTime, err := time.Parse(time.RFC1123, retryAfter); err == nil { + delay := time.Until(retryTime) + if delay > 0 { + if delay > maxRetryDelay { + delay = maxRetryDelay + } + return delay, nil + } + } + } + + // Then check for industry-standard X-RateLimit-Reset header, applying positive jitter + if rateLimitReset := response.Header.Get("X-RateLimit-Reset"); rateLimitReset != "" { + if resetTimestamp, err := strconv.ParseInt(rateLimitReset, 10, 64); err == nil { + // Assume Unix timestamp in seconds + resetTime := time.Unix(resetTimestamp, 0) + delay := time.Until(resetTime) + if delay > 0 { + if delay > maxRetryDelay { + delay = maxRetryDelay + } + return r.addPositiveJitter(delay) + } + } + } + + // Fall back to exponential backoff + return r.exponentialBackoff(retryAttempt) +} + +// exponentialBackoff calculates the delay time based on the retry attempt +// and applies symmetric jitter (±10% around the delay). +func (r *Retrier) exponentialBackoff(retryAttempt uint) (time.Duration, error) { + if retryAttempt > 63 { // 2^63+ would overflow uint64 + retryAttempt = 63 + } + + delay := minRetryDelay << retryAttempt + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + return r.addSymmetricJitter(delay) +} + +// addJitterWithRange applies jitter to the given delay. +// minPercent and maxPercent define the jitter range (e.g., 100, 120 for +0% to +20%). +func (r *Retrier) addJitterWithRange(delay time.Duration, minPercent, maxPercent int) (time.Duration, error) { + jitterRange := big.NewInt(int64(delay * time.Duration(maxPercent-minPercent) / 100)) + jitter, err := rand.Int(rand.Reader, jitterRange) + if err != nil { + return 0, err + } + + jitteredDelay := delay + time.Duration(jitter.Int64()) + delay*time.Duration(minPercent-100)/100 + if jitteredDelay < minRetryDelay { + jitteredDelay = minRetryDelay + } + if jitteredDelay > maxRetryDelay { + jitteredDelay = maxRetryDelay + } + return jitteredDelay, nil +} + +// addPositiveJitter applies positive jitter to the given delay (100%-120% range). +func (r *Retrier) addPositiveJitter(delay time.Duration) (time.Duration, error) { + return r.addJitterWithRange(delay, 100, 120) +} + +// addSymmetricJitter applies symmetric jitter to the given delay (90%-110% range). +func (r *Retrier) addSymmetricJitter(delay time.Duration) (time.Duration, error) { + return r.addJitterWithRange(delay, 90, 110) +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/allof-inline/internal/retrier_test.go b/seed/go-sdk/allof-inline/internal/retrier_test.go new file mode 100644 index 000000000000..8c4692cdf509 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/retrier_test.go @@ -0,0 +1,352 @@ +package internal + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/allof-inline/fern/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type RetryTestCase struct { + description string + + giveAttempts uint + giveStatusCodes []int + giveResponse *InternalTestResponse + + wantResponse *InternalTestResponse + wantError *core.APIError +} + +func TestRetrier(t *testing.T) { + tests := []*RetryTestCase{ + { + description: "retry request succeeds after multiple failures", + giveAttempts: 3, + giveStatusCodes: []int{ + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusOK, + }, + giveResponse: &InternalTestResponse{ + Id: "1", + }, + wantResponse: &InternalTestResponse{ + Id: "1", + }, + }, + { + description: "retry request fails if MaxAttempts is exceeded", + giveAttempts: 3, + giveStatusCodes: []int{ + http.StatusRequestTimeout, + http.StatusRequestTimeout, + http.StatusRequestTimeout, + http.StatusOK, + }, + wantError: &core.APIError{ + StatusCode: http.StatusRequestTimeout, + }, + }, + { + description: "retry durations increase exponentially and stay within the min and max delay values", + giveAttempts: 4, + giveStatusCodes: []int{ + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusOK, + }, + }, + { + description: "retry does not occur on status code 404", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusNotFound, http.StatusOK}, + wantError: &core.APIError{ + StatusCode: http.StatusNotFound, + }, + }, + { + description: "retries occur on status code 429", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusTooManyRequests, http.StatusOK}, + }, + { + description: "retries occur on status code 408", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusRequestTimeout, http.StatusOK}, + }, + { + description: "retries occur on status code 500", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusInternalServerError, http.StatusOK}, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + var ( + test = tc + server = newTestRetryServer(t, test) + client = server.Client() + ) + + t.Parallel() + + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: http.MethodGet, + Request: &InternalTestRequest{}, + Response: &response, + MaxAttempts: test.giveAttempts, + ResponseIsOptional: true, + }, + ) + + if test.wantError != nil { + require.IsType(t, err, &core.APIError{}) + expectedErrorCode := test.wantError.StatusCode + actualErrorCode := err.(*core.APIError).StatusCode + assert.Equal(t, expectedErrorCode, actualErrorCode) + return + } + + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +// newTestRetryServer returns a new *httptest.Server configured with the +// given test parameters, suitable for testing retries. +func newTestRetryServer(t *testing.T, tc *RetryTestCase) *httptest.Server { + var index int + timestamps := make([]time.Time, 0, len(tc.giveStatusCodes)) + + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + timestamps = append(timestamps, time.Now()) + if index > 0 && index < len(expectedRetryDurations) { + // Ensure that the duration between retries increases exponentially, + // and that it is within the minimum and maximum retry delay values. + actualDuration := timestamps[index].Sub(timestamps[index-1]) + expectedDurationMin := expectedRetryDurations[index-1] * 50 / 100 + expectedDurationMax := expectedRetryDurations[index-1] * 150 / 100 + assert.True( + t, + actualDuration >= expectedDurationMin && actualDuration <= expectedDurationMax, + "expected duration to be in range [%v, %v], got %v", + expectedDurationMin, + expectedDurationMax, + actualDuration, + ) + assert.LessOrEqual( + t, + actualDuration, + maxRetryDelay, + "expected duration to be less than the maxRetryDelay (%v), got %v", + maxRetryDelay, + actualDuration, + ) + assert.GreaterOrEqual( + t, + actualDuration, + minRetryDelay, + "expected duration to be greater than the minRetryDelay (%v), got %v", + minRetryDelay, + actualDuration, + ) + } + + request := new(InternalTestRequest) + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + require.LessOrEqual(t, index, len(tc.giveStatusCodes)) + + statusCode := tc.giveStatusCodes[index] + + w.WriteHeader(statusCode) + + if tc.giveResponse != nil && statusCode == http.StatusOK { + bytes, err = json.Marshal(tc.giveResponse) + require.NoError(t, err) + _, err = w.Write(bytes) + require.NoError(t, err) + } + + index++ + }, + ), + ) +} + +// expectedRetryDurations holds an array of calculated retry durations, +// where the index of the array should correspond to the retry attempt. +// +// Values are calculated based off of `minRetryDelay * 2^i`. +var expectedRetryDurations = []time.Duration{ + 1000 * time.Millisecond, // 500ms * 2^1 = 1000ms + 2000 * time.Millisecond, // 500ms * 2^2 = 2000ms + 4000 * time.Millisecond, // 500ms * 2^3 = 4000ms + 8000 * time.Millisecond, // 500ms * 2^4 = 8000ms +} + +func TestRetryWithRequestBody(t *testing.T) { + // This test verifies that POST requests with a body are properly retried. + // The request body should be re-sent on each retry attempt. + expectedBody := `{"id":"test-id"}` + var requestBodies []string + var requestCount int + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount++ + bodyBytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + requestBodies = append(requestBodies, string(bodyBytes)) + + if requestCount == 1 { + // First request - return retryable error + w.WriteHeader(http.StatusServiceUnavailable) + return + } + // Second request - return success + w.WriteHeader(http.StatusOK) + response := &InternalTestResponse{Id: "success"} + bytes, _ := json.Marshal(response) + _, _ = w.Write(bytes) + })) + defer server.Close() + + caller := NewCaller(&CallerParams{ + Client: server.Client(), + }) + + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: http.MethodPost, + Request: &InternalTestRequest{Id: "test-id"}, + Response: &response, + MaxAttempts: 2, + ResponseIsOptional: true, + }, + ) + + require.NoError(t, err) + require.Equal(t, 2, requestCount, "Expected exactly 2 requests") + require.Len(t, requestBodies, 2, "Expected 2 request bodies to be captured") + + // Both requests should have the same non-empty body + assert.Equal(t, expectedBody, requestBodies[0], "First request body should match expected") + assert.Equal(t, expectedBody, requestBodies[1], "Second request body should match expected (retry should re-send body)") +} + +func TestRetryDelayTiming(t *testing.T) { + tests := []struct { + name string + headerName string + headerValueFunc func() string + expectedMinMs int64 + expectedMaxMs int64 + }{ + { + name: "retry-after with seconds value", + headerName: "retry-after", + headerValueFunc: func() string { + return "1" + }, + expectedMinMs: 500, + expectedMaxMs: 1500, + }, + { + name: "retry-after with HTTP date", + headerName: "retry-after", + headerValueFunc: func() string { + return time.Now().Add(3 * time.Second).Format(time.RFC1123) + }, + expectedMinMs: 1500, + expectedMaxMs: 4500, + }, + { + name: "x-ratelimit-reset with future timestamp", + headerName: "x-ratelimit-reset", + headerValueFunc: func() string { + return fmt.Sprintf("%d", time.Now().Add(3*time.Second).Unix()) + }, + expectedMinMs: 1500, + expectedMaxMs: 4500, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var timestamps []time.Time + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + timestamps = append(timestamps, time.Now()) + if len(timestamps) == 1 { + // First request - return retryable error with header + w.Header().Set(tt.headerName, tt.headerValueFunc()) + w.WriteHeader(http.StatusTooManyRequests) + } else { + // Second request - return success + w.WriteHeader(http.StatusOK) + response := &InternalTestResponse{Id: "success"} + bytes, _ := json.Marshal(response) + _, _ = w.Write(bytes) + } + })) + defer server.Close() + + caller := NewCaller(&CallerParams{ + Client: server.Client(), + }) + + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: http.MethodGet, + Request: &InternalTestRequest{}, + Response: &response, + MaxAttempts: 2, + ResponseIsOptional: true, + }, + ) + + require.NoError(t, err) + require.Len(t, timestamps, 2, "Expected exactly 2 requests") + + actualDelayMs := timestamps[1].Sub(timestamps[0]).Milliseconds() + + assert.GreaterOrEqual(t, actualDelayMs, tt.expectedMinMs, + "Actual delay %dms should be >= expected min %dms", actualDelayMs, tt.expectedMinMs) + assert.LessOrEqual(t, actualDelayMs, tt.expectedMaxMs, + "Actual delay %dms should be <= expected max %dms", actualDelayMs, tt.expectedMaxMs) + }) + } +} diff --git a/seed/go-sdk/allof-inline/internal/stringer.go b/seed/go-sdk/allof-inline/internal/stringer.go new file mode 100644 index 000000000000..312801851e0e --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/stringer.go @@ -0,0 +1,13 @@ +package internal + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/allof-inline/internal/time.go b/seed/go-sdk/allof-inline/internal/time.go new file mode 100644 index 000000000000..57f901a35ed8 --- /dev/null +++ b/seed/go-sdk/allof-inline/internal/time.go @@ -0,0 +1,165 @@ +package internal + +import ( + "encoding/json" + "fmt" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + // If the value is not a string, check if it is a number (unix epoch seconds). + var epoch int64 + if numErr := json.Unmarshal(data, &epoch); numErr == nil { + t := time.Unix(epoch, 0).UTC() + *d = DateTime{t: &t} + return nil + } + return err + } + + // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). + parsedTime, err := time.Parse(time.RFC3339Nano, raw) + if err == nil { + *d = DateTime{t: &parsedTime} + return nil + } + rfc3339NanoErr := err + + // Fall back to ISO 8601 without timezone (assume UTC). + parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + iso8601Err := err + + // Fall back to date-only format. + parsedTime, err = time.Parse("2006-01-02", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + dateOnlyErr := err + + return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) +} diff --git a/seed/go-sdk/allof-inline/option/request_option.go b/seed/go-sdk/allof-inline/option/request_option.go new file mode 100644 index 000000000000..f91507faf8c9 --- /dev/null +++ b/seed/go-sdk/allof-inline/option/request_option.go @@ -0,0 +1,73 @@ +// Code generated by Fern. DO NOT EDIT. + +package option + +import ( + core "github.com/allof-inline/fern/core" + http "net/http" + url "net/url" +) + +// RequestOption adapts the behavior of an individual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithBodyProperties adds the given body properties to the request. +func WithBodyProperties(bodyProperties map[string]interface{}) *core.BodyPropertiesOption { + copiedBodyProperties := make(map[string]interface{}, len(bodyProperties)) + for key, value := range bodyProperties { + copiedBodyProperties[key] = value + } + return &core.BodyPropertiesOption{ + BodyProperties: copiedBodyProperties, + } +} + +// WithQueryParameters adds the given query parameters to the request. +func WithQueryParameters(queryParameters url.Values) *core.QueryParametersOption { + copiedQueryParameters := make(url.Values, len(queryParameters)) + for key, values := range queryParameters { + copiedQueryParameters[key] = values + } + return &core.QueryParametersOption{ + QueryParameters: copiedQueryParameters, + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithMaxStreamBufSize configures the maximum buffer size for streaming responses. +// This controls the maximum size of a single message (in bytes) that the stream +// can process. By default, this is set to 1MB. +func WithMaxStreamBufSize(size int) *core.MaxBufSizeOption { + return &core.MaxBufSizeOption{ + MaxBufSize: size, + } +} diff --git a/seed/go-sdk/allof-inline/pointer.go b/seed/go-sdk/allof-inline/pointer.go new file mode 100644 index 000000000000..f9a8177e734f --- /dev/null +++ b/seed/go-sdk/allof-inline/pointer.go @@ -0,0 +1,137 @@ +package api + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Bytes returns a pointer to the given []byte value. +func Bytes(b []byte) *[]byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/allof-inline/pointer_test.go b/seed/go-sdk/allof-inline/pointer_test.go new file mode 100644 index 000000000000..06d62d2410b2 --- /dev/null +++ b/seed/go-sdk/allof-inline/pointer_test.go @@ -0,0 +1,211 @@ +package api + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestBool(t *testing.T) { + value := true + ptr := Bool(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestByte(t *testing.T) { + value := byte(42) + ptr := Byte(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestComplex64(t *testing.T) { + value := complex64(1 + 2i) + ptr := Complex64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestComplex128(t *testing.T) { + value := complex128(1 + 2i) + ptr := Complex128(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestFloat32(t *testing.T) { + value := float32(3.14) + ptr := Float32(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestFloat64(t *testing.T) { + value := 3.14159 + ptr := Float64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt(t *testing.T) { + value := 42 + ptr := Int(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt8(t *testing.T) { + value := int8(42) + ptr := Int8(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt16(t *testing.T) { + value := int16(42) + ptr := Int16(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt32(t *testing.T) { + value := int32(42) + ptr := Int32(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt64(t *testing.T) { + value := int64(42) + ptr := Int64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestRune(t *testing.T) { + value := 'A' + ptr := Rune(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestString(t *testing.T) { + value := "hello" + ptr := String(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint(t *testing.T) { + value := uint(42) + ptr := Uint(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint8(t *testing.T) { + value := uint8(42) + ptr := Uint8(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint16(t *testing.T) { + value := uint16(42) + ptr := Uint16(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint32(t *testing.T) { + value := uint32(42) + ptr := Uint32(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint64(t *testing.T) { + value := uint64(42) + ptr := Uint64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUintptr(t *testing.T) { + value := uintptr(42) + ptr := Uintptr(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUUID(t *testing.T) { + value := uuid.New() + ptr := UUID(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestTime(t *testing.T) { + value := time.Now() + ptr := Time(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestMustParseDate(t *testing.T) { + t.Run("valid date", func(t *testing.T) { + result := MustParseDate("2024-01-15") + expected, _ := time.Parse("2006-01-02", "2024-01-15") + assert.Equal(t, expected, result) + }) + + t.Run("invalid date panics", func(t *testing.T) { + assert.Panics(t, func() { + MustParseDate("invalid-date") + }) + }) +} + +func TestMustParseDateTime(t *testing.T) { + t.Run("valid datetime", func(t *testing.T) { + result := MustParseDateTime("2024-01-15T10:30:00Z") + expected, _ := time.Parse(time.RFC3339, "2024-01-15T10:30:00Z") + assert.Equal(t, expected, result) + }) + + t.Run("invalid datetime panics", func(t *testing.T) { + assert.Panics(t, func() { + MustParseDateTime("invalid-datetime") + }) + }) +} + +func TestPointerHelpersWithZeroValues(t *testing.T) { + t.Run("zero bool", func(t *testing.T) { + ptr := Bool(false) + assert.NotNil(t, ptr) + assert.Equal(t, false, *ptr) + }) + + t.Run("zero int", func(t *testing.T) { + ptr := Int(0) + assert.NotNil(t, ptr) + assert.Equal(t, 0, *ptr) + }) + + t.Run("empty string", func(t *testing.T) { + ptr := String("") + assert.NotNil(t, ptr) + assert.Equal(t, "", *ptr) + }) + + t.Run("zero time", func(t *testing.T) { + zeroTime := time.Time{} + ptr := Time(zeroTime) + assert.NotNil(t, ptr) + assert.Equal(t, zeroTime, *ptr) + }) +} diff --git a/seed/go-sdk/allof-inline/reference.md b/seed/go-sdk/allof-inline/reference.md new file mode 100644 index 000000000000..86fef51f321b --- /dev/null +++ b/seed/go-sdk/allof-inline/reference.md @@ -0,0 +1,186 @@ +# Reference +
client.SearchRuleTypes() -> *fern.RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +request := &fern.SearchRuleTypesRequest{} +client.SearchRuleTypes( + context.TODO(), + request, + ) +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `*string` + +
+
+
+
+ + +
+
+
+ +
client.CreateRule(request) -> *fern.RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } +client.CreateRule( + context.TODO(), + request, + ) +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `string` + +
+
+ +
+
+ +**executionContext:** `*fern.RuleExecutionContext` + +
+
+
+
+ + +
+
+
+ +
client.ListUsers() -> *fern.UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +client.ListUsers( + context.TODO(), + ) +} +``` +
+
+
+
+ + +
+
+
+ +
client.GetEntity() -> *fern.CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +client.GetEntity( + context.TODO(), + ) +} +``` +
+
+
+
+ + +
+
+
+ +
client.GetOrganization() -> *fern.Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +client.GetOrganization( + context.TODO(), + ) +} +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/go-sdk/allof-inline/snippet.json b/seed/go-sdk/allof-inline/snippet.json new file mode 100644 index 000000000000..1b623ba80b32 --- /dev/null +++ b/seed/go-sdk/allof-inline/snippet.json @@ -0,0 +1,59 @@ +{ + "endpoints": [ + { + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetEntity(\n\tcontext.TODO(),\n)\n" + } + }, + { + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetOrganization(\n\tcontext.TODO(),\n)\n" + } + }, + { + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof-inline/fern\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.SearchRuleTypes(\n\tcontext.TODO(),\n\t\u0026fern.SearchRuleTypesRequest{},\n)\n" + } + }, + { + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof-inline/fern\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.CreateRule(\n\tcontext.TODO(),\n\t\u0026fern.RuleCreateRequest{\n\t\tName: \"name\",\n\t\tExecutionContext: fern.RuleExecutionContextProd,\n\t},\n)\n" + } + }, + { + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.ListUsers(\n\tcontext.TODO(),\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/allof-inline/types.go b/seed/go-sdk/allof-inline/types.go new file mode 100644 index 000000000000..096cbdbff438 --- /dev/null +++ b/seed/go-sdk/allof-inline/types.go @@ -0,0 +1,2091 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + json "encoding/json" + fmt "fmt" + internal "github.com/allof-inline/fern/internal" + big "math/big" + time "time" +) + +var ( + ruleCreateRequestFieldName = big.NewInt(1 << 0) + ruleCreateRequestFieldExecutionContext = big.NewInt(1 << 1) +) + +type RuleCreateRequest struct { + Name string `json:"name" url:"-"` + ExecutionContext RuleExecutionContext `json:"executionContext" url:"-"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` +} + +func (r *RuleCreateRequest) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleCreateRequest) SetName(name string) { + r.Name = name + r.require(ruleCreateRequestFieldName) +} + +// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleCreateRequest) SetExecutionContext(executionContext RuleExecutionContext) { + r.ExecutionContext = executionContext + r.require(ruleCreateRequestFieldExecutionContext) +} + +func (r *RuleCreateRequest) UnmarshalJSON(data []byte) error { + type unmarshaler RuleCreateRequest + var body unmarshaler + if err := json.Unmarshal(data, &body); err != nil { + return err + } + *r = RuleCreateRequest(body) + return nil +} + +func (r *RuleCreateRequest) MarshalJSON() ([]byte, error) { + type embed RuleCreateRequest + var marshaler = struct { + embed + }{ + embed: embed(*r), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +var ( + searchRuleTypesRequestFieldQuery = big.NewInt(1 << 0) +) + +type SearchRuleTypesRequest struct { + Query *string `json:"-" url:"query,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` +} + +func (s *SearchRuleTypesRequest) require(field *big.Int) { + if s.explicitFields == nil { + s.explicitFields = big.NewInt(0) + } + s.explicitFields.Or(s.explicitFields, field) +} + +// SetQuery sets the Query field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (s *SearchRuleTypesRequest) SetQuery(query *string) { + s.Query = query + s.require(searchRuleTypesRequestFieldQuery) +} + +// Common audit metadata. +var ( + auditInfoFieldCreatedBy = big.NewInt(1 << 0) + auditInfoFieldCreatedDateTime = big.NewInt(1 << 1) + auditInfoFieldModifiedBy = big.NewInt(1 << 2) + auditInfoFieldModifiedDateTime = big.NewInt(1 << 3) +) + +type AuditInfo struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (a *AuditInfo) GetCreatedBy() *string { + if a == nil { + return nil + } + return a.CreatedBy +} + +func (a *AuditInfo) GetCreatedDateTime() *time.Time { + if a == nil { + return nil + } + return a.CreatedDateTime +} + +func (a *AuditInfo) GetModifiedBy() *string { + if a == nil { + return nil + } + return a.ModifiedBy +} + +func (a *AuditInfo) GetModifiedDateTime() *time.Time { + if a == nil { + return nil + } + return a.ModifiedDateTime +} + +func (a *AuditInfo) GetExtraProperties() map[string]interface{} { + if a == nil { + return nil + } + return a.extraProperties +} + +func (a *AuditInfo) require(field *big.Int) { + if a.explicitFields == nil { + a.explicitFields = big.NewInt(0) + } + a.explicitFields.Or(a.explicitFields, field) +} + +// SetCreatedBy sets the CreatedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetCreatedBy(createdBy *string) { + a.CreatedBy = createdBy + a.require(auditInfoFieldCreatedBy) +} + +// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetCreatedDateTime(createdDateTime *time.Time) { + a.CreatedDateTime = createdDateTime + a.require(auditInfoFieldCreatedDateTime) +} + +// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetModifiedBy(modifiedBy *string) { + a.ModifiedBy = modifiedBy + a.require(auditInfoFieldModifiedBy) +} + +// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetModifiedDateTime(modifiedDateTime *time.Time) { + a.ModifiedDateTime = modifiedDateTime + a.require(auditInfoFieldModifiedDateTime) +} + +func (a *AuditInfo) UnmarshalJSON(data []byte) error { + type embed AuditInfo + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *a = AuditInfo(unmarshaler.embed) + a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *a) + if err != nil { + return err + } + a.extraProperties = extraProperties + a.rawJSON = json.RawMessage(data) + return nil +} + +func (a *AuditInfo) MarshalJSON() ([]byte, error) { + type embed AuditInfo + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*a), + CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, a.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (a *AuditInfo) String() string { + if a == nil { + return "" + } + if len(a.rawJSON) > 0 { + if value, err := internal.StringifyJSON(a.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +var ( + baseOrgFieldID = big.NewInt(1 << 0) + baseOrgFieldMetadata = big.NewInt(1 << 1) +) + +type BaseOrg struct { + ID string `json:"id" url:"id"` + Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (b *BaseOrg) GetID() string { + if b == nil { + return "" + } + return b.ID +} + +func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { + if b == nil { + return nil + } + return b.Metadata +} + +func (b *BaseOrg) GetExtraProperties() map[string]interface{} { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrg) require(field *big.Int) { + if b.explicitFields == nil { + b.explicitFields = big.NewInt(0) + } + b.explicitFields.Or(b.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrg) SetID(id string) { + b.ID = id + b.require(baseOrgFieldID) +} + +// SetMetadata sets the Metadata field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrg) SetMetadata(metadata *BaseOrgMetadata) { + b.Metadata = metadata + b.require(baseOrgFieldMetadata) +} + +func (b *BaseOrg) UnmarshalJSON(data []byte) error { + type unmarshaler BaseOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrg) MarshalJSON() ([]byte, error) { + type embed BaseOrg + var marshaler = struct { + embed + }{ + embed: embed(*b), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (b *BaseOrg) String() string { + if b == nil { + return "" + } + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +var ( + baseOrgMetadataFieldRegion = big.NewInt(1 << 0) + baseOrgMetadataFieldTier = big.NewInt(1 << 1) +) + +type BaseOrgMetadata struct { + // Deployment region from BaseOrg. + Region string `json:"region" url:"region"` + // Subscription tier. + Tier *string `json:"tier,omitempty" url:"tier,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (b *BaseOrgMetadata) GetRegion() string { + if b == nil { + return "" + } + return b.Region +} + +func (b *BaseOrgMetadata) GetTier() *string { + if b == nil { + return nil + } + return b.Tier +} + +func (b *BaseOrgMetadata) GetExtraProperties() map[string]interface{} { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrgMetadata) require(field *big.Int) { + if b.explicitFields == nil { + b.explicitFields = big.NewInt(0) + } + b.explicitFields.Or(b.explicitFields, field) +} + +// SetRegion sets the Region field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrgMetadata) SetRegion(region string) { + b.Region = region + b.require(baseOrgMetadataFieldRegion) +} + +// SetTier sets the Tier field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrgMetadata) SetTier(tier *string) { + b.Tier = tier + b.require(baseOrgMetadataFieldTier) +} + +func (b *BaseOrgMetadata) UnmarshalJSON(data []byte) error { + type unmarshaler BaseOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrgMetadata) MarshalJSON() ([]byte, error) { + type embed BaseOrgMetadata + var marshaler = struct { + embed + }{ + embed: embed(*b), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (b *BaseOrgMetadata) String() string { + if b == nil { + return "" + } + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +var ( + combinedEntityFieldID = big.NewInt(1 << 0) + combinedEntityFieldName = big.NewInt(1 << 1) + combinedEntityFieldSummary = big.NewInt(1 << 2) + combinedEntityFieldStatus = big.NewInt(1 << 3) +) + +type CombinedEntity struct { + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Describable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + Status CombinedEntityStatus `json:"status" url:"status"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (c *CombinedEntity) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *CombinedEntity) GetName() *string { + if c == nil { + return nil + } + return c.Name +} + +func (c *CombinedEntity) GetSummary() *string { + if c == nil { + return nil + } + return c.Summary +} + +func (c *CombinedEntity) GetStatus() CombinedEntityStatus { + if c == nil { + return "" + } + return c.Status +} + +func (c *CombinedEntity) GetExtraProperties() map[string]interface{} { + if c == nil { + return nil + } + return c.extraProperties +} + +func (c *CombinedEntity) require(field *big.Int) { + if c.explicitFields == nil { + c.explicitFields = big.NewInt(0) + } + c.explicitFields.Or(c.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetID(id string) { + c.ID = id + c.require(combinedEntityFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetName(name *string) { + c.Name = name + c.require(combinedEntityFieldName) +} + +// SetSummary sets the Summary field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetSummary(summary *string) { + c.Summary = summary + c.require(combinedEntityFieldSummary) +} + +// SetStatus sets the Status field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetStatus(status CombinedEntityStatus) { + c.Status = status + c.require(combinedEntityFieldStatus) +} + +func (c *CombinedEntity) UnmarshalJSON(data []byte) error { + type unmarshaler CombinedEntity + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CombinedEntity(value) + extraProperties, err := internal.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + c.rawJSON = json.RawMessage(data) + return nil +} + +func (c *CombinedEntity) MarshalJSON() ([]byte, error) { + type embed CombinedEntity + var marshaler = struct { + embed + }{ + embed: embed(*c), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, c.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (c *CombinedEntity) String() string { + if c == nil { + return "" + } + if len(c.rawJSON) > 0 { + if value, err := internal.StringifyJSON(c.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type CombinedEntityStatus string + +const ( + CombinedEntityStatusActive CombinedEntityStatus = "active" + CombinedEntityStatusArchived CombinedEntityStatus = "archived" +) + +func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { + switch s { + case "active": + return CombinedEntityStatusActive, nil + case "archived": + return CombinedEntityStatusArchived, nil + } + var t CombinedEntityStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { + return &c +} + +var ( + describableFieldName = big.NewInt(1 << 0) + describableFieldSummary = big.NewInt(1 << 1) +) + +type Describable struct { + // Display name from Describable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (d *Describable) GetName() *string { + if d == nil { + return nil + } + return d.Name +} + +func (d *Describable) GetSummary() *string { + if d == nil { + return nil + } + return d.Summary +} + +func (d *Describable) GetExtraProperties() map[string]interface{} { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *Describable) require(field *big.Int) { + if d.explicitFields == nil { + d.explicitFields = big.NewInt(0) + } + d.explicitFields.Or(d.explicitFields, field) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *Describable) SetName(name *string) { + d.Name = name + d.require(describableFieldName) +} + +// SetSummary sets the Summary field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *Describable) SetSummary(summary *string) { + d.Summary = summary + d.require(describableFieldSummary) +} + +func (d *Describable) UnmarshalJSON(data []byte) error { + type unmarshaler Describable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Describable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *Describable) MarshalJSON() ([]byte, error) { + type embed Describable + var marshaler = struct { + embed + }{ + embed: embed(*d), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (d *Describable) String() string { + if d == nil { + return "" + } + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +var ( + detailedOrgFieldMetadata = big.NewInt(1 << 0) +) + +type DetailedOrg struct { + Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { + if d == nil { + return nil + } + return d.Metadata +} + +func (d *DetailedOrg) GetExtraProperties() map[string]interface{} { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrg) require(field *big.Int) { + if d.explicitFields == nil { + d.explicitFields = big.NewInt(0) + } + d.explicitFields.Or(d.explicitFields, field) +} + +// SetMetadata sets the Metadata field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *DetailedOrg) SetMetadata(metadata *DetailedOrgMetadata) { + d.Metadata = metadata + d.require(detailedOrgFieldMetadata) +} + +func (d *DetailedOrg) UnmarshalJSON(data []byte) error { + type unmarshaler DetailedOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrg) MarshalJSON() ([]byte, error) { + type embed DetailedOrg + var marshaler = struct { + embed + }{ + embed: embed(*d), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (d *DetailedOrg) String() string { + if d == nil { + return "" + } + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +var ( + detailedOrgMetadataFieldRegion = big.NewInt(1 << 0) + detailedOrgMetadataFieldDomain = big.NewInt(1 << 1) +) + +type DetailedOrgMetadata struct { + // Deployment region from DetailedOrg. + Region string `json:"region" url:"region"` + // Custom domain name. + Domain *string `json:"domain,omitempty" url:"domain,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (d *DetailedOrgMetadata) GetRegion() string { + if d == nil { + return "" + } + return d.Region +} + +func (d *DetailedOrgMetadata) GetDomain() *string { + if d == nil { + return nil + } + return d.Domain +} + +func (d *DetailedOrgMetadata) GetExtraProperties() map[string]interface{} { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrgMetadata) require(field *big.Int) { + if d.explicitFields == nil { + d.explicitFields = big.NewInt(0) + } + d.explicitFields.Or(d.explicitFields, field) +} + +// SetRegion sets the Region field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *DetailedOrgMetadata) SetRegion(region string) { + d.Region = region + d.require(detailedOrgMetadataFieldRegion) +} + +// SetDomain sets the Domain field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *DetailedOrgMetadata) SetDomain(domain *string) { + d.Domain = domain + d.require(detailedOrgMetadataFieldDomain) +} + +func (d *DetailedOrgMetadata) UnmarshalJSON(data []byte) error { + type unmarshaler DetailedOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrgMetadata) MarshalJSON() ([]byte, error) { + type embed DetailedOrgMetadata + var marshaler = struct { + embed + }{ + embed: embed(*d), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (d *DetailedOrgMetadata) String() string { + if d == nil { + return "" + } + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +var ( + identifiableFieldID = big.NewInt(1 << 0) + identifiableFieldName = big.NewInt(1 << 1) +) + +type Identifiable struct { + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Identifiable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (i *Identifiable) GetID() string { + if i == nil { + return "" + } + return i.ID +} + +func (i *Identifiable) GetName() *string { + if i == nil { + return nil + } + return i.Name +} + +func (i *Identifiable) GetExtraProperties() map[string]interface{} { + if i == nil { + return nil + } + return i.extraProperties +} + +func (i *Identifiable) require(field *big.Int) { + if i.explicitFields == nil { + i.explicitFields = big.NewInt(0) + } + i.explicitFields.Or(i.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (i *Identifiable) SetID(id string) { + i.ID = id + i.require(identifiableFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (i *Identifiable) SetName(name *string) { + i.Name = name + i.require(identifiableFieldName) +} + +func (i *Identifiable) UnmarshalJSON(data []byte) error { + type unmarshaler Identifiable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *i = Identifiable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *i) + if err != nil { + return err + } + i.extraProperties = extraProperties + i.rawJSON = json.RawMessage(data) + return nil +} + +func (i *Identifiable) MarshalJSON() ([]byte, error) { + type embed Identifiable + var marshaler = struct { + embed + }{ + embed: embed(*i), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, i.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (i *Identifiable) String() string { + if i == nil { + return "" + } + if len(i.rawJSON) > 0 { + if value, err := internal.StringifyJSON(i.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +var ( + organizationFieldID = big.NewInt(1 << 0) + organizationFieldMetadata = big.NewInt(1 << 1) + organizationFieldName = big.NewInt(1 << 2) +) + +type Organization struct { + ID string `json:"id" url:"id"` + Metadata *OrganizationMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + Name string `json:"name" url:"name"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (o *Organization) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *Organization) GetMetadata() *OrganizationMetadata { + if o == nil { + return nil + } + return o.Metadata +} + +func (o *Organization) GetName() string { + if o == nil { + return "" + } + return o.Name +} + +func (o *Organization) GetExtraProperties() map[string]interface{} { + if o == nil { + return nil + } + return o.extraProperties +} + +func (o *Organization) require(field *big.Int) { + if o.explicitFields == nil { + o.explicitFields = big.NewInt(0) + } + o.explicitFields.Or(o.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *Organization) SetID(id string) { + o.ID = id + o.require(organizationFieldID) +} + +// SetMetadata sets the Metadata field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *Organization) SetMetadata(metadata *OrganizationMetadata) { + o.Metadata = metadata + o.require(organizationFieldMetadata) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *Organization) SetName(name string) { + o.Name = name + o.require(organizationFieldName) +} + +func (o *Organization) UnmarshalJSON(data []byte) error { + type unmarshaler Organization + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *o = Organization(value) + extraProperties, err := internal.ExtractExtraProperties(data, *o) + if err != nil { + return err + } + o.extraProperties = extraProperties + o.rawJSON = json.RawMessage(data) + return nil +} + +func (o *Organization) MarshalJSON() ([]byte, error) { + type embed Organization + var marshaler = struct { + embed + }{ + embed: embed(*o), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, o.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (o *Organization) String() string { + if o == nil { + return "" + } + if len(o.rawJSON) > 0 { + if value, err := internal.StringifyJSON(o.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +var ( + organizationMetadataFieldRegion = big.NewInt(1 << 0) + organizationMetadataFieldDomain = big.NewInt(1 << 1) +) + +type OrganizationMetadata struct { + // Deployment region from DetailedOrg. + Region string `json:"region" url:"region"` + // Custom domain name. + Domain *string `json:"domain,omitempty" url:"domain,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (o *OrganizationMetadata) GetRegion() string { + if o == nil { + return "" + } + return o.Region +} + +func (o *OrganizationMetadata) GetDomain() *string { + if o == nil { + return nil + } + return o.Domain +} + +func (o *OrganizationMetadata) GetExtraProperties() map[string]interface{} { + if o == nil { + return nil + } + return o.extraProperties +} + +func (o *OrganizationMetadata) require(field *big.Int) { + if o.explicitFields == nil { + o.explicitFields = big.NewInt(0) + } + o.explicitFields.Or(o.explicitFields, field) +} + +// SetRegion sets the Region field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *OrganizationMetadata) SetRegion(region string) { + o.Region = region + o.require(organizationMetadataFieldRegion) +} + +// SetDomain sets the Domain field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *OrganizationMetadata) SetDomain(domain *string) { + o.Domain = domain + o.require(organizationMetadataFieldDomain) +} + +func (o *OrganizationMetadata) UnmarshalJSON(data []byte) error { + type unmarshaler OrganizationMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *o = OrganizationMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *o) + if err != nil { + return err + } + o.extraProperties = extraProperties + o.rawJSON = json.RawMessage(data) + return nil +} + +func (o *OrganizationMetadata) MarshalJSON() ([]byte, error) { + type embed OrganizationMetadata + var marshaler = struct { + embed + }{ + embed: embed(*o), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, o.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (o *OrganizationMetadata) String() string { + if o == nil { + return "" + } + if len(o.rawJSON) > 0 { + if value, err := internal.StringifyJSON(o.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +var ( + paginatedResultFieldPaging = big.NewInt(1 << 0) + paginatedResultFieldResults = big.NewInt(1 << 1) +) + +type PaginatedResult struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []any `json:"results" url:"results"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (p *PaginatedResult) GetPaging() *PagingCursors { + if p == nil { + return nil + } + return p.Paging +} + +func (p *PaginatedResult) GetResults() []any { + if p == nil { + return nil + } + return p.Results +} + +func (p *PaginatedResult) GetExtraProperties() map[string]interface{} { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PaginatedResult) require(field *big.Int) { + if p.explicitFields == nil { + p.explicitFields = big.NewInt(0) + } + p.explicitFields.Or(p.explicitFields, field) +} + +// SetPaging sets the Paging field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PaginatedResult) SetPaging(paging *PagingCursors) { + p.Paging = paging + p.require(paginatedResultFieldPaging) +} + +// SetResults sets the Results field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PaginatedResult) SetResults(results []any) { + p.Results = results + p.require(paginatedResultFieldResults) +} + +func (p *PaginatedResult) UnmarshalJSON(data []byte) error { + type unmarshaler PaginatedResult + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PaginatedResult(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PaginatedResult) MarshalJSON() ([]byte, error) { + type embed PaginatedResult + var marshaler = struct { + embed + }{ + embed: embed(*p), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (p *PaginatedResult) String() string { + if p == nil { + return "" + } + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +var ( + pagingCursorsFieldNext = big.NewInt(1 << 0) + pagingCursorsFieldPrevious = big.NewInt(1 << 1) +) + +type PagingCursors struct { + // Cursor for the next page of results. + Next string `json:"next" url:"next"` + // Cursor for the previous page of results. + Previous *string `json:"previous,omitempty" url:"previous,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (p *PagingCursors) GetNext() string { + if p == nil { + return "" + } + return p.Next +} + +func (p *PagingCursors) GetPrevious() *string { + if p == nil { + return nil + } + return p.Previous +} + +func (p *PagingCursors) GetExtraProperties() map[string]interface{} { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PagingCursors) require(field *big.Int) { + if p.explicitFields == nil { + p.explicitFields = big.NewInt(0) + } + p.explicitFields.Or(p.explicitFields, field) +} + +// SetNext sets the Next field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PagingCursors) SetNext(next string) { + p.Next = next + p.require(pagingCursorsFieldNext) +} + +// SetPrevious sets the Previous field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PagingCursors) SetPrevious(previous *string) { + p.Previous = previous + p.require(pagingCursorsFieldPrevious) +} + +func (p *PagingCursors) UnmarshalJSON(data []byte) error { + type unmarshaler PagingCursors + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PagingCursors(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PagingCursors) MarshalJSON() ([]byte, error) { + type embed PagingCursors + var marshaler = struct { + embed + }{ + embed: embed(*p), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (p *PagingCursors) String() string { + if p == nil { + return "" + } + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +// Execution environment for a rule. +type RuleExecutionContext string + +const ( + RuleExecutionContextProd RuleExecutionContext = "prod" + RuleExecutionContextStaging RuleExecutionContext = "staging" + RuleExecutionContextDev RuleExecutionContext = "dev" +) + +func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { + switch s { + case "prod": + return RuleExecutionContextProd, nil + case "staging": + return RuleExecutionContextStaging, nil + case "dev": + return RuleExecutionContextDev, nil + } + var t RuleExecutionContext + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleExecutionContext) Ptr() *RuleExecutionContext { + return &r +} + +var ( + ruleResponseFieldCreatedBy = big.NewInt(1 << 0) + ruleResponseFieldCreatedDateTime = big.NewInt(1 << 1) + ruleResponseFieldModifiedBy = big.NewInt(1 << 2) + ruleResponseFieldModifiedDateTime = big.NewInt(1 << 3) + ruleResponseFieldID = big.NewInt(1 << 4) + ruleResponseFieldName = big.NewInt(1 << 5) + ruleResponseFieldStatus = big.NewInt(1 << 6) + ruleResponseFieldExecutionContext = big.NewInt(1 << 7) +) + +type RuleResponse struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Status RuleResponseStatus `json:"status" url:"status"` + ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (r *RuleResponse) GetCreatedBy() *string { + if r == nil { + return nil + } + return r.CreatedBy +} + +func (r *RuleResponse) GetCreatedDateTime() *time.Time { + if r == nil { + return nil + } + return r.CreatedDateTime +} + +func (r *RuleResponse) GetModifiedBy() *string { + if r == nil { + return nil + } + return r.ModifiedBy +} + +func (r *RuleResponse) GetModifiedDateTime() *time.Time { + if r == nil { + return nil + } + return r.ModifiedDateTime +} + +func (r *RuleResponse) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleResponse) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleResponse) GetStatus() RuleResponseStatus { + if r == nil { + return "" + } + return r.Status +} + +func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { + if r == nil { + return nil + } + return r.ExecutionContext +} + +func (r *RuleResponse) GetExtraProperties() map[string]interface{} { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleResponse) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetCreatedBy sets the CreatedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetCreatedBy(createdBy *string) { + r.CreatedBy = createdBy + r.require(ruleResponseFieldCreatedBy) +} + +// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetCreatedDateTime(createdDateTime *time.Time) { + r.CreatedDateTime = createdDateTime + r.require(ruleResponseFieldCreatedDateTime) +} + +// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetModifiedBy(modifiedBy *string) { + r.ModifiedBy = modifiedBy + r.require(ruleResponseFieldModifiedBy) +} + +// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetModifiedDateTime(modifiedDateTime *time.Time) { + r.ModifiedDateTime = modifiedDateTime + r.require(ruleResponseFieldModifiedDateTime) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetID(id string) { + r.ID = id + r.require(ruleResponseFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetName(name string) { + r.Name = name + r.require(ruleResponseFieldName) +} + +// SetStatus sets the Status field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetStatus(status RuleResponseStatus) { + r.Status = status + r.require(ruleResponseFieldStatus) +} + +// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetExecutionContext(executionContext *RuleExecutionContext) { + r.ExecutionContext = executionContext + r.require(ruleResponseFieldExecutionContext) +} + +func (r *RuleResponse) UnmarshalJSON(data []byte) error { + type embed RuleResponse + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*r), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *r = RuleResponse(unmarshaler.embed) + r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleResponse) MarshalJSON() ([]byte, error) { + type embed RuleResponse + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*r), + CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (r *RuleResponse) String() string { + if r == nil { + return "" + } + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type RuleResponseStatus string + +const ( + RuleResponseStatusActive RuleResponseStatus = "active" + RuleResponseStatusInactive RuleResponseStatus = "inactive" + RuleResponseStatusDraft RuleResponseStatus = "draft" +) + +func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { + switch s { + case "active": + return RuleResponseStatusActive, nil + case "inactive": + return RuleResponseStatusInactive, nil + case "draft": + return RuleResponseStatusDraft, nil + } + var t RuleResponseStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleResponseStatus) Ptr() *RuleResponseStatus { + return &r +} + +var ( + ruleTypeFieldID = big.NewInt(1 << 0) + ruleTypeFieldName = big.NewInt(1 << 1) + ruleTypeFieldDescription = big.NewInt(1 << 2) +) + +type RuleType struct { + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Description *string `json:"description,omitempty" url:"description,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (r *RuleType) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleType) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleType) GetDescription() *string { + if r == nil { + return nil + } + return r.Description +} + +func (r *RuleType) GetExtraProperties() map[string]interface{} { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleType) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleType) SetID(id string) { + r.ID = id + r.require(ruleTypeFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleType) SetName(name string) { + r.Name = name + r.require(ruleTypeFieldName) +} + +// SetDescription sets the Description field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleType) SetDescription(description *string) { + r.Description = description + r.require(ruleTypeFieldDescription) +} + +func (r *RuleType) UnmarshalJSON(data []byte) error { + type unmarshaler RuleType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleType(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleType) MarshalJSON() ([]byte, error) { + type embed RuleType + var marshaler = struct { + embed + }{ + embed: embed(*r), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (r *RuleType) String() string { + if r == nil { + return "" + } + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +var ( + ruleTypeSearchResponseFieldPaging = big.NewInt(1 << 0) + ruleTypeSearchResponseFieldResults = big.NewInt(1 << 1) +) + +type RuleTypeSearchResponse struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { + if r == nil { + return nil + } + return r.Paging +} + +func (r *RuleTypeSearchResponse) GetResults() []*RuleType { + if r == nil { + return nil + } + return r.Results +} + +func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]interface{} { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleTypeSearchResponse) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetPaging sets the Paging field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleTypeSearchResponse) SetPaging(paging *PagingCursors) { + r.Paging = paging + r.require(ruleTypeSearchResponseFieldPaging) +} + +// SetResults sets the Results field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleTypeSearchResponse) SetResults(results []*RuleType) { + r.Results = results + r.require(ruleTypeSearchResponseFieldResults) +} + +func (r *RuleTypeSearchResponse) UnmarshalJSON(data []byte) error { + type unmarshaler RuleTypeSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleTypeSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleTypeSearchResponse) MarshalJSON() ([]byte, error) { + type embed RuleTypeSearchResponse + var marshaler = struct { + embed + }{ + embed: embed(*r), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (r *RuleTypeSearchResponse) String() string { + if r == nil { + return "" + } + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +var ( + userFieldID = big.NewInt(1 << 0) + userFieldEmail = big.NewInt(1 << 1) +) + +type User struct { + ID string `json:"id" url:"id"` + Email string `json:"email" url:"email"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (u *User) GetID() string { + if u == nil { + return "" + } + return u.ID +} + +func (u *User) GetEmail() string { + if u == nil { + return "" + } + return u.Email +} + +func (u *User) GetExtraProperties() map[string]interface{} { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *User) require(field *big.Int) { + if u.explicitFields == nil { + u.explicitFields = big.NewInt(0) + } + u.explicitFields.Or(u.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *User) SetID(id string) { + u.ID = id + u.require(userFieldID) +} + +// SetEmail sets the Email field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *User) SetEmail(email string) { + u.Email = email + u.require(userFieldEmail) +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *User) MarshalJSON() ([]byte, error) { + type embed User + var marshaler = struct { + embed + }{ + embed: embed(*u), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (u *User) String() string { + if u == nil { + return "" + } + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} + +var ( + userSearchResponseFieldPaging = big.NewInt(1 << 0) + userSearchResponseFieldResults = big.NewInt(1 << 1) +) + +type UserSearchResponse struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []*User `json:"results,omitempty" url:"results,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (u *UserSearchResponse) GetPaging() *PagingCursors { + if u == nil { + return nil + } + return u.Paging +} + +func (u *UserSearchResponse) GetResults() []*User { + if u == nil { + return nil + } + return u.Results +} + +func (u *UserSearchResponse) GetExtraProperties() map[string]interface{} { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *UserSearchResponse) require(field *big.Int) { + if u.explicitFields == nil { + u.explicitFields = big.NewInt(0) + } + u.explicitFields.Or(u.explicitFields, field) +} + +// SetPaging sets the Paging field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *UserSearchResponse) SetPaging(paging *PagingCursors) { + u.Paging = paging + u.require(userSearchResponseFieldPaging) +} + +// SetResults sets the Results field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *UserSearchResponse) SetResults(results []*User) { + u.Results = results + u.require(userSearchResponseFieldResults) +} + +func (u *UserSearchResponse) UnmarshalJSON(data []byte) error { + type unmarshaler UserSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UserSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *UserSearchResponse) MarshalJSON() ([]byte, error) { + type embed UserSearchResponse + var marshaler = struct { + embed + }{ + embed: embed(*u), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (u *UserSearchResponse) String() string { + if u == nil { + return "" + } + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/allof-inline/types_test.go b/seed/go-sdk/allof-inline/types_test.go new file mode 100644 index 000000000000..7f39409652b8 --- /dev/null +++ b/seed/go-sdk/allof-inline/types_test.go @@ -0,0 +1,4688 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + json "encoding/json" + assert "github.com/stretchr/testify/assert" + require "github.com/stretchr/testify/require" + testing "testing" + time "time" +) + +func TestSettersRuleCreateRequest(t *testing.T) { + t.Run("SetName", func(t *testing.T) { + obj := &RuleCreateRequest{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetExecutionContext", func(t *testing.T) { + obj := &RuleCreateRequest{} + var fernTestValueExecutionContext RuleExecutionContext + obj.SetExecutionContext(fernTestValueExecutionContext) + assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestSettersMarkExplicitRuleCreateRequest(t *testing.T) { + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleCreateRequest{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleCreateRequest{} + var fernTestValueExecutionContext RuleExecutionContext + + // Act + obj.SetExecutionContext(fernTestValueExecutionContext) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersSearchRuleTypesRequest(t *testing.T) { + t.Run("SetQuery", func(t *testing.T) { + obj := &SearchRuleTypesRequest{} + var fernTestValueQuery *string + obj.SetQuery(fernTestValueQuery) + assert.Equal(t, fernTestValueQuery, obj.Query) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestSettersMarkExplicitSearchRuleTypesRequest(t *testing.T) { + t.Run("SetQuery_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &SearchRuleTypesRequest{} + var fernTestValueQuery *string + + // Act + obj.SetQuery(fernTestValueQuery) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersAuditInfo(t *testing.T) { + t.Run("SetCreatedBy", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueCreatedBy *string + obj.SetCreatedBy(fernTestValueCreatedBy) + assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetCreatedDateTime", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueCreatedDateTime *time.Time + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedBy", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueModifiedBy *string + obj.SetModifiedBy(fernTestValueModifiedBy) + assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedDateTime", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueModifiedDateTime *time.Time + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersAuditInfo(t *testing.T) { + t.Run("GetCreatedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *string + obj.CreatedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") + }) + + t.Run("GetCreatedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.CreatedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedBy() // Should return zero value + }) + + t.Run("GetCreatedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *time.Time + obj.CreatedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") + }) + + t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.CreatedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedDateTime() // Should return zero value + }) + + t.Run("GetModifiedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *string + obj.ModifiedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") + }) + + t.Run("GetModifiedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.ModifiedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedBy() // Should return zero value + }) + + t.Run("GetModifiedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *time.Time + obj.ModifiedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") + }) + + t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.ModifiedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedDateTime() // Should return zero value + }) + +} + +func TestSettersMarkExplicitAuditInfo(t *testing.T) { + t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueCreatedBy *string + + // Act + obj.SetCreatedBy(fernTestValueCreatedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueCreatedDateTime *time.Time + + // Act + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueModifiedBy *string + + // Act + obj.SetModifiedBy(fernTestValueModifiedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueModifiedDateTime *time.Time + + // Act + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersBaseOrg(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &BaseOrg{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetMetadata", func(t *testing.T) { + obj := &BaseOrg{} + var fernTestValueMetadata *BaseOrgMetadata + obj.SetMetadata(fernTestValueMetadata) + assert.Equal(t, fernTestValueMetadata, obj.Metadata) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersBaseOrg(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetMetadata", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var expected *BaseOrgMetadata + obj.Metadata = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") + }) + + t.Run("GetMetadata_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + obj.Metadata = nil + + // Act & Assert + assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") + }) + + t.Run("GetMetadata_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetMetadata() // Should return zero value + }) + +} + +func TestSettersMarkExplicitBaseOrg(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var fernTestValueMetadata *BaseOrgMetadata + + // Act + obj.SetMetadata(fernTestValueMetadata) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersBaseOrgMetadata(t *testing.T) { + t.Run("SetRegion", func(t *testing.T) { + obj := &BaseOrgMetadata{} + var fernTestValueRegion string + obj.SetRegion(fernTestValueRegion) + assert.Equal(t, fernTestValueRegion, obj.Region) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetTier", func(t *testing.T) { + obj := &BaseOrgMetadata{} + var fernTestValueTier *string + obj.SetTier(fernTestValueTier) + assert.Equal(t, fernTestValueTier, obj.Tier) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersBaseOrgMetadata(t *testing.T) { + t.Run("GetRegion", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var expected string + obj.Region = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") + }) + + t.Run("GetRegion_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetRegion() // Should return zero value + }) + + t.Run("GetTier", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var expected *string + obj.Tier = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetTier(), "getter should return the property value") + }) + + t.Run("GetTier_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + obj.Tier = nil + + // Act & Assert + assert.Nil(t, obj.GetTier(), "getter should return nil when property is nil") + }) + + t.Run("GetTier_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetTier() // Should return zero value + }) + +} + +func TestSettersMarkExplicitBaseOrgMetadata(t *testing.T) { + t.Run("SetRegion_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var fernTestValueRegion string + + // Act + obj.SetRegion(fernTestValueRegion) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetTier_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var fernTestValueTier *string + + // Act + obj.SetTier(fernTestValueTier) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersCombinedEntity(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueName *string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetSummary", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueSummary *string + obj.SetSummary(fernTestValueSummary) + assert.Equal(t, fernTestValueSummary, obj.Summary) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetStatus", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueStatus CombinedEntityStatus + obj.SetStatus(fernTestValueStatus) + assert.Equal(t, fernTestValueStatus, obj.Status) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersCombinedEntity(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected *string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + obj.Name = nil + + // Act & Assert + assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetSummary", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected *string + obj.Summary = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") + }) + + t.Run("GetSummary_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + obj.Summary = nil + + // Act & Assert + assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") + }) + + t.Run("GetSummary_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetSummary() // Should return zero value + }) + + t.Run("GetStatus", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected CombinedEntityStatus + obj.Status = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") + }) + + t.Run("GetStatus_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetStatus() // Should return zero value + }) + +} + +func TestSettersMarkExplicitCombinedEntity(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueName *string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetSummary_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueSummary *string + + // Act + obj.SetSummary(fernTestValueSummary) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetStatus_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueStatus CombinedEntityStatus + + // Act + obj.SetStatus(fernTestValueStatus) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersDescribable(t *testing.T) { + t.Run("SetName", func(t *testing.T) { + obj := &Describable{} + var fernTestValueName *string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetSummary", func(t *testing.T) { + obj := &Describable{} + var fernTestValueSummary *string + obj.SetSummary(fernTestValueSummary) + assert.Equal(t, fernTestValueSummary, obj.Summary) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersDescribable(t *testing.T) { + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var expected *string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + obj.Name = nil + + // Act & Assert + assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetSummary", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var expected *string + obj.Summary = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") + }) + + t.Run("GetSummary_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + obj.Summary = nil + + // Act & Assert + assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") + }) + + t.Run("GetSummary_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetSummary() // Should return zero value + }) + +} + +func TestSettersMarkExplicitDescribable(t *testing.T) { + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var fernTestValueName *string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetSummary_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var fernTestValueSummary *string + + // Act + obj.SetSummary(fernTestValueSummary) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersDetailedOrg(t *testing.T) { + t.Run("SetMetadata", func(t *testing.T) { + obj := &DetailedOrg{} + var fernTestValueMetadata *DetailedOrgMetadata + obj.SetMetadata(fernTestValueMetadata) + assert.Equal(t, fernTestValueMetadata, obj.Metadata) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersDetailedOrg(t *testing.T) { + t.Run("GetMetadata", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + var expected *DetailedOrgMetadata + obj.Metadata = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") + }) + + t.Run("GetMetadata_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + obj.Metadata = nil + + // Act & Assert + assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") + }) + + t.Run("GetMetadata_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrg + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetMetadata() // Should return zero value + }) + +} + +func TestSettersMarkExplicitDetailedOrg(t *testing.T) { + t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + var fernTestValueMetadata *DetailedOrgMetadata + + // Act + obj.SetMetadata(fernTestValueMetadata) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersDetailedOrgMetadata(t *testing.T) { + t.Run("SetRegion", func(t *testing.T) { + obj := &DetailedOrgMetadata{} + var fernTestValueRegion string + obj.SetRegion(fernTestValueRegion) + assert.Equal(t, fernTestValueRegion, obj.Region) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetDomain", func(t *testing.T) { + obj := &DetailedOrgMetadata{} + var fernTestValueDomain *string + obj.SetDomain(fernTestValueDomain) + assert.Equal(t, fernTestValueDomain, obj.Domain) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersDetailedOrgMetadata(t *testing.T) { + t.Run("GetRegion", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var expected string + obj.Region = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") + }) + + t.Run("GetRegion_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetRegion() // Should return zero value + }) + + t.Run("GetDomain", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var expected *string + obj.Domain = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetDomain(), "getter should return the property value") + }) + + t.Run("GetDomain_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + obj.Domain = nil + + // Act & Assert + assert.Nil(t, obj.GetDomain(), "getter should return nil when property is nil") + }) + + t.Run("GetDomain_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetDomain() // Should return zero value + }) + +} + +func TestSettersMarkExplicitDetailedOrgMetadata(t *testing.T) { + t.Run("SetRegion_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var fernTestValueRegion string + + // Act + obj.SetRegion(fernTestValueRegion) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetDomain_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var fernTestValueDomain *string + + // Act + obj.SetDomain(fernTestValueDomain) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersIdentifiable(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &Identifiable{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &Identifiable{} + var fernTestValueName *string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersIdentifiable(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var expected *string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + obj.Name = nil + + // Act & Assert + assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + +} + +func TestSettersMarkExplicitIdentifiable(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var fernTestValueName *string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersOrganization(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &Organization{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetMetadata", func(t *testing.T) { + obj := &Organization{} + var fernTestValueMetadata *OrganizationMetadata + obj.SetMetadata(fernTestValueMetadata) + assert.Equal(t, fernTestValueMetadata, obj.Metadata) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &Organization{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersOrganization(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetMetadata", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var expected *OrganizationMetadata + obj.Metadata = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") + }) + + t.Run("GetMetadata_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + obj.Metadata = nil + + // Act & Assert + assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") + }) + + t.Run("GetMetadata_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetMetadata() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var expected string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + +} + +func TestSettersMarkExplicitOrganization(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var fernTestValueMetadata *OrganizationMetadata + + // Act + obj.SetMetadata(fernTestValueMetadata) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersOrganizationMetadata(t *testing.T) { + t.Run("SetRegion", func(t *testing.T) { + obj := &OrganizationMetadata{} + var fernTestValueRegion string + obj.SetRegion(fernTestValueRegion) + assert.Equal(t, fernTestValueRegion, obj.Region) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetDomain", func(t *testing.T) { + obj := &OrganizationMetadata{} + var fernTestValueDomain *string + obj.SetDomain(fernTestValueDomain) + assert.Equal(t, fernTestValueDomain, obj.Domain) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersOrganizationMetadata(t *testing.T) { + t.Run("GetRegion", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &OrganizationMetadata{} + var expected string + obj.Region = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") + }) + + t.Run("GetRegion_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *OrganizationMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetRegion() // Should return zero value + }) + + t.Run("GetDomain", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &OrganizationMetadata{} + var expected *string + obj.Domain = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetDomain(), "getter should return the property value") + }) + + t.Run("GetDomain_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &OrganizationMetadata{} + obj.Domain = nil + + // Act & Assert + assert.Nil(t, obj.GetDomain(), "getter should return nil when property is nil") + }) + + t.Run("GetDomain_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *OrganizationMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetDomain() // Should return zero value + }) + +} + +func TestSettersMarkExplicitOrganizationMetadata(t *testing.T) { + t.Run("SetRegion_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &OrganizationMetadata{} + var fernTestValueRegion string + + // Act + obj.SetRegion(fernTestValueRegion) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetDomain_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &OrganizationMetadata{} + var fernTestValueDomain *string + + // Act + obj.SetDomain(fernTestValueDomain) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersPaginatedResult(t *testing.T) { + t.Run("SetPaging", func(t *testing.T) { + obj := &PaginatedResult{} + var fernTestValuePaging *PagingCursors + obj.SetPaging(fernTestValuePaging) + assert.Equal(t, fernTestValuePaging, obj.Paging) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetResults", func(t *testing.T) { + obj := &PaginatedResult{} + var fernTestValueResults []any + obj.SetResults(fernTestValueResults) + assert.Equal(t, fernTestValueResults, obj.Results) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersPaginatedResult(t *testing.T) { + t.Run("GetPaging", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var expected *PagingCursors + obj.Paging = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") + }) + + t.Run("GetPaging_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + obj.Paging = nil + + // Act & Assert + assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") + }) + + t.Run("GetPaging_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPaging() // Should return zero value + }) + + t.Run("GetResults", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var expected []any + obj.Results = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") + }) + + t.Run("GetResults_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + obj.Results = nil + + // Act & Assert + assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") + }) + + t.Run("GetResults_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetResults() // Should return zero value + }) + +} + +func TestSettersMarkExplicitPaginatedResult(t *testing.T) { + t.Run("SetPaging_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var fernTestValuePaging *PagingCursors + + // Act + obj.SetPaging(fernTestValuePaging) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetResults_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var fernTestValueResults []any + + // Act + obj.SetResults(fernTestValueResults) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersPagingCursors(t *testing.T) { + t.Run("SetNext", func(t *testing.T) { + obj := &PagingCursors{} + var fernTestValueNext string + obj.SetNext(fernTestValueNext) + assert.Equal(t, fernTestValueNext, obj.Next) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetPrevious", func(t *testing.T) { + obj := &PagingCursors{} + var fernTestValuePrevious *string + obj.SetPrevious(fernTestValuePrevious) + assert.Equal(t, fernTestValuePrevious, obj.Previous) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersPagingCursors(t *testing.T) { + t.Run("GetNext", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var expected string + obj.Next = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetNext(), "getter should return the property value") + }) + + t.Run("GetNext_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetNext() // Should return zero value + }) + + t.Run("GetPrevious", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var expected *string + obj.Previous = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPrevious(), "getter should return the property value") + }) + + t.Run("GetPrevious_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + obj.Previous = nil + + // Act & Assert + assert.Nil(t, obj.GetPrevious(), "getter should return nil when property is nil") + }) + + t.Run("GetPrevious_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPrevious() // Should return zero value + }) + +} + +func TestSettersMarkExplicitPagingCursors(t *testing.T) { + t.Run("SetNext_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var fernTestValueNext string + + // Act + obj.SetNext(fernTestValueNext) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetPrevious_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var fernTestValuePrevious *string + + // Act + obj.SetPrevious(fernTestValuePrevious) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersRuleResponse(t *testing.T) { + t.Run("SetCreatedBy", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueCreatedBy *string + obj.SetCreatedBy(fernTestValueCreatedBy) + assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetCreatedDateTime", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueCreatedDateTime *time.Time + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedBy", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueModifiedBy *string + obj.SetModifiedBy(fernTestValueModifiedBy) + assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedDateTime", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueModifiedDateTime *time.Time + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetID", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetStatus", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueStatus RuleResponseStatus + obj.SetStatus(fernTestValueStatus) + assert.Equal(t, fernTestValueStatus, obj.Status) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetExecutionContext", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueExecutionContext *RuleExecutionContext + obj.SetExecutionContext(fernTestValueExecutionContext) + assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersRuleResponse(t *testing.T) { + t.Run("GetCreatedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *string + obj.CreatedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") + }) + + t.Run("GetCreatedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.CreatedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedBy() // Should return zero value + }) + + t.Run("GetCreatedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *time.Time + obj.CreatedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") + }) + + t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.CreatedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedDateTime() // Should return zero value + }) + + t.Run("GetModifiedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *string + obj.ModifiedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") + }) + + t.Run("GetModifiedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.ModifiedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedBy() // Should return zero value + }) + + t.Run("GetModifiedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *time.Time + obj.ModifiedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") + }) + + t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.ModifiedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedDateTime() // Should return zero value + }) + + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetStatus", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected RuleResponseStatus + obj.Status = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") + }) + + t.Run("GetStatus_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetStatus() // Should return zero value + }) + + t.Run("GetExecutionContext", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *RuleExecutionContext + obj.ExecutionContext = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetExecutionContext(), "getter should return the property value") + }) + + t.Run("GetExecutionContext_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.ExecutionContext = nil + + // Act & Assert + assert.Nil(t, obj.GetExecutionContext(), "getter should return nil when property is nil") + }) + + t.Run("GetExecutionContext_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetExecutionContext() // Should return zero value + }) + +} + +func TestSettersMarkExplicitRuleResponse(t *testing.T) { + t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueCreatedBy *string + + // Act + obj.SetCreatedBy(fernTestValueCreatedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueCreatedDateTime *time.Time + + // Act + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueModifiedBy *string + + // Act + obj.SetModifiedBy(fernTestValueModifiedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueModifiedDateTime *time.Time + + // Act + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetStatus_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueStatus RuleResponseStatus + + // Act + obj.SetStatus(fernTestValueStatus) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueExecutionContext *RuleExecutionContext + + // Act + obj.SetExecutionContext(fernTestValueExecutionContext) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersRuleType(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &RuleType{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &RuleType{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetDescription", func(t *testing.T) { + obj := &RuleType{} + var fernTestValueDescription *string + obj.SetDescription(fernTestValueDescription) + assert.Equal(t, fernTestValueDescription, obj.Description) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersRuleType(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var expected string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetDescription", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var expected *string + obj.Description = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetDescription(), "getter should return the property value") + }) + + t.Run("GetDescription_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + obj.Description = nil + + // Act & Assert + assert.Nil(t, obj.GetDescription(), "getter should return nil when property is nil") + }) + + t.Run("GetDescription_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetDescription() // Should return zero value + }) + +} + +func TestSettersMarkExplicitRuleType(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetDescription_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var fernTestValueDescription *string + + // Act + obj.SetDescription(fernTestValueDescription) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersRuleTypeSearchResponse(t *testing.T) { + t.Run("SetPaging", func(t *testing.T) { + obj := &RuleTypeSearchResponse{} + var fernTestValuePaging *PagingCursors + obj.SetPaging(fernTestValuePaging) + assert.Equal(t, fernTestValuePaging, obj.Paging) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetResults", func(t *testing.T) { + obj := &RuleTypeSearchResponse{} + var fernTestValueResults []*RuleType + obj.SetResults(fernTestValueResults) + assert.Equal(t, fernTestValueResults, obj.Results) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersRuleTypeSearchResponse(t *testing.T) { + t.Run("GetPaging", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var expected *PagingCursors + obj.Paging = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") + }) + + t.Run("GetPaging_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + obj.Paging = nil + + // Act & Assert + assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") + }) + + t.Run("GetPaging_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPaging() // Should return zero value + }) + + t.Run("GetResults", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var expected []*RuleType + obj.Results = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") + }) + + t.Run("GetResults_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + obj.Results = nil + + // Act & Assert + assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") + }) + + t.Run("GetResults_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetResults() // Should return zero value + }) + +} + +func TestSettersMarkExplicitRuleTypeSearchResponse(t *testing.T) { + t.Run("SetPaging_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var fernTestValuePaging *PagingCursors + + // Act + obj.SetPaging(fernTestValuePaging) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetResults_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var fernTestValueResults []*RuleType + + // Act + obj.SetResults(fernTestValueResults) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersUser(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &User{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetEmail", func(t *testing.T) { + obj := &User{} + var fernTestValueEmail string + obj.SetEmail(fernTestValueEmail) + assert.Equal(t, fernTestValueEmail, obj.Email) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersUser(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetEmail", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var expected string + obj.Email = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetEmail(), "getter should return the property value") + }) + + t.Run("GetEmail_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetEmail() // Should return zero value + }) + +} + +func TestSettersMarkExplicitUser(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetEmail_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var fernTestValueEmail string + + // Act + obj.SetEmail(fernTestValueEmail) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersUserSearchResponse(t *testing.T) { + t.Run("SetPaging", func(t *testing.T) { + obj := &UserSearchResponse{} + var fernTestValuePaging *PagingCursors + obj.SetPaging(fernTestValuePaging) + assert.Equal(t, fernTestValuePaging, obj.Paging) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetResults", func(t *testing.T) { + obj := &UserSearchResponse{} + var fernTestValueResults []*User + obj.SetResults(fernTestValueResults) + assert.Equal(t, fernTestValueResults, obj.Results) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersUserSearchResponse(t *testing.T) { + t.Run("GetPaging", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var expected *PagingCursors + obj.Paging = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") + }) + + t.Run("GetPaging_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + obj.Paging = nil + + // Act & Assert + assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") + }) + + t.Run("GetPaging_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPaging() // Should return zero value + }) + + t.Run("GetResults", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var expected []*User + obj.Results = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") + }) + + t.Run("GetResults_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + obj.Results = nil + + // Act & Assert + assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") + }) + + t.Run("GetResults_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetResults() // Should return zero value + }) + +} + +func TestSettersMarkExplicitUserSearchResponse(t *testing.T) { + t.Run("SetPaging_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var fernTestValuePaging *PagingCursors + + // Act + obj.SetPaging(fernTestValuePaging) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetResults_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var fernTestValueResults []*User + + // Act + obj.SetResults(fernTestValueResults) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestJSONMarshalingAuditInfo(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled AuditInfo + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj AuditInfo + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj AuditInfo + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingBaseOrg(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled BaseOrg + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj BaseOrg + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj BaseOrg + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingBaseOrgMetadata(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled BaseOrgMetadata + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj BaseOrgMetadata + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj BaseOrgMetadata + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingCombinedEntity(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled CombinedEntity + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj CombinedEntity + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj CombinedEntity + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingDescribable(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled Describable + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj Describable + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj Describable + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingDetailedOrg(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled DetailedOrg + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj DetailedOrg + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj DetailedOrg + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingDetailedOrgMetadata(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled DetailedOrgMetadata + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj DetailedOrgMetadata + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj DetailedOrgMetadata + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingIdentifiable(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled Identifiable + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj Identifiable + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj Identifiable + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingOrganization(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled Organization + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj Organization + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj Organization + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingOrganizationMetadata(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &OrganizationMetadata{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled OrganizationMetadata + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj OrganizationMetadata + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj OrganizationMetadata + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingPaginatedResult(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled PaginatedResult + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj PaginatedResult + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj PaginatedResult + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingPagingCursors(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled PagingCursors + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj PagingCursors + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj PagingCursors + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingRuleResponse(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled RuleResponse + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj RuleResponse + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj RuleResponse + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingRuleType(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled RuleType + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj RuleType + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj RuleType + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingRuleTypeSearchResponse(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled RuleTypeSearchResponse + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj RuleTypeSearchResponse + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj RuleTypeSearchResponse + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingUser(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled User + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj User + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj User + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingUserSearchResponse(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled UserSearchResponse + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj UserSearchResponse + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj UserSearchResponse + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestStringAuditInfo(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &AuditInfo{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringBaseOrg(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &BaseOrg{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringBaseOrgMetadata(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &BaseOrgMetadata{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringCombinedEntity(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &CombinedEntity{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringDescribable(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &Describable{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringDetailedOrg(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrg{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrg + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringDetailedOrgMetadata(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrgMetadata{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringIdentifiable(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &Identifiable{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringOrganization(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &Organization{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringOrganizationMetadata(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &OrganizationMetadata{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *OrganizationMetadata + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringPaginatedResult(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &PaginatedResult{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringPagingCursors(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &PagingCursors{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringRuleResponse(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &RuleResponse{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringRuleType(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &RuleType{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringRuleTypeSearchResponse(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &RuleTypeSearchResponse{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringUser(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &User{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringUserSearchResponse(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &UserSearchResponse{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestEnumCombinedEntityStatus(t *testing.T) { + t.Run("NewFromString_active", func(t *testing.T) { + t.Parallel() + val, err := NewCombinedEntityStatusFromString("active") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, CombinedEntityStatus("active"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_archived", func(t *testing.T) { + t.Parallel() + val, err := NewCombinedEntityStatusFromString("archived") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, CombinedEntityStatus("archived"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_Invalid", func(t *testing.T) { + _, err := NewCombinedEntityStatusFromString("invalid_value_that_does_not_exist") + assert.Error(t, err) + }) + + t.Run("Ptr", func(t *testing.T) { + val, err := NewCombinedEntityStatusFromString("active") + assert.NoError(t, err) + ptr := val.Ptr() + assert.NotNil(t, ptr) + assert.Equal(t, val, *ptr) + }) +} + +func TestEnumRuleExecutionContext(t *testing.T) { + t.Run("NewFromString_prod", func(t *testing.T) { + t.Parallel() + val, err := NewRuleExecutionContextFromString("prod") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleExecutionContext("prod"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_staging", func(t *testing.T) { + t.Parallel() + val, err := NewRuleExecutionContextFromString("staging") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleExecutionContext("staging"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_dev", func(t *testing.T) { + t.Parallel() + val, err := NewRuleExecutionContextFromString("dev") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleExecutionContext("dev"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_Invalid", func(t *testing.T) { + _, err := NewRuleExecutionContextFromString("invalid_value_that_does_not_exist") + assert.Error(t, err) + }) + + t.Run("Ptr", func(t *testing.T) { + val, err := NewRuleExecutionContextFromString("prod") + assert.NoError(t, err) + ptr := val.Ptr() + assert.NotNil(t, ptr) + assert.Equal(t, val, *ptr) + }) +} + +func TestEnumRuleResponseStatus(t *testing.T) { + t.Run("NewFromString_active", func(t *testing.T) { + t.Parallel() + val, err := NewRuleResponseStatusFromString("active") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleResponseStatus("active"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_inactive", func(t *testing.T) { + t.Parallel() + val, err := NewRuleResponseStatusFromString("inactive") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleResponseStatus("inactive"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_draft", func(t *testing.T) { + t.Parallel() + val, err := NewRuleResponseStatusFromString("draft") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleResponseStatus("draft"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_Invalid", func(t *testing.T) { + _, err := NewRuleResponseStatusFromString("invalid_value_that_does_not_exist") + assert.Error(t, err) + }) + + t.Run("Ptr", func(t *testing.T) { + val, err := NewRuleResponseStatusFromString("active") + assert.NoError(t, err) + ptr := val.Ptr() + assert.NotNil(t, ptr) + assert.Equal(t, val, *ptr) + }) +} + +func TestExtraPropertiesAuditInfo(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &AuditInfo{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesBaseOrg(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &BaseOrg{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesBaseOrgMetadata(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &BaseOrgMetadata{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesCombinedEntity(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &CombinedEntity{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesDescribable(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &Describable{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesDetailedOrg(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrg{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrg + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesDetailedOrgMetadata(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrgMetadata{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesIdentifiable(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &Identifiable{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesOrganization(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &Organization{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesOrganizationMetadata(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &OrganizationMetadata{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *OrganizationMetadata + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesPaginatedResult(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &PaginatedResult{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesPagingCursors(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &PagingCursors{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesRuleResponse(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &RuleResponse{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesRuleType(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &RuleType{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesRuleTypeSearchResponse(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &RuleTypeSearchResponse{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesUser(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &User{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesUserSearchResponse(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &UserSearchResponse{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} diff --git a/seed/go-sdk/allof/.fern/metadata.json b/seed/go-sdk/allof/.fern/metadata.json new file mode 100644 index 000000000000..1e0f7d8e54e4 --- /dev/null +++ b/seed/go-sdk/allof/.fern/metadata.json @@ -0,0 +1,10 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-go-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "enableWireTests": false + }, + "originGitCommit": "DUMMY", + "sdkVersion": "v0.0.1" +} \ No newline at end of file diff --git a/seed/go-sdk/allof/.github/workflows/ci.yml b/seed/go-sdk/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..1097e6a18acc --- /dev/null +++ b/seed/go-sdk/allof/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.10.1 + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Setup wiremock server + run: | + PROJECT_NAME="wiremock-$(basename $(dirname $(pwd)) | tr -d '.')" + echo "PROJECT_NAME=$PROJECT_NAME" >> $GITHUB_ENV + if [ -f wiremock/docker-compose.test.yml ]; then + docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down + docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml up -d + WIREMOCK_PORT=$(docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml port wiremock 8080 | cut -d: -f2) + echo "WIREMOCK_URL=http://localhost:$WIREMOCK_PORT" >> $GITHUB_ENV + fi + + - name: Test + run: go test ./... + + - name: Teardown wiremock server + if: always() + run: | + if [ -f wiremock/docker-compose.test.yml ]; then + docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down + fi diff --git a/seed/go-sdk/allof/README.md b/seed/go-sdk/allof/README.md new file mode 100644 index 000000000000..ee34317a346e --- /dev/null +++ b/seed/go-sdk/allof/README.md @@ -0,0 +1,195 @@ +# Seed Go Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FGo) + +The Seed Go library provides convenient access to the Seed APIs from Go. + +## Table of Contents + +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Request Options](#request-options) +- [Advanced](#advanced) + - [Response Headers](#response-headers) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Explicit Null](#explicit-null) +- [Contributing](#contributing) + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```go +package example + +import ( + context "context" + + fern "github.com/allof/fern" + client "github.com/allof/fern/client" +) + +func do() { + client := client.NewClient() + request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } + client.CreateRule( + context.TODO(), + request, + ) +} +``` + +## Environments + +You can choose between different environments by using the `option.WithBaseURL` option. You can configure any arbitrary base +URL, which is particularly useful in test environments. + +```go +client := client.NewClient( + option.WithBaseURL(api.Environments.Default), +) +``` + +## Errors + +Structured error types are returned from API calls that return non-success status codes. These errors are compatible +with the `errors.Is` and `errors.As` APIs, so you can access the error like so: + +```go +response, err := client.CreateRule(...) +if err != nil { + var apiError *core.APIError + if errors.As(err, apiError) { + // Do something with the API error ... + } + return err +} +``` + +## Request Options + +A variety of request options are included to adapt the behavior of the library, which includes configuring +authorization tokens, or providing your own instrumented `*http.Client`. + +These request options can either be +specified on the client so that they're applied on every request, or for an individual request, like so: + +> Providing your own `*http.Client` is recommended. Otherwise, the `http.DefaultClient` will be used, +> and your client will wait indefinitely for a response (unless the per-request, context-based timeout +> is used). + +```go +// Specify default options applied on every request. +client := client.NewClient( + option.WithToken(""), + option.WithHTTPClient( + &http.Client{ + Timeout: 5 * time.Second, + }, + ), +) + +// Specify options for an individual request. +response, err := client.CreateRule( + ..., + option.WithToken(""), +) +``` + +## Advanced + +### Response Headers + +You can access the raw HTTP response data by using the `WithRawResponse` field on the client. This is useful +when you need to examine the response headers received from the API call. (When the endpoint is paginated, +the raw HTTP response data will be included automatically in the Page response object.) + +```go +response, err := client.WithRawResponse.CreateRule(...) +if err != nil { + return err +} +fmt.Printf("Got response headers: %v", response.Header) +fmt.Printf("Got status code: %d", response.StatusCode) +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +If the `Retry-After` header is present in the response, the SDK will prioritize respecting its value exactly +over the default exponential backoff. + +Use the `option.WithMaxAttempts` option to configure this behavior for the entire client or an individual request: + +```go +client := client.NewClient( + option.WithMaxAttempts(1), +) + +response, err := client.CreateRule( + ..., + option.WithMaxAttempts(1), +) +``` + +### Timeouts + +Setting a timeout for each individual request is as simple as using the standard context library. Setting a one second timeout for an individual API call looks like the following: + +```go +ctx, cancel := context.WithTimeout(ctx, time.Second) +defer cancel() + +response, err := client.CreateRule(ctx, ...) +``` + +### Explicit Null + +If you want to send the explicit `null` JSON value through an optional parameter, you can use the setters\ +that come with every object. Calling a setter method for a property will flip a bit in the `explicitFields` +bitfield for that setter's object; during serialization, any property with a flipped bit will have its +omittable status stripped, so zero or `nil` values will be sent explicitly rather than omitted altogether: + +```go +type ExampleRequest struct { + // An optional string parameter. + Name *string `json:"name,omitempty" url:"-"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` +} + +request := &ExampleRequest{} +request.SetName(nil) + +response, err := client.CreateRule(ctx, request, ...) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/go-sdk/allof/client/client.go b/seed/go-sdk/allof/client/client.go new file mode 100644 index 000000000000..9d6bf76337ab --- /dev/null +++ b/seed/go-sdk/allof/client/client.go @@ -0,0 +1,109 @@ +// Code generated by Fern. DO NOT EDIT. + +package client + +import ( + context "context" + + fern "github.com/allof/fern" + core "github.com/allof/fern/core" + internal "github.com/allof/fern/internal" + option "github.com/allof/fern/option" +) + +type Client struct { + WithRawResponse *RawClient + + options *core.RequestOptions + baseURL string + caller *internal.Caller +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + WithRawResponse: NewRawClient(options), + options: options, + baseURL: options.BaseURL, + caller: internal.NewCaller( + &internal.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + } +} + +func (c *Client) SearchRuleTypes( + ctx context.Context, + request *fern.SearchRuleTypesRequest, + opts ...option.RequestOption, +) (*fern.RuleTypeSearchResponse, error) { + response, err := c.WithRawResponse.SearchRuleTypes( + ctx, + request, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) CreateRule( + ctx context.Context, + request *fern.RuleCreateRequest, + opts ...option.RequestOption, +) (*fern.RuleResponse, error) { + response, err := c.WithRawResponse.CreateRule( + ctx, + request, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) ListUsers( + ctx context.Context, + opts ...option.RequestOption, +) (*fern.UserSearchResponse, error) { + response, err := c.WithRawResponse.ListUsers( + ctx, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) GetEntity( + ctx context.Context, + opts ...option.RequestOption, +) (*fern.CombinedEntity, error) { + response, err := c.WithRawResponse.GetEntity( + ctx, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} + +func (c *Client) GetOrganization( + ctx context.Context, + opts ...option.RequestOption, +) (*fern.Organization, error) { + response, err := c.WithRawResponse.GetOrganization( + ctx, + opts..., + ) + if err != nil { + return nil, err + } + return response.Body, nil +} diff --git a/seed/go-sdk/allof/client/client_test.go b/seed/go-sdk/allof/client/client_test.go new file mode 100644 index 000000000000..191355fede9a --- /dev/null +++ b/seed/go-sdk/allof/client/client_test.go @@ -0,0 +1,45 @@ +// Code generated by Fern. DO NOT EDIT. + +package client + +import ( + option "github.com/allof/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.options.HTTPHeader.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/allof/client/raw_client.go b/seed/go-sdk/allof/client/raw_client.go new file mode 100644 index 000000000000..c340ae08f80d --- /dev/null +++ b/seed/go-sdk/allof/client/raw_client.go @@ -0,0 +1,238 @@ +// Code generated by Fern. DO NOT EDIT. + +package client + +import ( + context "context" + http "net/http" + + fern "github.com/allof/fern" + core "github.com/allof/fern/core" + internal "github.com/allof/fern/internal" + option "github.com/allof/fern/option" +) + +type RawClient struct { + baseURL string + caller *internal.Caller + options *core.RequestOptions +} + +func NewRawClient(options *core.RequestOptions) *RawClient { + return &RawClient{ + options: options, + baseURL: options.BaseURL, + caller: internal.NewCaller( + &internal.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + } +} + +func (r *RawClient) SearchRuleTypes( + ctx context.Context, + request *fern.SearchRuleTypesRequest, + opts ...option.RequestOption, +) (*core.Response[*fern.RuleTypeSearchResponse], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/rule-types" + queryParams, err := internal.QueryValues(request) + if err != nil { + return nil, err + } + if len(queryParams) > 0 { + endpointURL += "?" + queryParams.Encode() + } + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.RuleTypeSearchResponse + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.RuleTypeSearchResponse]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) CreateRule( + ctx context.Context, + request *fern.RuleCreateRequest, + opts ...option.RequestOption, +) (*core.Response[*fern.RuleResponse], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/rules" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + headers.Add("Content-Type", "application/json") + var response *fern.RuleResponse + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.RuleResponse]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) ListUsers( + ctx context.Context, + opts ...option.RequestOption, +) (*core.Response[*fern.UserSearchResponse], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/users" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.UserSearchResponse + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.UserSearchResponse]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) GetEntity( + ctx context.Context, + opts ...option.RequestOption, +) (*core.Response[*fern.CombinedEntity], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/entities" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.CombinedEntity + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.CombinedEntity]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} + +func (r *RawClient) GetOrganization( + ctx context.Context, + opts ...option.RequestOption, +) (*core.Response[*fern.Organization], error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + r.baseURL, + "https://api.example.com", + ) + endpointURL := baseURL + "/organizations" + headers := internal.MergeHeaders( + r.options.ToHeader(), + options.ToHeader(), + ) + var response *fern.Organization + raw, err := r.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ) + if err != nil { + return nil, err + } + return &core.Response[*fern.Organization]{ + StatusCode: raw.StatusCode, + Header: raw.Header, + Body: response, + }, nil +} diff --git a/seed/go-sdk/allof/core/api_error.go b/seed/go-sdk/allof/core/api_error.go new file mode 100644 index 000000000000..6168388541b4 --- /dev/null +++ b/seed/go-sdk/allof/core/api_error.go @@ -0,0 +1,47 @@ +package core + +import ( + "fmt" + "net/http" +) + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` + Header http.Header `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, header http.Header, err error) *APIError { + return &APIError{ + err: err, + Header: header, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} diff --git a/seed/go-sdk/allof/core/http.go b/seed/go-sdk/allof/core/http.go new file mode 100644 index 000000000000..92c435692940 --- /dev/null +++ b/seed/go-sdk/allof/core/http.go @@ -0,0 +1,15 @@ +package core + +import "net/http" + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// Response is an HTTP response from an HTTP client. +type Response[T any] struct { + StatusCode int + Header http.Header + Body T +} diff --git a/seed/go-sdk/allof/core/request_option.go b/seed/go-sdk/allof/core/request_option.go new file mode 100644 index 000000000000..d675ba244eb8 --- /dev/null +++ b/seed/go-sdk/allof/core/request_option.go @@ -0,0 +1,119 @@ +// Code generated by Fern. DO NOT EDIT. + +package core + +import ( + http "net/http" + url "net/url" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + BodyProperties map[string]interface{} + QueryParameters url.Values + MaxAttempts uint + MaxBufSize int +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + BodyProperties: make(map[string]interface{}), + QueryParameters: make(url.Values), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/allof/fern") + headers.Set("X-Fern-SDK-Version", "v0.0.1") + headers.Set("User-Agent", "github.com/allof/fern/0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// BodyPropertiesOption implements the RequestOption interface. +type BodyPropertiesOption struct { + BodyProperties map[string]interface{} +} + +func (b *BodyPropertiesOption) applyRequestOptions(opts *RequestOptions) { + opts.BodyProperties = b.BodyProperties +} + +// QueryParametersOption implements the RequestOption interface. +type QueryParametersOption struct { + QueryParameters url.Values +} + +func (q *QueryParametersOption) applyRequestOptions(opts *RequestOptions) { + opts.QueryParameters = q.QueryParameters +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// MaxBufSizeOption implements the RequestOption interface. +type MaxBufSizeOption struct { + MaxBufSize int +} + +func (m *MaxBufSizeOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxBufSize = m.MaxBufSize +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example0/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example0/snippet.go new file mode 100644 index 000000000000..72903cf9a04f --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example0/snippet.go @@ -0,0 +1,22 @@ +package example + +import ( + context "context" + + fern "github.com/allof/fern" + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.SearchRuleTypesRequest{} + client.SearchRuleTypes( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example1/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example1/snippet.go new file mode 100644 index 000000000000..c255973cd015 --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example1/snippet.go @@ -0,0 +1,26 @@ +package example + +import ( + context "context" + + fern "github.com/allof/fern" + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.SearchRuleTypesRequest{ + Query: fern.String( + "query", + ), + } + client.SearchRuleTypes( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example2/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example2/snippet.go new file mode 100644 index 000000000000..a9823743c596 --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example2/snippet.go @@ -0,0 +1,25 @@ +package example + +import ( + context "context" + + fern "github.com/allof/fern" + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } + client.CreateRule( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example3/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example3/snippet.go new file mode 100644 index 000000000000..a9823743c596 --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example3/snippet.go @@ -0,0 +1,25 @@ +package example + +import ( + context "context" + + fern "github.com/allof/fern" + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } + client.CreateRule( + context.TODO(), + request, + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example4/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example4/snippet.go new file mode 100644 index 000000000000..8d3e4ead5a0e --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example4/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.ListUsers( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example5/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example5/snippet.go new file mode 100644 index 000000000000..8d3e4ead5a0e --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example5/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.ListUsers( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example6/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example6/snippet.go new file mode 100644 index 000000000000..e7666f5aaf1d --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example6/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetEntity( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example7/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example7/snippet.go new file mode 100644 index 000000000000..e7666f5aaf1d --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example7/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetEntity( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example8/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example8/snippet.go new file mode 100644 index 000000000000..61385fbcb9bf --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example8/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetOrganization( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof/dynamic-snippets/example9/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example9/snippet.go new file mode 100644 index 000000000000..61385fbcb9bf --- /dev/null +++ b/seed/go-sdk/allof/dynamic-snippets/example9/snippet.go @@ -0,0 +1,19 @@ +package example + +import ( + context "context" + + client "github.com/allof/fern/client" + option "github.com/allof/fern/option" +) + +func do() { + client := client.NewClient( + option.WithBaseURL( + "https://api.fern.com", + ), + ) + client.GetOrganization( + context.TODO(), + ) +} diff --git a/seed/go-sdk/allof/environments.go b/seed/go-sdk/allof/environments.go new file mode 100644 index 000000000000..27941a670e97 --- /dev/null +++ b/seed/go-sdk/allof/environments.go @@ -0,0 +1,13 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +// Environments defines all of the API environments. +// These values can be used with the WithBaseURL +// RequestOption to override the client's default environment, +// if any. +var Environments = struct { + Default string +}{ + Default: "https://api.example.com", +} diff --git a/seed/go-sdk/allof/error_codes.go b/seed/go-sdk/allof/error_codes.go new file mode 100644 index 000000000000..d1094c40fab1 --- /dev/null +++ b/seed/go-sdk/allof/error_codes.go @@ -0,0 +1,9 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + internal "github.com/allof/fern/internal" +) + +var ErrorCodes internal.ErrorCodes = internal.ErrorCodes{} diff --git a/seed/go-sdk/allof/file_param.go b/seed/go-sdk/allof/file_param.go new file mode 100644 index 000000000000..737abb97c837 --- /dev/null +++ b/seed/go-sdk/allof/file_param.go @@ -0,0 +1,41 @@ +package api + +import ( + "io" +) + +// FileParam is a file type suitable for multipart/form-data uploads. +type FileParam struct { + io.Reader + filename string + contentType string +} + +// FileParamOption adapts the behavior of the FileParam. No options are +// implemented yet, but this interface allows for future extensibility. +type FileParamOption interface { + apply() +} + +// NewFileParam returns a *FileParam type suitable for multipart/form-data uploads. All file +// upload endpoints accept a simple io.Reader, which is usually created by opening a file +// via os.Open. +// +// However, some endpoints require additional metadata about the file such as a specific +// Content-Type or custom filename. FileParam makes it easier to create the correct type +// signature for these endpoints. +func NewFileParam( + reader io.Reader, + filename string, + contentType string, + opts ...FileParamOption, +) *FileParam { + return &FileParam{ + Reader: reader, + filename: filename, + contentType: contentType, + } +} + +func (f *FileParam) Name() string { return f.filename } +func (f *FileParam) ContentType() string { return f.contentType } diff --git a/seed/go-sdk/allof/go.mod b/seed/go-sdk/allof/go.mod new file mode 100644 index 000000000000..99668dd6c416 --- /dev/null +++ b/seed/go-sdk/allof/go.mod @@ -0,0 +1,16 @@ +module github.com/allof/fern + +go 1.21 + +toolchain go1.23.8 + +require github.com/google/uuid v1.6.0 + +require github.com/stretchr/testify v1.8.4 + +require gopkg.in/yaml.v3 v3.0.1 // indirect + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/seed/go-sdk/allof/go.sum b/seed/go-sdk/allof/go.sum new file mode 100644 index 000000000000..fcca6d128057 --- /dev/null +++ b/seed/go-sdk/allof/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/allof/internal/caller.go b/seed/go-sdk/allof/internal/caller.go new file mode 100644 index 000000000000..29d91e237cc6 --- /dev/null +++ b/seed/go-sdk/allof/internal/caller.go @@ -0,0 +1,311 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "reflect" + "strings" + + "github.com/allof/fern/core" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" + contentTypeFormURLEncoded = "application/x-www-form-urlencoded" +) + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client core.HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client core.HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient core.HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + BodyProperties map[string]interface{} + QueryParameters url.Values + Client core.HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// CallResponse is a parsed HTTP response from an API call. +type CallResponse struct { + StatusCode int + Header http.Header +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) (*CallResponse, error) { + url := buildURL(params.URL, params.QueryParameters) + req, err := newRequest( + ctx, + url, + params.Method, + params.Headers, + params.Request, + params.BodyProperties, + ) + if err != nil { + return nil, err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return nil, err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return nil, err + } + + // Close the response body after we're done. + defer func() { _ = resp.Body.Close() }() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return &CallResponse{ + StatusCode: resp.StatusCode, + Header: resp.Header, + }, nil + } + return nil, fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return nil, err + } + } + + return &CallResponse{ + StatusCode: resp.StatusCode, + Header: resp.Header, + }, nil +} + +// buildURL constructs the final URL by appending the given query parameters (if any). +func buildURL( + url string, + queryParameters url.Values, +) string { + if len(queryParameters) == 0 { + return url + } + if strings.ContainsRune(url, '?') { + url += "&" + } else { + url += "?" + } + url += queryParameters.Encode() + return url +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, + bodyProperties map[string]interface{}, +) (*http.Request, error) { + // Determine the content type from headers, defaulting to JSON. + reqContentType := contentType + if endpointHeaders != nil { + if ct := endpointHeaders.Get(contentTypeHeader); ct != "" { + reqContentType = ct + } + } + requestBody, err := newRequestBody(request, bodyProperties, reqContentType) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req.Header.Set(contentTypeHeader, reqContentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}, bodyProperties map[string]interface{}, reqContentType string) (io.Reader, error) { + if isNil(request) { + if len(bodyProperties) == 0 { + return nil, nil + } + if reqContentType == contentTypeFormURLEncoded { + return newFormURLEncodedBody(bodyProperties), nil + } + requestBytes, err := json.Marshal(bodyProperties) + if err != nil { + return nil, err + } + return bytes.NewReader(requestBytes), nil + } + if body, ok := request.(io.Reader); ok { + return body, nil + } + // Handle form URL encoded content type. + if reqContentType == contentTypeFormURLEncoded { + return newFormURLEncodedRequestBody(request, bodyProperties) + } + requestBytes, err := MarshalJSONWithExtraProperties(request, bodyProperties) + if err != nil { + return nil, err + } + return bytes.NewReader(requestBytes), nil +} + +// newFormURLEncodedBody returns a new io.Reader that represents a form URL encoded body +// from the given body properties map. +func newFormURLEncodedBody(bodyProperties map[string]interface{}) io.Reader { + values := url.Values{} + for key, val := range bodyProperties { + values.Set(key, fmt.Sprintf("%v", val)) + } + return strings.NewReader(values.Encode()) +} + +// newFormURLEncodedRequestBody returns a new io.Reader that represents a form URL encoded body +// from the given request struct and body properties. +func newFormURLEncodedRequestBody(request interface{}, bodyProperties map[string]interface{}) (io.Reader, error) { + values := url.Values{} + // Marshal the request to JSON first to respect any custom MarshalJSON methods, + // then unmarshal into a map to extract the field values. + jsonBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + var jsonMap map[string]interface{} + if err := json.Unmarshal(jsonBytes, &jsonMap); err != nil { + return nil, err + } + // Convert the JSON map to form URL encoded values. + for key, val := range jsonMap { + if val == nil { + continue + } + values.Set(key, fmt.Sprintf("%v", val)) + } + // Add any extra body properties. + for key, val := range bodyProperties { + values.Set(key, fmt.Sprintf("%v", val)) + } + return strings.NewReader(values.Encode()), nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Header, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return core.NewAPIError(response.StatusCode, response.Header, nil) + } + return core.NewAPIError(response.StatusCode, response.Header, errors.New(string(bytes))) +} + +// isNil is used to determine if the request value is equal to nil (i.e. an interface +// value that holds a nil concrete value is itself non-nil). +func isNil(value interface{}) bool { + if value == nil { + return true + } + v := reflect.ValueOf(value) + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: + return v.IsNil() + default: + return false + } +} diff --git a/seed/go-sdk/allof/internal/caller_test.go b/seed/go-sdk/allof/internal/caller_test.go new file mode 100644 index 000000000000..b3dcb522884a --- /dev/null +++ b/seed/go-sdk/allof/internal/caller_test.go @@ -0,0 +1,705 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "strings" + "testing" + + "github.com/allof/fern/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// InternalTestCase represents a single test case. +type InternalTestCase struct { + description string + + // Server-side assertions. + givePathSuffix string + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *InternalTestRequest + giveQueryParams url.Values + giveBodyProperties map[string]interface{} + + // Client-side assertions. + wantResponse *InternalTestResponse + wantError error +} + +// InternalTestRequest a simple request body. +type InternalTestRequest struct { + Id string `json:"id"` +} + +// InternalTestResponse a simple response body. +type InternalTestResponse struct { + Id string `json:"id"` + ExtraBodyProperties map[string]interface{} `json:"extraBodyProperties,omitempty"` + QueryParameters url.Values `json:"queryParameters,omitempty"` +} + +// InternalTestNotFoundError represents a 404. +type InternalTestNotFoundError struct { + *core.APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*InternalTestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + wantResponse: &InternalTestResponse{ + Id: "123", + }, + }, + { + description: "GET success with query", + givePathSuffix: "?limit=1", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + wantResponse: &InternalTestResponse{ + Id: "123", + QueryParameters: url.Values{ + "limit": []string{"1"}, + }, + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &InternalTestRequest{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &InternalTestNotFoundError{ + APIError: core.NewAPIError( + http.StatusNotFound, + http.Header{}, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST empty body", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: nil, + wantError: core.NewAPIError( + http.StatusBadRequest, + http.Header{}, + errors.New("invalid request"), + ), + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &InternalTestRequest{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: core.NewAPIError( + http.StatusInternalServerError, + http.Header{}, + errors.New("failed to process request"), + ), + }, + { + description: "POST extra properties", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: new(InternalTestRequest), + giveBodyProperties: map[string]interface{}{ + "key": "value", + }, + wantResponse: &InternalTestResponse{ + ExtraBodyProperties: map[string]interface{}{ + "key": "value", + }, + }, + }, + { + description: "GET extra query parameters", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveQueryParams: url.Values{ + "extra": []string{"true"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + wantResponse: &InternalTestResponse{ + Id: "123", + QueryParameters: url.Values{ + "extra": []string{"true"}, + }, + }, + }, + { + description: "GET merge extra query parameters", + givePathSuffix: "?limit=1", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &InternalTestRequest{ + Id: "123", + }, + giveQueryParams: url.Values{ + "extra": []string{"true"}, + }, + wantResponse: &InternalTestResponse{ + Id: "123", + QueryParameters: url.Values{ + "limit": []string{"1"}, + "extra": []string{"true"}, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL + test.givePathSuffix, + Method: test.giveMethod, + Headers: test.giveHeader, + BodyProperties: test.giveBodyProperties, + QueryParameters: test.giveQueryParams, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *InternalTestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + request := new(InternalTestRequest) + + bytes, err := io.ReadAll(r.Body) + if tc.giveRequest == nil { + require.Empty(t, bytes) + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("invalid request")) + require.NoError(t, err) + return + } + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &InternalTestNotFoundError{ + APIError: &core.APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + extraBodyProperties := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &extraBodyProperties)) + delete(extraBodyProperties, "id") + + response := &InternalTestResponse{ + Id: request.Id, + ExtraBodyProperties: extraBodyProperties, + QueryParameters: r.URL.Query(), + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +func TestIsNil(t *testing.T) { + t.Run("nil interface", func(t *testing.T) { + assert.True(t, isNil(nil)) + }) + + t.Run("nil pointer", func(t *testing.T) { + var ptr *string + assert.True(t, isNil(ptr)) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + s := "test" + assert.False(t, isNil(&s)) + }) + + t.Run("nil slice", func(t *testing.T) { + var slice []string + assert.True(t, isNil(slice)) + }) + + t.Run("non-nil slice", func(t *testing.T) { + slice := []string{} + assert.False(t, isNil(slice)) + }) + + t.Run("nil map", func(t *testing.T) { + var m map[string]string + assert.True(t, isNil(m)) + }) + + t.Run("non-nil map", func(t *testing.T) { + m := make(map[string]string) + assert.False(t, isNil(m)) + }) + + t.Run("string value", func(t *testing.T) { + assert.False(t, isNil("test")) + }) + + t.Run("empty string value", func(t *testing.T) { + assert.False(t, isNil("")) + }) + + t.Run("int value", func(t *testing.T) { + assert.False(t, isNil(42)) + }) + + t.Run("zero int value", func(t *testing.T) { + assert.False(t, isNil(0)) + }) + + t.Run("bool value", func(t *testing.T) { + assert.False(t, isNil(true)) + }) + + t.Run("false bool value", func(t *testing.T) { + assert.False(t, isNil(false)) + }) + + t.Run("struct value", func(t *testing.T) { + type testStruct struct { + Field string + } + assert.False(t, isNil(testStruct{Field: "test"})) + }) + + t.Run("empty struct value", func(t *testing.T) { + type testStruct struct { + Field string + } + assert.False(t, isNil(testStruct{})) + }) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, http.Header, io.Reader) error { + return func(statusCode int, header http.Header, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = core.NewAPIError(statusCode, header, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(InternalTestNotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} + +// FormURLEncodedTestRequest is a test struct for form URL encoding tests. +type FormURLEncodedTestRequest struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + GrantType string `json:"grant_type,omitempty"` + Scope *string `json:"scope,omitempty"` + NilPointer *string `json:"nil_pointer,omitempty"` +} + +func TestNewFormURLEncodedBody(t *testing.T) { + t.Run("simple key-value pairs", func(t *testing.T) { + bodyProperties := map[string]interface{}{ + "client_id": "test_client_id", + "client_secret": "test_client_secret", + "grant_type": "client_credentials", + } + reader := newFormURLEncodedBody(bodyProperties) + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Parse the body and verify values + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "client_credentials", values.Get("grant_type")) + + // Verify it's not JSON + bodyStr := string(body) + assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should not be JSON, got: %s", bodyStr) + }) + + t.Run("special characters requiring URL encoding", func(t *testing.T) { + bodyProperties := map[string]interface{}{ + "value_with_space": "hello world", + "value_with_ampersand": "a&b", + "value_with_equals": "a=b", + "value_with_plus": "a+b", + } + reader := newFormURLEncodedBody(bodyProperties) + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Parse the body and verify values are correctly decoded + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "hello world", values.Get("value_with_space")) + assert.Equal(t, "a&b", values.Get("value_with_ampersand")) + assert.Equal(t, "a=b", values.Get("value_with_equals")) + assert.Equal(t, "a+b", values.Get("value_with_plus")) + }) + + t.Run("empty map", func(t *testing.T) { + bodyProperties := map[string]interface{}{} + reader := newFormURLEncodedBody(bodyProperties) + body, err := io.ReadAll(reader) + require.NoError(t, err) + assert.Empty(t, string(body)) + }) +} + +func TestNewFormURLEncodedRequestBody(t *testing.T) { + t.Run("struct with json tags", func(t *testing.T) { + scope := "read write" + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + GrantType: "client_credentials", + Scope: &scope, + NilPointer: nil, + } + reader, err := newFormURLEncodedRequestBody(request, nil) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Parse the body and verify values + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "client_credentials", values.Get("grant_type")) + assert.Equal(t, "read write", values.Get("scope")) + // nil_pointer should not be present (nil pointer with omitempty) + assert.Empty(t, values.Get("nil_pointer")) + + // Verify it's not JSON + bodyStr := string(body) + assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should not be JSON, got: %s", bodyStr) + }) + + t.Run("struct with omitempty and zero values", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + GrantType: "", // empty string with omitempty should be omitted + Scope: nil, + NilPointer: nil, + } + reader, err := newFormURLEncodedRequestBody(request, nil) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + // grant_type should not be present (empty string with omitempty) + assert.Empty(t, values.Get("grant_type")) + assert.Empty(t, values.Get("scope")) + }) + + t.Run("struct with extra body properties", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + } + bodyProperties := map[string]interface{}{ + "extra_param": "extra_value", + } + reader, err := newFormURLEncodedRequestBody(request, bodyProperties) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "extra_value", values.Get("extra_param")) + }) + + t.Run("special characters in struct fields", func(t *testing.T) { + scope := "read&write=all+permissions" + request := &FormURLEncodedTestRequest{ + ClientID: "client with spaces", + ClientSecret: "secret&with=special+chars", + Scope: &scope, + } + reader, err := newFormURLEncodedRequestBody(request, nil) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "client with spaces", values.Get("client_id")) + assert.Equal(t, "secret&with=special+chars", values.Get("client_secret")) + assert.Equal(t, "read&write=all+permissions", values.Get("scope")) + }) +} + +func TestNewRequestBodyFormURLEncoded(t *testing.T) { + t.Run("selects form encoding when content-type is form-urlencoded", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + GrantType: "client_credentials", + } + reader, err := newRequestBody(request, nil, contentTypeFormURLEncoded) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Verify it's form-urlencoded, not JSON + bodyStr := string(body) + assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should not be JSON when Content-Type is form-urlencoded, got: %s", bodyStr) + + // Parse and verify values + values, err := url.ParseQuery(bodyStr) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + assert.Equal(t, "client_credentials", values.Get("grant_type")) + }) + + t.Run("selects JSON encoding when content-type is application/json", func(t *testing.T) { + request := &FormURLEncodedTestRequest{ + ClientID: "test_client_id", + ClientSecret: "test_client_secret", + } + reader, err := newRequestBody(request, nil, contentType) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + // Verify it's JSON + bodyStr := string(body) + assert.True(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), + "Body should be JSON when Content-Type is application/json, got: %s", bodyStr) + + // Parse and verify it's valid JSON + var parsed map[string]interface{} + err = json.Unmarshal(body, &parsed) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", parsed["client_id"]) + assert.Equal(t, "test_client_secret", parsed["client_secret"]) + }) + + t.Run("form encoding with body properties only (nil request)", func(t *testing.T) { + bodyProperties := map[string]interface{}{ + "client_id": "test_client_id", + "client_secret": "test_client_secret", + } + reader, err := newRequestBody(nil, bodyProperties, contentTypeFormURLEncoded) + require.NoError(t, err) + + body, err := io.ReadAll(reader) + require.NoError(t, err) + + values, err := url.ParseQuery(string(body)) + require.NoError(t, err) + + assert.Equal(t, "test_client_id", values.Get("client_id")) + assert.Equal(t, "test_client_secret", values.Get("client_secret")) + }) +} diff --git a/seed/go-sdk/allof/internal/error_decoder.go b/seed/go-sdk/allof/internal/error_decoder.go new file mode 100644 index 000000000000..10a0b8b9bc93 --- /dev/null +++ b/seed/go-sdk/allof/internal/error_decoder.go @@ -0,0 +1,64 @@ +package internal + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/allof/fern/core" +) + +// ErrorCodes maps HTTP status codes to error constructors. +type ErrorCodes map[int]func(*core.APIError) error + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *core.APIError). +type ErrorDecoder func(statusCode int, header http.Header, body io.Reader) error + +// NewErrorDecoder returns a new ErrorDecoder backed by the given error codes. +// errorCodesOverrides is optional and will be merged with the default error codes, +// with overrides taking precedence. +func NewErrorDecoder(errorCodes ErrorCodes, errorCodesOverrides ...ErrorCodes) ErrorDecoder { + // Merge default error codes with overrides + mergedErrorCodes := make(ErrorCodes) + + // Start with default error codes + for statusCode, errorFunc := range errorCodes { + mergedErrorCodes[statusCode] = errorFunc + } + + // Apply overrides if provided + if len(errorCodesOverrides) > 0 && errorCodesOverrides[0] != nil { + for statusCode, errorFunc := range errorCodesOverrides[0] { + mergedErrorCodes[statusCode] = errorFunc + } + } + + return func(statusCode int, header http.Header, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return fmt.Errorf("failed to read error from response body: %w", err) + } + apiError := core.NewAPIError( + statusCode, + header, + errors.New(string(raw)), + ) + newErrorFunc, ok := mergedErrorCodes[statusCode] + if !ok { + // This status code isn't recognized, so we return + // the API error as-is. + return apiError + } + customError := newErrorFunc(apiError) + if err := json.NewDecoder(bytes.NewReader(raw)).Decode(customError); err != nil { + // If we fail to decode the error, we return the + // API error as-is. + return apiError + } + return customError + } +} diff --git a/seed/go-sdk/allof/internal/error_decoder_test.go b/seed/go-sdk/allof/internal/error_decoder_test.go new file mode 100644 index 000000000000..09109239d743 --- /dev/null +++ b/seed/go-sdk/allof/internal/error_decoder_test.go @@ -0,0 +1,59 @@ +package internal + +import ( + "bytes" + "errors" + "net/http" + "testing" + + "github.com/allof/fern/core" + "github.com/stretchr/testify/assert" +) + +func TestErrorDecoder(t *testing.T) { + decoder := NewErrorDecoder( + ErrorCodes{ + http.StatusNotFound: func(apiError *core.APIError) error { + return &InternalTestNotFoundError{APIError: apiError} + }, + }) + + tests := []struct { + description string + giveStatusCode int + giveHeader http.Header + giveBody string + wantError error + }{ + { + description: "unrecognized status code", + giveStatusCode: http.StatusInternalServerError, + giveHeader: http.Header{}, + giveBody: "Internal Server Error", + wantError: core.NewAPIError(http.StatusInternalServerError, http.Header{}, errors.New("Internal Server Error")), + }, + { + description: "not found with valid JSON", + giveStatusCode: http.StatusNotFound, + giveHeader: http.Header{}, + giveBody: `{"message": "Resource not found"}`, + wantError: &InternalTestNotFoundError{ + APIError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New(`{"message": "Resource not found"}`)), + Message: "Resource not found", + }, + }, + { + description: "not found with invalid JSON", + giveStatusCode: http.StatusNotFound, + giveHeader: http.Header{}, + giveBody: `Resource not found`, + wantError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New("Resource not found")), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + assert.Equal(t, tt.wantError, decoder(tt.giveStatusCode, tt.giveHeader, bytes.NewReader([]byte(tt.giveBody)))) + }) + } +} diff --git a/seed/go-sdk/allof/internal/explicit_fields.go b/seed/go-sdk/allof/internal/explicit_fields.go new file mode 100644 index 000000000000..4bdf34fc2b7c --- /dev/null +++ b/seed/go-sdk/allof/internal/explicit_fields.go @@ -0,0 +1,116 @@ +package internal + +import ( + "math/big" + "reflect" + "strings" +) + +// HandleExplicitFields processes a struct to remove `omitempty` from +// fields that have been explicitly set (as indicated by their corresponding bit in explicitFields). +// Note that `marshaler` should be an embedded struct to avoid infinite recursion. +// Returns an interface{} that can be passed to json.Marshal. +func HandleExplicitFields(marshaler interface{}, explicitFields *big.Int) interface{} { + val := reflect.ValueOf(marshaler) + typ := reflect.TypeOf(marshaler) + + // Handle pointer types + if val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil + } + val = val.Elem() + typ = typ.Elem() + } + + // Only handle struct types + if val.Kind() != reflect.Struct { + return marshaler + } + + // Handle embedded struct pattern + var sourceVal reflect.Value + var sourceType reflect.Type + + // Check if this is an embedded struct pattern + if typ.NumField() == 1 && typ.Field(0).Anonymous { + // This is likely an embedded struct, get the embedded value + embeddedField := val.Field(0) + sourceVal = embeddedField + sourceType = embeddedField.Type() + } else { + // Regular struct + sourceVal = val + sourceType = typ + } + + // If no explicit fields set, use standard marshaling + if explicitFields == nil || explicitFields.Sign() == 0 { + return marshaler + } + + // Create a new struct type with modified tags + fields := make([]reflect.StructField, 0, sourceType.NumField()) + + for i := 0; i < sourceType.NumField(); i++ { + field := sourceType.Field(i) + + // Skip unexported fields and the explicitFields field itself + if !field.IsExported() || field.Name == "explicitFields" { + continue + } + + // Check if this field has been explicitly set + fieldBit := big.NewInt(1) + fieldBit.Lsh(fieldBit, uint(i)) + if big.NewInt(0).And(explicitFields, fieldBit).Sign() != 0 { + // Remove omitempty from the json tag + tag := field.Tag.Get("json") + if tag != "" && tag != "-" { + // Parse the json tag, remove omitempty from options + parts := strings.Split(tag, ",") + if len(parts) > 1 { + var newParts []string + newParts = append(newParts, parts[0]) // Keep the field name + for _, part := range parts[1:] { + if strings.TrimSpace(part) != "omitempty" { + newParts = append(newParts, part) + } + } + tag = strings.Join(newParts, ",") + } + + // Reconstruct the struct tag + newTag := `json:"` + tag + `"` + if urlTag := field.Tag.Get("url"); urlTag != "" { + newTag += ` url:"` + urlTag + `"` + } + + field.Tag = reflect.StructTag(newTag) + } + } + + fields = append(fields, field) + } + + // Create new struct type with modified tags + newType := reflect.StructOf(fields) + newVal := reflect.New(newType).Elem() + + // Copy field values from original struct to new struct + fieldIndex := 0 + for i := 0; i < sourceType.NumField(); i++ { + originalField := sourceType.Field(i) + + // Skip unexported fields and the explicitFields field itself + if !originalField.IsExported() || originalField.Name == "explicitFields" { + continue + } + + originalValue := sourceVal.Field(i) + newVal.Field(fieldIndex).Set(originalValue) + fieldIndex++ + } + + return newVal.Interface() +} diff --git a/seed/go-sdk/allof/internal/explicit_fields_test.go b/seed/go-sdk/allof/internal/explicit_fields_test.go new file mode 100644 index 000000000000..f44beec447d6 --- /dev/null +++ b/seed/go-sdk/allof/internal/explicit_fields_test.go @@ -0,0 +1,645 @@ +package internal + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testExplicitFieldsStruct struct { + Name *string `json:"name,omitempty"` + Code *string `json:"code,omitempty"` + Count *int `json:"count,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Tags []string `json:"tags,omitempty"` + unexported string `json:"-"` //nolint:unused + explicitFields *big.Int `json:"-"` +} + +var ( + testFieldName = big.NewInt(1 << 0) + testFieldCode = big.NewInt(1 << 1) + testFieldCount = big.NewInt(1 << 2) + testFieldEnabled = big.NewInt(1 << 3) + testFieldTags = big.NewInt(1 << 4) +) + +func (t *testExplicitFieldsStruct) require(field *big.Int) { + if t.explicitFields == nil { + t.explicitFields = big.NewInt(0) + } + t.explicitFields.Or(t.explicitFields, field) +} + +func (t *testExplicitFieldsStruct) SetName(name *string) { + t.Name = name + t.require(testFieldName) +} + +func (t *testExplicitFieldsStruct) SetCode(code *string) { + t.Code = code + t.require(testFieldCode) +} + +func (t *testExplicitFieldsStruct) SetCount(count *int) { + t.Count = count + t.require(testFieldCount) +} + +func (t *testExplicitFieldsStruct) SetEnabled(enabled *bool) { + t.Enabled = enabled + t.require(testFieldEnabled) +} + +func (t *testExplicitFieldsStruct) SetTags(tags []string) { + t.Tags = tags + t.require(testFieldTags) +} + +func (t *testExplicitFieldsStruct) MarshalJSON() ([]byte, error) { + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*t), + } + return json.Marshal(HandleExplicitFields(marshaler, t.explicitFields)) +} + +type testStructWithoutExplicitFields struct { + Name *string `json:"name,omitempty"` + Code *string `json:"code,omitempty"` +} + +func TestHandleExplicitFields(t *testing.T) { + tests := []struct { + desc string + giveInput interface{} + wantBytes []byte + wantError string + }{ + { + desc: "nil input", + giveInput: nil, + wantBytes: []byte(`null`), + }, + { + desc: "non-struct input", + giveInput: "string", + wantBytes: []byte(`"string"`), + }, + { + desc: "slice input", + giveInput: []string{"a", "b"}, + wantBytes: []byte(`["a","b"]`), + }, + { + desc: "map input", + giveInput: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "struct without explicitFields field", + giveInput: &testStructWithoutExplicitFields{ + Name: stringPtr("test"), + Code: nil, + }, + wantBytes: []byte(`{"name":"test"}`), + }, + { + desc: "struct with no explicit fields set", + giveInput: &testExplicitFieldsStruct{ + Name: stringPtr("test"), + Code: nil, + }, + wantBytes: []byte(`{"name":"test"}`), + }, + { + desc: "struct with explicit nil field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Name: stringPtr("test"), + } + s.SetCode(nil) + return s + }(), + wantBytes: []byte(`{"name":"test","code":null}`), + }, + { + desc: "struct with explicit non-nil field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{} + s.SetName(stringPtr("explicit")) + s.SetCode(stringPtr("also-explicit")) + return s + }(), + wantBytes: []byte(`{"name":"explicit","code":"also-explicit"}`), + }, + { + desc: "struct with mixed explicit and implicit fields", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Name: stringPtr("implicit"), + Count: intPtr(42), + } + s.SetCode(nil) // explicit nil + return s + }(), + wantBytes: []byte(`{"name":"implicit","code":null,"count":42}`), + }, + { + desc: "struct with multiple explicit nil fields", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Name: stringPtr("test"), + } + s.SetCode(nil) + s.SetCount(nil) + return s + }(), + wantBytes: []byte(`{"name":"test","code":null,"count":null}`), + }, + { + desc: "struct with slice field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{ + Tags: []string{"tag1", "tag2"}, + } + s.SetTags(nil) // explicit nil slice + return s + }(), + wantBytes: []byte(`{"tags":null}`), + }, + { + desc: "struct with boolean field", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{} + s.SetEnabled(boolPtr(false)) // explicit false + return s + }(), + wantBytes: []byte(`{"enabled":false}`), + }, + { + desc: "struct with all fields explicit", + giveInput: func() *testExplicitFieldsStruct { + s := &testExplicitFieldsStruct{} + s.SetName(stringPtr("test")) + s.SetCode(nil) + s.SetCount(intPtr(0)) + s.SetEnabled(boolPtr(false)) + s.SetTags([]string{}) + return s + }(), + wantBytes: []byte(`{"name":"test","code":null,"count":0,"enabled":false,"tags":[]}`), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + var explicitFields *big.Int + if s, ok := tt.giveInput.(*testExplicitFieldsStruct); ok { + explicitFields = s.explicitFields + } + bytes, err := json.Marshal(HandleExplicitFields(tt.giveInput, explicitFields)) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.JSONEq(t, string(tt.wantBytes), string(bytes)) + + // Verify it's valid JSON + var value interface{} + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestHandleExplicitFieldsCustomMarshaler(t *testing.T) { + t.Run("custom marshaler with explicit fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + s.SetName(nil) + s.SetCode(stringPtr("test-code")) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) + }) + + t.Run("custom marshaler with no explicit fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("implicit"), + Code: stringPtr("also-implicit"), + } + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) + }) +} + +func TestHandleExplicitFieldsPointerHandling(t *testing.T) { + t.Run("nil pointer", func(t *testing.T) { + var s *testExplicitFieldsStruct + bytes, err := json.Marshal(HandleExplicitFields(s, nil)) + require.NoError(t, err) + assert.Equal(t, []byte(`null`), bytes) + }) + + t.Run("pointer to struct", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + s.SetName(nil) + + bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) + require.NoError(t, err) + assert.JSONEq(t, `{"name":null}`, string(bytes)) + }) +} + +func TestHandleExplicitFieldsEmbeddedStruct(t *testing.T) { + t.Run("embedded struct with explicit fields", func(t *testing.T) { + // Create a struct similar to what MarshalJSON creates + s := &testExplicitFieldsStruct{} + s.SetName(nil) + s.SetCode(stringPtr("test-code")) + + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*s), + } + + bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) + require.NoError(t, err) + // Should include both explicit fields (name as null, code as "test-code") + assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) + }) + + t.Run("embedded struct with no explicit fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("implicit"), + Code: stringPtr("also-implicit"), + } + + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*s), + } + + bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) + require.NoError(t, err) + // Should only include non-nil fields (omitempty behavior) + assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) + }) + + t.Run("embedded struct with mixed fields", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Count: intPtr(42), // implicit field + } + s.SetName(nil) // explicit nil + s.SetCode(stringPtr("explicit")) // explicit value + + type embed testExplicitFieldsStruct + var marshaler = struct { + embed + }{ + embed: embed(*s), + } + + bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) + require.NoError(t, err) + // Should include explicit null, explicit value, and implicit value + assert.JSONEq(t, `{"name":null,"code":"explicit","count":42}`, string(bytes)) + }) +} + +func TestHandleExplicitFieldsTagHandling(t *testing.T) { + type testStructWithComplexTags struct { + Field1 *string `json:"field1,omitempty" url:"field1,omitempty"` + Field2 *string `json:"field2,omitempty,string" url:"field2"` + Field3 *string `json:"-"` + Field4 *string `json:"field4"` + explicitFields *big.Int `json:"-"` + } + + s := &testStructWithComplexTags{ + Field1: stringPtr("test1"), + Field4: stringPtr("test4"), + explicitFields: big.NewInt(1), // Only first field is explicit + } + + bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) + require.NoError(t, err) + + // Field1 should have omitempty removed, Field2 should keep omitempty, Field4 should be included + assert.JSONEq(t, `{"field1":"test1","field4":"test4"}`, string(bytes)) +} + +// Test types for nested struct explicit fields testing +type testNestedStruct struct { + NestedName *string `json:"nested_name,omitempty"` + NestedCode *string `json:"nested_code,omitempty"` + explicitFields *big.Int `json:"-"` +} + +type testParentStruct struct { + ParentName *string `json:"parent_name,omitempty"` + Nested *testNestedStruct `json:"nested,omitempty"` + explicitFields *big.Int `json:"-"` +} + +var ( + nestedFieldName = big.NewInt(1 << 0) + nestedFieldCode = big.NewInt(1 << 1) +) + +var ( + parentFieldName = big.NewInt(1 << 0) + parentFieldNested = big.NewInt(1 << 1) +) + +func (n *testNestedStruct) require(field *big.Int) { + if n.explicitFields == nil { + n.explicitFields = big.NewInt(0) + } + n.explicitFields.Or(n.explicitFields, field) +} + +func (n *testNestedStruct) SetNestedName(name *string) { + n.NestedName = name + n.require(nestedFieldName) +} + +func (n *testNestedStruct) SetNestedCode(code *string) { + n.NestedCode = code + n.require(nestedFieldCode) +} + +func (n *testNestedStruct) MarshalJSON() ([]byte, error) { + type embed testNestedStruct + var marshaler = struct { + embed + }{ + embed: embed(*n), + } + return json.Marshal(HandleExplicitFields(marshaler, n.explicitFields)) +} + +func (p *testParentStruct) require(field *big.Int) { + if p.explicitFields == nil { + p.explicitFields = big.NewInt(0) + } + p.explicitFields.Or(p.explicitFields, field) +} + +func (p *testParentStruct) SetParentName(name *string) { + p.ParentName = name + p.require(parentFieldName) +} + +func (p *testParentStruct) SetNested(nested *testNestedStruct) { + p.Nested = nested + p.require(parentFieldNested) +} + +func (p *testParentStruct) MarshalJSON() ([]byte, error) { + type embed testParentStruct + var marshaler = struct { + embed + }{ + embed: embed(*p), + } + return json.Marshal(HandleExplicitFields(marshaler, p.explicitFields)) +} + +func TestHandleExplicitFieldsNestedStruct(t *testing.T) { + tests := []struct { + desc string + setupFunc func() *testParentStruct + wantBytes []byte + }{ + { + desc: "nested struct with explicit nil in nested object", + setupFunc: func() *testParentStruct { + nested := &testNestedStruct{ + NestedName: stringPtr("implicit-nested"), + } + nested.SetNestedCode(nil) // explicit nil + + return &testParentStruct{ + ParentName: stringPtr("implicit-parent"), + Nested: nested, + } + }, + wantBytes: []byte(`{"parent_name":"implicit-parent","nested":{"nested_name":"implicit-nested","nested_code":null}}`), + }, + { + desc: "parent with explicit nil nested struct", + setupFunc: func() *testParentStruct { + parent := &testParentStruct{ + ParentName: stringPtr("implicit-parent"), + } + parent.SetNested(nil) // explicit nil nested struct + return parent + }, + wantBytes: []byte(`{"parent_name":"implicit-parent","nested":null}`), + }, + { + desc: "all explicit fields in nested structure", + setupFunc: func() *testParentStruct { + nested := &testNestedStruct{} + nested.SetNestedName(stringPtr("explicit-nested")) + nested.SetNestedCode(nil) // explicit nil + + parent := &testParentStruct{} + parent.SetParentName(nil) // explicit nil + parent.SetNested(nested) // explicit nested struct + + return parent + }, + wantBytes: []byte(`{"parent_name":null,"nested":{"nested_name":"explicit-nested","nested_code":null}}`), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + parent := tt.setupFunc() + bytes, err := parent.MarshalJSON() + require.NoError(t, err) + assert.JSONEq(t, string(tt.wantBytes), string(bytes)) + + // Verify it's valid JSON + var value interface{} + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +// Test for setter method documentation and behavior +func TestSetterMethodsDocumentation(t *testing.T) { + t.Run("setter prevents omitempty for nil values", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Use setter to explicitly set nil - this should prevent omitempty + s.SetName(nil) + s.SetCode(nil) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Both fields should be included as null, not omitted + assert.JSONEq(t, `{"name":null,"code":null}`, string(bytes)) + }) + + t.Run("setter prevents omitempty for empty slice", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Use setter to explicitly set empty slice + s.SetTags([]string{}) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Empty slice should be included as [], not omitted + assert.JSONEq(t, `{"tags":[]}`, string(bytes)) + }) + + t.Run("setter prevents omitempty for zero values", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Use setter to explicitly set zero values + s.SetCount(intPtr(0)) + s.SetEnabled(boolPtr(false)) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Zero values should be included, not omitted + assert.JSONEq(t, `{"count":0,"enabled":false}`, string(bytes)) + }) + + t.Run("direct assignment is omitted when nil", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: nil, // Direct assignment, not using setter + Code: nil, // Direct assignment, not using setter + } + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Fields not set via setter should be omitted when nil + assert.JSONEq(t, `{}`, string(bytes)) + }) + + t.Run("mix of setter and direct assignment", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("direct"), // Direct assignment + Count: intPtr(42), // Direct assignment + } + s.SetCode(nil) // Setter with nil + s.SetEnabled(boolPtr(false)) // Setter with zero value + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Direct assignments included if non-nil, setter fields always included + assert.JSONEq(t, `{"name":"direct","code":null,"count":42,"enabled":false}`, string(bytes)) + }) +} + +// Test for complex scenarios with multiple setters +func TestComplexSetterScenarios(t *testing.T) { + t.Run("multiple setter calls on same field", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + + // Call setter multiple times - last one should win + s.SetName(stringPtr("first")) + s.SetName(stringPtr("second")) + s.SetName(nil) // Final value is nil + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Should serialize the last set value (nil) + assert.JSONEq(t, `{"name":null}`, string(bytes)) + }) + + t.Run("setter after direct assignment", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("direct"), + } + + // Override with setter + s.SetName(nil) + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // Setter should mark field as explicit, so nil is serialized + assert.JSONEq(t, `{"name":null}`, string(bytes)) + }) + + t.Run("all fields set via setters", func(t *testing.T) { + s := &testExplicitFieldsStruct{} + s.SetName(nil) + s.SetCode(stringPtr("")) // Empty string + s.SetCount(intPtr(0)) // Zero + s.SetEnabled(boolPtr(false)) // False + s.SetTags(nil) // Nil slice + + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + // All fields should be present even with nil/zero values + assert.JSONEq(t, `{"name":null,"code":"","count":0,"enabled":false,"tags":null}`, string(bytes)) + }) +} + +// Test for backwards compatibility +func TestBackwardsCompatibility(t *testing.T) { + t.Run("struct without setters behaves normally", func(t *testing.T) { + s := &testStructWithoutExplicitFields{ + Name: stringPtr("test"), + Code: nil, // This should be omitted + } + + bytes, err := json.Marshal(s) + require.NoError(t, err) + + // Without setters, omitempty works normally + assert.JSONEq(t, `{"name":"test"}`, string(bytes)) + }) + + t.Run("struct with explicit fields works with standard json.Marshal", func(t *testing.T) { + s := &testExplicitFieldsStruct{ + Name: stringPtr("test"), + } + s.SetCode(nil) + + // Using the custom MarshalJSON + bytes, err := s.MarshalJSON() + require.NoError(t, err) + + assert.JSONEq(t, `{"name":"test","code":null}`, string(bytes)) + }) +} + +// Helper functions +func stringPtr(s string) *string { + return &s +} + +func intPtr(i int) *int { + return &i +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/seed/go-sdk/allof/internal/extra_properties.go b/seed/go-sdk/allof/internal/extra_properties.go new file mode 100644 index 000000000000..540c3fd89eeb --- /dev/null +++ b/seed/go-sdk/allof/internal/extra_properties.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-sdk/allof/internal/extra_properties_test.go b/seed/go-sdk/allof/internal/extra_properties_test.go new file mode 100644 index 000000000000..aa2510ee5121 --- /dev/null +++ b/seed/go-sdk/allof/internal/extra_properties_test.go @@ -0,0 +1,228 @@ +package internal + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-sdk/allof/internal/http.go b/seed/go-sdk/allof/internal/http.go new file mode 100644 index 000000000000..77863752bb58 --- /dev/null +++ b/seed/go-sdk/allof/internal/http.go @@ -0,0 +1,71 @@ +package internal + +import ( + "fmt" + "net/http" + "net/url" + "reflect" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// ResolveBaseURL resolves the base URL from the given arguments, +// preferring the first non-empty value. +func ResolveBaseURL(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. Pointer arguments are dereferenced before processing. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + // Dereference the argument if it's a pointer + value := dereferenceArg(arg) + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", value))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// dereferenceArg dereferences a pointer argument if necessary, returning the underlying value. +// If the argument is not a pointer or is nil, it returns the argument as-is. +func dereferenceArg(arg interface{}) interface{} { + if arg == nil { + return arg + } + + v := reflect.ValueOf(arg) + + // Keep dereferencing until we get to a non-pointer value or hit nil + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil + } + v = v.Elem() + } + + return v.Interface() +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} diff --git a/seed/go-sdk/allof/internal/query.go b/seed/go-sdk/allof/internal/query.go new file mode 100644 index 000000000000..9b567f7a5563 --- /dev/null +++ b/seed/go-sdk/allof/internal/query.go @@ -0,0 +1,358 @@ +package internal + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +// RFC3339Milli is a time format string for RFC 3339 with millisecond precision. +// Go's time.RFC3339 omits fractional seconds and time.RFC3339Nano trims trailing +// zeros, so neither produces the fixed ".000" millisecond suffix that many APIs expect. +const RFC3339Milli = "2006-01-02T15:04:05.000Z07:00" + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// prepareValue handles common validation and unwrapping logic for both functions +func prepareValue(v interface{}) (reflect.Value, url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return reflect.Value{}, values, nil + } + val = val.Elem() + } + + if v == nil { + return reflect.Value{}, values, nil + } + + if val.Kind() != reflect.Struct { + return reflect.Value{}, nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + if err != nil { + return reflect.Value{}, nil, err + } + + return val, values, nil +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + _, values, err := prepareValue(v) + return values, err +} + +// QueryValuesWithDefaults encodes url.Values from request objects +// and default values, merging the defaults into the request. +// It's expected that the values of defaults are wire names. +func QueryValuesWithDefaults(v interface{}, defaults map[string]interface{}) (url.Values, error) { + val, values, err := prepareValue(v) + if err != nil { + return values, err + } + if !val.IsValid() { + return values, nil + } + + // apply defaults to zero-value fields directly on the original struct + valType := val.Type() + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldType := valType.Field(i) + fieldName := fieldType.Name + + if fieldType.PkgPath != "" && !fieldType.Anonymous { + // Skip unexported fields. + continue + } + + // check if field is zero value and we have a default for it + if field.CanSet() && field.IsZero() { + tag := fieldType.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + wireName, _ := parseTag(tag) + if wireName == "" { + wireName = fieldName + } + if defaultVal, exists := defaults[wireName]; exists { + values.Set(wireName, valueString(reflect.ValueOf(defaultVal), tagOptions{}, reflect.StructField{})) + } + } + } + + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + value := sv.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), name); err != nil { + return err + } + } else { + values.Add(name, valueString(value, opts, sf)) + } + } + continue + } + + if sv.Kind() == reflect.Map { + if err := reflectMap(values, sv, name); err != nil { + return err + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// reflectMap handles map types specifically, generating query parameters in the format key[mapkey]=value +func reflectMap(values url.Values, val reflect.Value, scope string) error { + if val.IsNil() { + return nil + } + + iter := val.MapRange() + for iter.Next() { + k := iter.Key() + v := iter.Value() + + key := fmt.Sprint(k.Interface()) + paramName := scope + "[" + key + "]" + + for v.Kind() == reflect.Ptr { + if v.IsNil() { + break + } + v = v.Elem() + } + + for v.Kind() == reflect.Interface { + v = v.Elem() + } + + if v.Kind() == reflect.Map { + if err := reflectMap(values, v, paramName); err != nil { + return err + } + continue + } + + if v.Kind() == reflect.Struct { + if err := reflectValue(values, v, paramName); err != nil { + return err + } + continue + } + + if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { + if v.Len() == 0 { + continue + } + for i := 0; i < v.Len(); i++ { + value := v.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), paramName); err != nil { + return err + } + } else { + values.Add(paramName, valueString(value, tagOptions{}, reflect.StructField{})) + } + } + continue + } + + values.Add(paramName, valueString(v, tagOptions{}, reflect.StructField{})) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(RFC3339Milli) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsZero() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// isStructPointer returns true if the given reflect.Value is a pointer to a struct. +func isStructPointer(v reflect.Value) bool { + return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/allof/internal/query_test.go b/seed/go-sdk/allof/internal/query_test.go new file mode 100644 index 000000000000..5b463e297350 --- /dev/null +++ b/seed/go-sdk/allof/internal/query_test.go @@ -0,0 +1,395 @@ +package internal + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56.000Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("omitempty with non-pointer zero value", func(t *testing.T) { + type enum string + + type example struct { + Enum enum `json:"enum,omitempty" url:"enum,omitempty"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("object array", func(t *testing.T) { + type object struct { + Key string `json:"key" url:"key"` + Value string `json:"value" url:"value"` + } + type example struct { + Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` + } + + values, err := QueryValues( + &example{ + Objects: []*object{ + { + Key: "hello", + Value: "world", + }, + { + Key: "foo", + Value: "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) + }) + + t.Run("map", func(t *testing.T) { + type request struct { + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + } + values, err := QueryValues( + &request{ + Metadata: map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "metadata%5Bbaz%5D=qux&metadata%5Bfoo%5D=bar", values.Encode()) + }) + + t.Run("nested map", func(t *testing.T) { + type request struct { + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + } + values, err := QueryValues( + &request{ + Metadata: map[string]interface{}{ + "inner": map[string]interface{}{ + "foo": "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "metadata%5Binner%5D%5Bfoo%5D=bar", values.Encode()) + }) + + t.Run("nested map array", func(t *testing.T) { + type request struct { + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + } + values, err := QueryValues( + &request{ + Metadata: map[string]interface{}{ + "inner": []string{ + "one", + "two", + "three", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "metadata%5Binner%5D=one&metadata%5Binner%5D=two&metadata%5Binner%5D=three", values.Encode()) + }) +} + +func TestQueryValuesWithDefaults(t *testing.T) { + t.Run("apply defaults to zero values", func(t *testing.T) { + type example struct { + Name string `json:"name" url:"name"` + Age int `json:"age" url:"age"` + Enabled bool `json:"enabled" url:"enabled"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "enabled": true, + } + + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) + }) + + t.Run("preserve non-zero values over defaults", func(t *testing.T) { + type example struct { + Name string `json:"name" url:"name"` + Age int `json:"age" url:"age"` + Enabled bool `json:"enabled" url:"enabled"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "enabled": true, + } + + values, err := QueryValuesWithDefaults(&example{ + Name: "actual-name", + Age: 30, + // Enabled remains false (zero value), should get default + }, defaults) + require.NoError(t, err) + assert.Equal(t, "age=30&enabled=true&name=actual-name", values.Encode()) + }) + + t.Run("ignore defaults for fields not in struct", func(t *testing.T) { + type example struct { + Name string `json:"name" url:"name"` + Age int `json:"age" url:"age"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "nonexistent": "should-be-ignored", + } + + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "age=25&name=default-name", values.Encode()) + }) + + t.Run("type conversion for compatible defaults", func(t *testing.T) { + type example struct { + Count int64 `json:"count" url:"count"` + Rate float64 `json:"rate" url:"rate"` + Message string `json:"message" url:"message"` + } + + defaults := map[string]interface{}{ + "count": int(100), // int -> int64 conversion + "rate": float32(2.5), // float32 -> float64 conversion + "message": "hello", // string -> string (no conversion needed) + } + + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "count=100&message=hello&rate=2.5", values.Encode()) + }) + + t.Run("mixed with pointer fields and omitempty", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + Optional *string `json:"optional,omitempty" url:"optional,omitempty"` + Count int `json:"count,omitempty" url:"count,omitempty"` + } + + defaultOptional := "default-optional" + defaults := map[string]interface{}{ + "required": "default-required", + "optional": &defaultOptional, // pointer type + "count": 42, + } + + values, err := QueryValuesWithDefaults(&example{ + Required: "custom-required", // should override default + // Optional is nil, should get default + // Count is 0, should get default + }, defaults) + require.NoError(t, err) + assert.Equal(t, "count=42&optional=default-optional&required=custom-required", values.Encode()) + }) + + t.Run("override non-zero defaults with explicit zero values", func(t *testing.T) { + type example struct { + Name *string `json:"name" url:"name"` + Age *int `json:"age" url:"age"` + Enabled *bool `json:"enabled" url:"enabled"` + } + + defaults := map[string]interface{}{ + "name": "default-name", + "age": 25, + "enabled": true, + } + + // first, test that a properly empty request is overridden: + { + values, err := QueryValuesWithDefaults(&example{}, defaults) + require.NoError(t, err) + assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) + } + + // second, test that a request that contains zeros is not overridden: + var ( + name = "" + age = 0 + enabled = false + ) + values, err := QueryValuesWithDefaults(&example{ + Name: &name, // explicit empty string should override default + Age: &age, // explicit zero should override default + Enabled: &enabled, // explicit false should override default + }, defaults) + require.NoError(t, err) + assert.Equal(t, "age=0&enabled=false&name=", values.Encode()) + }) + + t.Run("nil input returns empty values", func(t *testing.T) { + defaults := map[string]any{ + "name": "default-name", + "age": 25, + } + + // Test with nil + values, err := QueryValuesWithDefaults(nil, defaults) + require.NoError(t, err) + assert.Empty(t, values) + + // Test with nil pointer + type example struct { + Name string `json:"name" url:"name"` + } + var nilPtr *example + values, err = QueryValuesWithDefaults(nilPtr, defaults) + require.NoError(t, err) + assert.Empty(t, values) + }) +} diff --git a/seed/go-sdk/allof/internal/retrier.go b/seed/go-sdk/allof/internal/retrier.go new file mode 100644 index 000000000000..02fd1fb7d3f1 --- /dev/null +++ b/seed/go-sdk/allof/internal/retrier.go @@ -0,0 +1,239 @@ +package internal + +import ( + "crypto/rand" + "math/big" + "net/http" + "strconv" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 1000 * time.Millisecond + maxRetryDelay = 60000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retryable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retryable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + // Reset the request body for retries since the body may have already been read. + if retryAttempt > 0 && request.GetBody != nil { + requestBody, err := request.GetBody() + if err != nil { + return nil, err + } + request.Body = requestBody + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer func() { _ = response.Body.Close() }() + + delay, err := r.retryDelay(response, retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time based on response headers, +// falling back to exponential backoff if no headers are present. +func (r *Retrier) retryDelay(response *http.Response, retryAttempt uint) (time.Duration, error) { + // Check for Retry-After header first (RFC 7231), applying no jitter + if retryAfter := response.Header.Get("Retry-After"); retryAfter != "" { + // Parse as number of seconds... + if seconds, err := strconv.Atoi(retryAfter); err == nil { + delay := time.Duration(seconds) * time.Second + if delay > 0 { + if delay > maxRetryDelay { + delay = maxRetryDelay + } + return delay, nil + } + } + + // ...or as an HTTP date; both are valid + if retryTime, err := time.Parse(time.RFC1123, retryAfter); err == nil { + delay := time.Until(retryTime) + if delay > 0 { + if delay > maxRetryDelay { + delay = maxRetryDelay + } + return delay, nil + } + } + } + + // Then check for industry-standard X-RateLimit-Reset header, applying positive jitter + if rateLimitReset := response.Header.Get("X-RateLimit-Reset"); rateLimitReset != "" { + if resetTimestamp, err := strconv.ParseInt(rateLimitReset, 10, 64); err == nil { + // Assume Unix timestamp in seconds + resetTime := time.Unix(resetTimestamp, 0) + delay := time.Until(resetTime) + if delay > 0 { + if delay > maxRetryDelay { + delay = maxRetryDelay + } + return r.addPositiveJitter(delay) + } + } + } + + // Fall back to exponential backoff + return r.exponentialBackoff(retryAttempt) +} + +// exponentialBackoff calculates the delay time based on the retry attempt +// and applies symmetric jitter (±10% around the delay). +func (r *Retrier) exponentialBackoff(retryAttempt uint) (time.Duration, error) { + if retryAttempt > 63 { // 2^63+ would overflow uint64 + retryAttempt = 63 + } + + delay := minRetryDelay << retryAttempt + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + return r.addSymmetricJitter(delay) +} + +// addJitterWithRange applies jitter to the given delay. +// minPercent and maxPercent define the jitter range (e.g., 100, 120 for +0% to +20%). +func (r *Retrier) addJitterWithRange(delay time.Duration, minPercent, maxPercent int) (time.Duration, error) { + jitterRange := big.NewInt(int64(delay * time.Duration(maxPercent-minPercent) / 100)) + jitter, err := rand.Int(rand.Reader, jitterRange) + if err != nil { + return 0, err + } + + jitteredDelay := delay + time.Duration(jitter.Int64()) + delay*time.Duration(minPercent-100)/100 + if jitteredDelay < minRetryDelay { + jitteredDelay = minRetryDelay + } + if jitteredDelay > maxRetryDelay { + jitteredDelay = maxRetryDelay + } + return jitteredDelay, nil +} + +// addPositiveJitter applies positive jitter to the given delay (100%-120% range). +func (r *Retrier) addPositiveJitter(delay time.Duration) (time.Duration, error) { + return r.addJitterWithRange(delay, 100, 120) +} + +// addSymmetricJitter applies symmetric jitter to the given delay (90%-110% range). +func (r *Retrier) addSymmetricJitter(delay time.Duration) (time.Duration, error) { + return r.addJitterWithRange(delay, 90, 110) +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/allof/internal/retrier_test.go b/seed/go-sdk/allof/internal/retrier_test.go new file mode 100644 index 000000000000..abe73758708b --- /dev/null +++ b/seed/go-sdk/allof/internal/retrier_test.go @@ -0,0 +1,352 @@ +package internal + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/allof/fern/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type RetryTestCase struct { + description string + + giveAttempts uint + giveStatusCodes []int + giveResponse *InternalTestResponse + + wantResponse *InternalTestResponse + wantError *core.APIError +} + +func TestRetrier(t *testing.T) { + tests := []*RetryTestCase{ + { + description: "retry request succeeds after multiple failures", + giveAttempts: 3, + giveStatusCodes: []int{ + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusOK, + }, + giveResponse: &InternalTestResponse{ + Id: "1", + }, + wantResponse: &InternalTestResponse{ + Id: "1", + }, + }, + { + description: "retry request fails if MaxAttempts is exceeded", + giveAttempts: 3, + giveStatusCodes: []int{ + http.StatusRequestTimeout, + http.StatusRequestTimeout, + http.StatusRequestTimeout, + http.StatusOK, + }, + wantError: &core.APIError{ + StatusCode: http.StatusRequestTimeout, + }, + }, + { + description: "retry durations increase exponentially and stay within the min and max delay values", + giveAttempts: 4, + giveStatusCodes: []int{ + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusOK, + }, + }, + { + description: "retry does not occur on status code 404", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusNotFound, http.StatusOK}, + wantError: &core.APIError{ + StatusCode: http.StatusNotFound, + }, + }, + { + description: "retries occur on status code 429", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusTooManyRequests, http.StatusOK}, + }, + { + description: "retries occur on status code 408", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusRequestTimeout, http.StatusOK}, + }, + { + description: "retries occur on status code 500", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusInternalServerError, http.StatusOK}, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + var ( + test = tc + server = newTestRetryServer(t, test) + client = server.Client() + ) + + t.Parallel() + + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: http.MethodGet, + Request: &InternalTestRequest{}, + Response: &response, + MaxAttempts: test.giveAttempts, + ResponseIsOptional: true, + }, + ) + + if test.wantError != nil { + require.IsType(t, err, &core.APIError{}) + expectedErrorCode := test.wantError.StatusCode + actualErrorCode := err.(*core.APIError).StatusCode + assert.Equal(t, expectedErrorCode, actualErrorCode) + return + } + + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +// newTestRetryServer returns a new *httptest.Server configured with the +// given test parameters, suitable for testing retries. +func newTestRetryServer(t *testing.T, tc *RetryTestCase) *httptest.Server { + var index int + timestamps := make([]time.Time, 0, len(tc.giveStatusCodes)) + + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + timestamps = append(timestamps, time.Now()) + if index > 0 && index < len(expectedRetryDurations) { + // Ensure that the duration between retries increases exponentially, + // and that it is within the minimum and maximum retry delay values. + actualDuration := timestamps[index].Sub(timestamps[index-1]) + expectedDurationMin := expectedRetryDurations[index-1] * 50 / 100 + expectedDurationMax := expectedRetryDurations[index-1] * 150 / 100 + assert.True( + t, + actualDuration >= expectedDurationMin && actualDuration <= expectedDurationMax, + "expected duration to be in range [%v, %v], got %v", + expectedDurationMin, + expectedDurationMax, + actualDuration, + ) + assert.LessOrEqual( + t, + actualDuration, + maxRetryDelay, + "expected duration to be less than the maxRetryDelay (%v), got %v", + maxRetryDelay, + actualDuration, + ) + assert.GreaterOrEqual( + t, + actualDuration, + minRetryDelay, + "expected duration to be greater than the minRetryDelay (%v), got %v", + minRetryDelay, + actualDuration, + ) + } + + request := new(InternalTestRequest) + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + require.LessOrEqual(t, index, len(tc.giveStatusCodes)) + + statusCode := tc.giveStatusCodes[index] + + w.WriteHeader(statusCode) + + if tc.giveResponse != nil && statusCode == http.StatusOK { + bytes, err = json.Marshal(tc.giveResponse) + require.NoError(t, err) + _, err = w.Write(bytes) + require.NoError(t, err) + } + + index++ + }, + ), + ) +} + +// expectedRetryDurations holds an array of calculated retry durations, +// where the index of the array should correspond to the retry attempt. +// +// Values are calculated based off of `minRetryDelay * 2^i`. +var expectedRetryDurations = []time.Duration{ + 1000 * time.Millisecond, // 500ms * 2^1 = 1000ms + 2000 * time.Millisecond, // 500ms * 2^2 = 2000ms + 4000 * time.Millisecond, // 500ms * 2^3 = 4000ms + 8000 * time.Millisecond, // 500ms * 2^4 = 8000ms +} + +func TestRetryWithRequestBody(t *testing.T) { + // This test verifies that POST requests with a body are properly retried. + // The request body should be re-sent on each retry attempt. + expectedBody := `{"id":"test-id"}` + var requestBodies []string + var requestCount int + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount++ + bodyBytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + requestBodies = append(requestBodies, string(bodyBytes)) + + if requestCount == 1 { + // First request - return retryable error + w.WriteHeader(http.StatusServiceUnavailable) + return + } + // Second request - return success + w.WriteHeader(http.StatusOK) + response := &InternalTestResponse{Id: "success"} + bytes, _ := json.Marshal(response) + _, _ = w.Write(bytes) + })) + defer server.Close() + + caller := NewCaller(&CallerParams{ + Client: server.Client(), + }) + + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: http.MethodPost, + Request: &InternalTestRequest{Id: "test-id"}, + Response: &response, + MaxAttempts: 2, + ResponseIsOptional: true, + }, + ) + + require.NoError(t, err) + require.Equal(t, 2, requestCount, "Expected exactly 2 requests") + require.Len(t, requestBodies, 2, "Expected 2 request bodies to be captured") + + // Both requests should have the same non-empty body + assert.Equal(t, expectedBody, requestBodies[0], "First request body should match expected") + assert.Equal(t, expectedBody, requestBodies[1], "Second request body should match expected (retry should re-send body)") +} + +func TestRetryDelayTiming(t *testing.T) { + tests := []struct { + name string + headerName string + headerValueFunc func() string + expectedMinMs int64 + expectedMaxMs int64 + }{ + { + name: "retry-after with seconds value", + headerName: "retry-after", + headerValueFunc: func() string { + return "1" + }, + expectedMinMs: 500, + expectedMaxMs: 1500, + }, + { + name: "retry-after with HTTP date", + headerName: "retry-after", + headerValueFunc: func() string { + return time.Now().Add(3 * time.Second).Format(time.RFC1123) + }, + expectedMinMs: 1500, + expectedMaxMs: 4500, + }, + { + name: "x-ratelimit-reset with future timestamp", + headerName: "x-ratelimit-reset", + headerValueFunc: func() string { + return fmt.Sprintf("%d", time.Now().Add(3*time.Second).Unix()) + }, + expectedMinMs: 1500, + expectedMaxMs: 4500, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var timestamps []time.Time + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + timestamps = append(timestamps, time.Now()) + if len(timestamps) == 1 { + // First request - return retryable error with header + w.Header().Set(tt.headerName, tt.headerValueFunc()) + w.WriteHeader(http.StatusTooManyRequests) + } else { + // Second request - return success + w.WriteHeader(http.StatusOK) + response := &InternalTestResponse{Id: "success"} + bytes, _ := json.Marshal(response) + _, _ = w.Write(bytes) + } + })) + defer server.Close() + + caller := NewCaller(&CallerParams{ + Client: server.Client(), + }) + + var response *InternalTestResponse + _, err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: http.MethodGet, + Request: &InternalTestRequest{}, + Response: &response, + MaxAttempts: 2, + ResponseIsOptional: true, + }, + ) + + require.NoError(t, err) + require.Len(t, timestamps, 2, "Expected exactly 2 requests") + + actualDelayMs := timestamps[1].Sub(timestamps[0]).Milliseconds() + + assert.GreaterOrEqual(t, actualDelayMs, tt.expectedMinMs, + "Actual delay %dms should be >= expected min %dms", actualDelayMs, tt.expectedMinMs) + assert.LessOrEqual(t, actualDelayMs, tt.expectedMaxMs, + "Actual delay %dms should be <= expected max %dms", actualDelayMs, tt.expectedMaxMs) + }) + } +} diff --git a/seed/go-sdk/allof/internal/stringer.go b/seed/go-sdk/allof/internal/stringer.go new file mode 100644 index 000000000000..312801851e0e --- /dev/null +++ b/seed/go-sdk/allof/internal/stringer.go @@ -0,0 +1,13 @@ +package internal + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/allof/internal/time.go b/seed/go-sdk/allof/internal/time.go new file mode 100644 index 000000000000..57f901a35ed8 --- /dev/null +++ b/seed/go-sdk/allof/internal/time.go @@ -0,0 +1,165 @@ +package internal + +import ( + "encoding/json" + "fmt" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + // If the value is not a string, check if it is a number (unix epoch seconds). + var epoch int64 + if numErr := json.Unmarshal(data, &epoch); numErr == nil { + t := time.Unix(epoch, 0).UTC() + *d = DateTime{t: &t} + return nil + } + return err + } + + // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). + parsedTime, err := time.Parse(time.RFC3339Nano, raw) + if err == nil { + *d = DateTime{t: &parsedTime} + return nil + } + rfc3339NanoErr := err + + // Fall back to ISO 8601 without timezone (assume UTC). + parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + iso8601Err := err + + // Fall back to date-only format. + parsedTime, err = time.Parse("2006-01-02", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + dateOnlyErr := err + + return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) +} diff --git a/seed/go-sdk/allof/option/request_option.go b/seed/go-sdk/allof/option/request_option.go new file mode 100644 index 000000000000..d9ffe1640240 --- /dev/null +++ b/seed/go-sdk/allof/option/request_option.go @@ -0,0 +1,73 @@ +// Code generated by Fern. DO NOT EDIT. + +package option + +import ( + core "github.com/allof/fern/core" + http "net/http" + url "net/url" +) + +// RequestOption adapts the behavior of an individual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithBodyProperties adds the given body properties to the request. +func WithBodyProperties(bodyProperties map[string]interface{}) *core.BodyPropertiesOption { + copiedBodyProperties := make(map[string]interface{}, len(bodyProperties)) + for key, value := range bodyProperties { + copiedBodyProperties[key] = value + } + return &core.BodyPropertiesOption{ + BodyProperties: copiedBodyProperties, + } +} + +// WithQueryParameters adds the given query parameters to the request. +func WithQueryParameters(queryParameters url.Values) *core.QueryParametersOption { + copiedQueryParameters := make(url.Values, len(queryParameters)) + for key, values := range queryParameters { + copiedQueryParameters[key] = values + } + return &core.QueryParametersOption{ + QueryParameters: copiedQueryParameters, + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithMaxStreamBufSize configures the maximum buffer size for streaming responses. +// This controls the maximum size of a single message (in bytes) that the stream +// can process. By default, this is set to 1MB. +func WithMaxStreamBufSize(size int) *core.MaxBufSizeOption { + return &core.MaxBufSizeOption{ + MaxBufSize: size, + } +} diff --git a/seed/go-sdk/allof/pointer.go b/seed/go-sdk/allof/pointer.go new file mode 100644 index 000000000000..f9a8177e734f --- /dev/null +++ b/seed/go-sdk/allof/pointer.go @@ -0,0 +1,137 @@ +package api + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Bytes returns a pointer to the given []byte value. +func Bytes(b []byte) *[]byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/allof/pointer_test.go b/seed/go-sdk/allof/pointer_test.go new file mode 100644 index 000000000000..06d62d2410b2 --- /dev/null +++ b/seed/go-sdk/allof/pointer_test.go @@ -0,0 +1,211 @@ +package api + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestBool(t *testing.T) { + value := true + ptr := Bool(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestByte(t *testing.T) { + value := byte(42) + ptr := Byte(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestComplex64(t *testing.T) { + value := complex64(1 + 2i) + ptr := Complex64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestComplex128(t *testing.T) { + value := complex128(1 + 2i) + ptr := Complex128(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestFloat32(t *testing.T) { + value := float32(3.14) + ptr := Float32(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestFloat64(t *testing.T) { + value := 3.14159 + ptr := Float64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt(t *testing.T) { + value := 42 + ptr := Int(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt8(t *testing.T) { + value := int8(42) + ptr := Int8(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt16(t *testing.T) { + value := int16(42) + ptr := Int16(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt32(t *testing.T) { + value := int32(42) + ptr := Int32(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestInt64(t *testing.T) { + value := int64(42) + ptr := Int64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestRune(t *testing.T) { + value := 'A' + ptr := Rune(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestString(t *testing.T) { + value := "hello" + ptr := String(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint(t *testing.T) { + value := uint(42) + ptr := Uint(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint8(t *testing.T) { + value := uint8(42) + ptr := Uint8(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint16(t *testing.T) { + value := uint16(42) + ptr := Uint16(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint32(t *testing.T) { + value := uint32(42) + ptr := Uint32(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUint64(t *testing.T) { + value := uint64(42) + ptr := Uint64(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUintptr(t *testing.T) { + value := uintptr(42) + ptr := Uintptr(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestUUID(t *testing.T) { + value := uuid.New() + ptr := UUID(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestTime(t *testing.T) { + value := time.Now() + ptr := Time(value) + assert.NotNil(t, ptr) + assert.Equal(t, value, *ptr) +} + +func TestMustParseDate(t *testing.T) { + t.Run("valid date", func(t *testing.T) { + result := MustParseDate("2024-01-15") + expected, _ := time.Parse("2006-01-02", "2024-01-15") + assert.Equal(t, expected, result) + }) + + t.Run("invalid date panics", func(t *testing.T) { + assert.Panics(t, func() { + MustParseDate("invalid-date") + }) + }) +} + +func TestMustParseDateTime(t *testing.T) { + t.Run("valid datetime", func(t *testing.T) { + result := MustParseDateTime("2024-01-15T10:30:00Z") + expected, _ := time.Parse(time.RFC3339, "2024-01-15T10:30:00Z") + assert.Equal(t, expected, result) + }) + + t.Run("invalid datetime panics", func(t *testing.T) { + assert.Panics(t, func() { + MustParseDateTime("invalid-datetime") + }) + }) +} + +func TestPointerHelpersWithZeroValues(t *testing.T) { + t.Run("zero bool", func(t *testing.T) { + ptr := Bool(false) + assert.NotNil(t, ptr) + assert.Equal(t, false, *ptr) + }) + + t.Run("zero int", func(t *testing.T) { + ptr := Int(0) + assert.NotNil(t, ptr) + assert.Equal(t, 0, *ptr) + }) + + t.Run("empty string", func(t *testing.T) { + ptr := String("") + assert.NotNil(t, ptr) + assert.Equal(t, "", *ptr) + }) + + t.Run("zero time", func(t *testing.T) { + zeroTime := time.Time{} + ptr := Time(zeroTime) + assert.NotNil(t, ptr) + assert.Equal(t, zeroTime, *ptr) + }) +} diff --git a/seed/go-sdk/allof/reference.md b/seed/go-sdk/allof/reference.md new file mode 100644 index 000000000000..86fef51f321b --- /dev/null +++ b/seed/go-sdk/allof/reference.md @@ -0,0 +1,186 @@ +# Reference +
client.SearchRuleTypes() -> *fern.RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +request := &fern.SearchRuleTypesRequest{} +client.SearchRuleTypes( + context.TODO(), + request, + ) +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `*string` + +
+
+
+
+ + +
+
+
+ +
client.CreateRule(request) -> *fern.RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +request := &fern.RuleCreateRequest{ + Name: "name", + ExecutionContext: fern.RuleExecutionContextProd, + } +client.CreateRule( + context.TODO(), + request, + ) +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `string` + +
+
+ +
+
+ +**executionContext:** `*fern.RuleExecutionContext` + +
+
+
+
+ + +
+
+
+ +
client.ListUsers() -> *fern.UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +client.ListUsers( + context.TODO(), + ) +} +``` +
+
+
+
+ + +
+
+
+ +
client.GetEntity() -> *fern.CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +client.GetEntity( + context.TODO(), + ) +} +``` +
+
+
+
+ + +
+
+
+ +
client.GetOrganization() -> *fern.Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```go +client.GetOrganization( + context.TODO(), + ) +} +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/go-sdk/allof/snippet.json b/seed/go-sdk/allof/snippet.json new file mode 100644 index 000000000000..900f2d6c1dbc --- /dev/null +++ b/seed/go-sdk/allof/snippet.json @@ -0,0 +1,59 @@ +{ + "endpoints": [ + { + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetEntity(\n\tcontext.TODO(),\n)\n" + } + }, + { + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetOrganization(\n\tcontext.TODO(),\n)\n" + } + }, + { + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof/fern\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.SearchRuleTypes(\n\tcontext.TODO(),\n\t\u0026fern.SearchRuleTypesRequest{},\n)\n" + } + }, + { + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof/fern\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.CreateRule(\n\tcontext.TODO(),\n\t\u0026fern.RuleCreateRequest{\n\t\tName: \"name\",\n\t\tExecutionContext: fern.RuleExecutionContextProd,\n\t},\n)\n" + } + }, + { + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.ListUsers(\n\tcontext.TODO(),\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/allof/types.go b/seed/go-sdk/allof/types.go new file mode 100644 index 000000000000..b3827eba21fc --- /dev/null +++ b/seed/go-sdk/allof/types.go @@ -0,0 +1,1989 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + json "encoding/json" + fmt "fmt" + internal "github.com/allof/fern/internal" + big "math/big" + time "time" +) + +var ( + ruleCreateRequestFieldName = big.NewInt(1 << 0) + ruleCreateRequestFieldExecutionContext = big.NewInt(1 << 1) +) + +type RuleCreateRequest struct { + Name string `json:"name" url:"-"` + ExecutionContext RuleExecutionContext `json:"executionContext" url:"-"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` +} + +func (r *RuleCreateRequest) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleCreateRequest) SetName(name string) { + r.Name = name + r.require(ruleCreateRequestFieldName) +} + +// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleCreateRequest) SetExecutionContext(executionContext RuleExecutionContext) { + r.ExecutionContext = executionContext + r.require(ruleCreateRequestFieldExecutionContext) +} + +func (r *RuleCreateRequest) UnmarshalJSON(data []byte) error { + type unmarshaler RuleCreateRequest + var body unmarshaler + if err := json.Unmarshal(data, &body); err != nil { + return err + } + *r = RuleCreateRequest(body) + return nil +} + +func (r *RuleCreateRequest) MarshalJSON() ([]byte, error) { + type embed RuleCreateRequest + var marshaler = struct { + embed + }{ + embed: embed(*r), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +var ( + searchRuleTypesRequestFieldQuery = big.NewInt(1 << 0) +) + +type SearchRuleTypesRequest struct { + Query *string `json:"-" url:"query,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` +} + +func (s *SearchRuleTypesRequest) require(field *big.Int) { + if s.explicitFields == nil { + s.explicitFields = big.NewInt(0) + } + s.explicitFields.Or(s.explicitFields, field) +} + +// SetQuery sets the Query field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (s *SearchRuleTypesRequest) SetQuery(query *string) { + s.Query = query + s.require(searchRuleTypesRequestFieldQuery) +} + +// Common audit metadata. +var ( + auditInfoFieldCreatedBy = big.NewInt(1 << 0) + auditInfoFieldCreatedDateTime = big.NewInt(1 << 1) + auditInfoFieldModifiedBy = big.NewInt(1 << 2) + auditInfoFieldModifiedDateTime = big.NewInt(1 << 3) +) + +type AuditInfo struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (a *AuditInfo) GetCreatedBy() *string { + if a == nil { + return nil + } + return a.CreatedBy +} + +func (a *AuditInfo) GetCreatedDateTime() *time.Time { + if a == nil { + return nil + } + return a.CreatedDateTime +} + +func (a *AuditInfo) GetModifiedBy() *string { + if a == nil { + return nil + } + return a.ModifiedBy +} + +func (a *AuditInfo) GetModifiedDateTime() *time.Time { + if a == nil { + return nil + } + return a.ModifiedDateTime +} + +func (a *AuditInfo) GetExtraProperties() map[string]interface{} { + if a == nil { + return nil + } + return a.extraProperties +} + +func (a *AuditInfo) require(field *big.Int) { + if a.explicitFields == nil { + a.explicitFields = big.NewInt(0) + } + a.explicitFields.Or(a.explicitFields, field) +} + +// SetCreatedBy sets the CreatedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetCreatedBy(createdBy *string) { + a.CreatedBy = createdBy + a.require(auditInfoFieldCreatedBy) +} + +// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetCreatedDateTime(createdDateTime *time.Time) { + a.CreatedDateTime = createdDateTime + a.require(auditInfoFieldCreatedDateTime) +} + +// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetModifiedBy(modifiedBy *string) { + a.ModifiedBy = modifiedBy + a.require(auditInfoFieldModifiedBy) +} + +// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (a *AuditInfo) SetModifiedDateTime(modifiedDateTime *time.Time) { + a.ModifiedDateTime = modifiedDateTime + a.require(auditInfoFieldModifiedDateTime) +} + +func (a *AuditInfo) UnmarshalJSON(data []byte) error { + type embed AuditInfo + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *a = AuditInfo(unmarshaler.embed) + a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *a) + if err != nil { + return err + } + a.extraProperties = extraProperties + a.rawJSON = json.RawMessage(data) + return nil +} + +func (a *AuditInfo) MarshalJSON() ([]byte, error) { + type embed AuditInfo + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*a), + CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, a.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (a *AuditInfo) String() string { + if a == nil { + return "" + } + if len(a.rawJSON) > 0 { + if value, err := internal.StringifyJSON(a.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +var ( + baseOrgFieldID = big.NewInt(1 << 0) + baseOrgFieldMetadata = big.NewInt(1 << 1) +) + +type BaseOrg struct { + ID string `json:"id" url:"id"` + Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (b *BaseOrg) GetID() string { + if b == nil { + return "" + } + return b.ID +} + +func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { + if b == nil { + return nil + } + return b.Metadata +} + +func (b *BaseOrg) GetExtraProperties() map[string]interface{} { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrg) require(field *big.Int) { + if b.explicitFields == nil { + b.explicitFields = big.NewInt(0) + } + b.explicitFields.Or(b.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrg) SetID(id string) { + b.ID = id + b.require(baseOrgFieldID) +} + +// SetMetadata sets the Metadata field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrg) SetMetadata(metadata *BaseOrgMetadata) { + b.Metadata = metadata + b.require(baseOrgFieldMetadata) +} + +func (b *BaseOrg) UnmarshalJSON(data []byte) error { + type unmarshaler BaseOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrg) MarshalJSON() ([]byte, error) { + type embed BaseOrg + var marshaler = struct { + embed + }{ + embed: embed(*b), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (b *BaseOrg) String() string { + if b == nil { + return "" + } + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +var ( + baseOrgMetadataFieldRegion = big.NewInt(1 << 0) + baseOrgMetadataFieldTier = big.NewInt(1 << 1) +) + +type BaseOrgMetadata struct { + // Deployment region from BaseOrg. + Region string `json:"region" url:"region"` + // Subscription tier. + Tier *string `json:"tier,omitempty" url:"tier,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (b *BaseOrgMetadata) GetRegion() string { + if b == nil { + return "" + } + return b.Region +} + +func (b *BaseOrgMetadata) GetTier() *string { + if b == nil { + return nil + } + return b.Tier +} + +func (b *BaseOrgMetadata) GetExtraProperties() map[string]interface{} { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrgMetadata) require(field *big.Int) { + if b.explicitFields == nil { + b.explicitFields = big.NewInt(0) + } + b.explicitFields.Or(b.explicitFields, field) +} + +// SetRegion sets the Region field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrgMetadata) SetRegion(region string) { + b.Region = region + b.require(baseOrgMetadataFieldRegion) +} + +// SetTier sets the Tier field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (b *BaseOrgMetadata) SetTier(tier *string) { + b.Tier = tier + b.require(baseOrgMetadataFieldTier) +} + +func (b *BaseOrgMetadata) UnmarshalJSON(data []byte) error { + type unmarshaler BaseOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrgMetadata) MarshalJSON() ([]byte, error) { + type embed BaseOrgMetadata + var marshaler = struct { + embed + }{ + embed: embed(*b), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (b *BaseOrgMetadata) String() string { + if b == nil { + return "" + } + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +var ( + combinedEntityFieldStatus = big.NewInt(1 << 0) + combinedEntityFieldID = big.NewInt(1 << 1) + combinedEntityFieldName = big.NewInt(1 << 2) + combinedEntityFieldSummary = big.NewInt(1 << 3) +) + +type CombinedEntity struct { + Status CombinedEntityStatus `json:"status" url:"status"` + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Identifiable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (c *CombinedEntity) GetStatus() CombinedEntityStatus { + if c == nil { + return "" + } + return c.Status +} + +func (c *CombinedEntity) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *CombinedEntity) GetName() *string { + if c == nil { + return nil + } + return c.Name +} + +func (c *CombinedEntity) GetSummary() *string { + if c == nil { + return nil + } + return c.Summary +} + +func (c *CombinedEntity) GetExtraProperties() map[string]interface{} { + if c == nil { + return nil + } + return c.extraProperties +} + +func (c *CombinedEntity) require(field *big.Int) { + if c.explicitFields == nil { + c.explicitFields = big.NewInt(0) + } + c.explicitFields.Or(c.explicitFields, field) +} + +// SetStatus sets the Status field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetStatus(status CombinedEntityStatus) { + c.Status = status + c.require(combinedEntityFieldStatus) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetID(id string) { + c.ID = id + c.require(combinedEntityFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetName(name *string) { + c.Name = name + c.require(combinedEntityFieldName) +} + +// SetSummary sets the Summary field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (c *CombinedEntity) SetSummary(summary *string) { + c.Summary = summary + c.require(combinedEntityFieldSummary) +} + +func (c *CombinedEntity) UnmarshalJSON(data []byte) error { + type unmarshaler CombinedEntity + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CombinedEntity(value) + extraProperties, err := internal.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + c.rawJSON = json.RawMessage(data) + return nil +} + +func (c *CombinedEntity) MarshalJSON() ([]byte, error) { + type embed CombinedEntity + var marshaler = struct { + embed + }{ + embed: embed(*c), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, c.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (c *CombinedEntity) String() string { + if c == nil { + return "" + } + if len(c.rawJSON) > 0 { + if value, err := internal.StringifyJSON(c.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type CombinedEntityStatus string + +const ( + CombinedEntityStatusActive CombinedEntityStatus = "active" + CombinedEntityStatusArchived CombinedEntityStatus = "archived" +) + +func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { + switch s { + case "active": + return CombinedEntityStatusActive, nil + case "archived": + return CombinedEntityStatusArchived, nil + } + var t CombinedEntityStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { + return &c +} + +var ( + describableFieldName = big.NewInt(1 << 0) + describableFieldSummary = big.NewInt(1 << 1) +) + +type Describable struct { + // Display name from Describable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (d *Describable) GetName() *string { + if d == nil { + return nil + } + return d.Name +} + +func (d *Describable) GetSummary() *string { + if d == nil { + return nil + } + return d.Summary +} + +func (d *Describable) GetExtraProperties() map[string]interface{} { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *Describable) require(field *big.Int) { + if d.explicitFields == nil { + d.explicitFields = big.NewInt(0) + } + d.explicitFields.Or(d.explicitFields, field) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *Describable) SetName(name *string) { + d.Name = name + d.require(describableFieldName) +} + +// SetSummary sets the Summary field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *Describable) SetSummary(summary *string) { + d.Summary = summary + d.require(describableFieldSummary) +} + +func (d *Describable) UnmarshalJSON(data []byte) error { + type unmarshaler Describable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Describable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *Describable) MarshalJSON() ([]byte, error) { + type embed Describable + var marshaler = struct { + embed + }{ + embed: embed(*d), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (d *Describable) String() string { + if d == nil { + return "" + } + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +var ( + detailedOrgFieldMetadata = big.NewInt(1 << 0) +) + +type DetailedOrg struct { + Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { + if d == nil { + return nil + } + return d.Metadata +} + +func (d *DetailedOrg) GetExtraProperties() map[string]interface{} { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrg) require(field *big.Int) { + if d.explicitFields == nil { + d.explicitFields = big.NewInt(0) + } + d.explicitFields.Or(d.explicitFields, field) +} + +// SetMetadata sets the Metadata field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *DetailedOrg) SetMetadata(metadata *DetailedOrgMetadata) { + d.Metadata = metadata + d.require(detailedOrgFieldMetadata) +} + +func (d *DetailedOrg) UnmarshalJSON(data []byte) error { + type unmarshaler DetailedOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrg) MarshalJSON() ([]byte, error) { + type embed DetailedOrg + var marshaler = struct { + embed + }{ + embed: embed(*d), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (d *DetailedOrg) String() string { + if d == nil { + return "" + } + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +var ( + detailedOrgMetadataFieldRegion = big.NewInt(1 << 0) + detailedOrgMetadataFieldDomain = big.NewInt(1 << 1) +) + +type DetailedOrgMetadata struct { + // Deployment region from DetailedOrg. + Region string `json:"region" url:"region"` + // Custom domain name. + Domain *string `json:"domain,omitempty" url:"domain,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (d *DetailedOrgMetadata) GetRegion() string { + if d == nil { + return "" + } + return d.Region +} + +func (d *DetailedOrgMetadata) GetDomain() *string { + if d == nil { + return nil + } + return d.Domain +} + +func (d *DetailedOrgMetadata) GetExtraProperties() map[string]interface{} { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrgMetadata) require(field *big.Int) { + if d.explicitFields == nil { + d.explicitFields = big.NewInt(0) + } + d.explicitFields.Or(d.explicitFields, field) +} + +// SetRegion sets the Region field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *DetailedOrgMetadata) SetRegion(region string) { + d.Region = region + d.require(detailedOrgMetadataFieldRegion) +} + +// SetDomain sets the Domain field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (d *DetailedOrgMetadata) SetDomain(domain *string) { + d.Domain = domain + d.require(detailedOrgMetadataFieldDomain) +} + +func (d *DetailedOrgMetadata) UnmarshalJSON(data []byte) error { + type unmarshaler DetailedOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrgMetadata) MarshalJSON() ([]byte, error) { + type embed DetailedOrgMetadata + var marshaler = struct { + embed + }{ + embed: embed(*d), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (d *DetailedOrgMetadata) String() string { + if d == nil { + return "" + } + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +var ( + identifiableFieldID = big.NewInt(1 << 0) + identifiableFieldName = big.NewInt(1 << 1) +) + +type Identifiable struct { + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Identifiable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (i *Identifiable) GetID() string { + if i == nil { + return "" + } + return i.ID +} + +func (i *Identifiable) GetName() *string { + if i == nil { + return nil + } + return i.Name +} + +func (i *Identifiable) GetExtraProperties() map[string]interface{} { + if i == nil { + return nil + } + return i.extraProperties +} + +func (i *Identifiable) require(field *big.Int) { + if i.explicitFields == nil { + i.explicitFields = big.NewInt(0) + } + i.explicitFields.Or(i.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (i *Identifiable) SetID(id string) { + i.ID = id + i.require(identifiableFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (i *Identifiable) SetName(name *string) { + i.Name = name + i.require(identifiableFieldName) +} + +func (i *Identifiable) UnmarshalJSON(data []byte) error { + type unmarshaler Identifiable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *i = Identifiable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *i) + if err != nil { + return err + } + i.extraProperties = extraProperties + i.rawJSON = json.RawMessage(data) + return nil +} + +func (i *Identifiable) MarshalJSON() ([]byte, error) { + type embed Identifiable + var marshaler = struct { + embed + }{ + embed: embed(*i), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, i.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (i *Identifiable) String() string { + if i == nil { + return "" + } + if len(i.rawJSON) > 0 { + if value, err := internal.StringifyJSON(i.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +var ( + organizationFieldName = big.NewInt(1 << 0) + organizationFieldID = big.NewInt(1 << 1) + organizationFieldMetadata = big.NewInt(1 << 2) +) + +type Organization struct { + Name string `json:"name" url:"name"` + ID string `json:"id" url:"id"` + Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (o *Organization) GetName() string { + if o == nil { + return "" + } + return o.Name +} + +func (o *Organization) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *Organization) GetMetadata() *BaseOrgMetadata { + if o == nil { + return nil + } + return o.Metadata +} + +func (o *Organization) GetExtraProperties() map[string]interface{} { + if o == nil { + return nil + } + return o.extraProperties +} + +func (o *Organization) require(field *big.Int) { + if o.explicitFields == nil { + o.explicitFields = big.NewInt(0) + } + o.explicitFields.Or(o.explicitFields, field) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *Organization) SetName(name string) { + o.Name = name + o.require(organizationFieldName) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *Organization) SetID(id string) { + o.ID = id + o.require(organizationFieldID) +} + +// SetMetadata sets the Metadata field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (o *Organization) SetMetadata(metadata *BaseOrgMetadata) { + o.Metadata = metadata + o.require(organizationFieldMetadata) +} + +func (o *Organization) UnmarshalJSON(data []byte) error { + type unmarshaler Organization + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *o = Organization(value) + extraProperties, err := internal.ExtractExtraProperties(data, *o) + if err != nil { + return err + } + o.extraProperties = extraProperties + o.rawJSON = json.RawMessage(data) + return nil +} + +func (o *Organization) MarshalJSON() ([]byte, error) { + type embed Organization + var marshaler = struct { + embed + }{ + embed: embed(*o), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, o.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (o *Organization) String() string { + if o == nil { + return "" + } + if len(o.rawJSON) > 0 { + if value, err := internal.StringifyJSON(o.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +var ( + paginatedResultFieldPaging = big.NewInt(1 << 0) + paginatedResultFieldResults = big.NewInt(1 << 1) +) + +type PaginatedResult struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []any `json:"results" url:"results"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (p *PaginatedResult) GetPaging() *PagingCursors { + if p == nil { + return nil + } + return p.Paging +} + +func (p *PaginatedResult) GetResults() []any { + if p == nil { + return nil + } + return p.Results +} + +func (p *PaginatedResult) GetExtraProperties() map[string]interface{} { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PaginatedResult) require(field *big.Int) { + if p.explicitFields == nil { + p.explicitFields = big.NewInt(0) + } + p.explicitFields.Or(p.explicitFields, field) +} + +// SetPaging sets the Paging field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PaginatedResult) SetPaging(paging *PagingCursors) { + p.Paging = paging + p.require(paginatedResultFieldPaging) +} + +// SetResults sets the Results field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PaginatedResult) SetResults(results []any) { + p.Results = results + p.require(paginatedResultFieldResults) +} + +func (p *PaginatedResult) UnmarshalJSON(data []byte) error { + type unmarshaler PaginatedResult + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PaginatedResult(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PaginatedResult) MarshalJSON() ([]byte, error) { + type embed PaginatedResult + var marshaler = struct { + embed + }{ + embed: embed(*p), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (p *PaginatedResult) String() string { + if p == nil { + return "" + } + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +var ( + pagingCursorsFieldNext = big.NewInt(1 << 0) + pagingCursorsFieldPrevious = big.NewInt(1 << 1) +) + +type PagingCursors struct { + // Cursor for the next page of results. + Next string `json:"next" url:"next"` + // Cursor for the previous page of results. + Previous *string `json:"previous,omitempty" url:"previous,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (p *PagingCursors) GetNext() string { + if p == nil { + return "" + } + return p.Next +} + +func (p *PagingCursors) GetPrevious() *string { + if p == nil { + return nil + } + return p.Previous +} + +func (p *PagingCursors) GetExtraProperties() map[string]interface{} { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PagingCursors) require(field *big.Int) { + if p.explicitFields == nil { + p.explicitFields = big.NewInt(0) + } + p.explicitFields.Or(p.explicitFields, field) +} + +// SetNext sets the Next field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PagingCursors) SetNext(next string) { + p.Next = next + p.require(pagingCursorsFieldNext) +} + +// SetPrevious sets the Previous field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (p *PagingCursors) SetPrevious(previous *string) { + p.Previous = previous + p.require(pagingCursorsFieldPrevious) +} + +func (p *PagingCursors) UnmarshalJSON(data []byte) error { + type unmarshaler PagingCursors + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PagingCursors(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PagingCursors) MarshalJSON() ([]byte, error) { + type embed PagingCursors + var marshaler = struct { + embed + }{ + embed: embed(*p), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (p *PagingCursors) String() string { + if p == nil { + return "" + } + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +// Execution environment for a rule. +type RuleExecutionContext string + +const ( + RuleExecutionContextProd RuleExecutionContext = "prod" + RuleExecutionContextStaging RuleExecutionContext = "staging" + RuleExecutionContextDev RuleExecutionContext = "dev" +) + +func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { + switch s { + case "prod": + return RuleExecutionContextProd, nil + case "staging": + return RuleExecutionContextStaging, nil + case "dev": + return RuleExecutionContextDev, nil + } + var t RuleExecutionContext + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleExecutionContext) Ptr() *RuleExecutionContext { + return &r +} + +var ( + ruleResponseFieldCreatedBy = big.NewInt(1 << 0) + ruleResponseFieldCreatedDateTime = big.NewInt(1 << 1) + ruleResponseFieldModifiedBy = big.NewInt(1 << 2) + ruleResponseFieldModifiedDateTime = big.NewInt(1 << 3) + ruleResponseFieldID = big.NewInt(1 << 4) + ruleResponseFieldName = big.NewInt(1 << 5) + ruleResponseFieldStatus = big.NewInt(1 << 6) + ruleResponseFieldExecutionContext = big.NewInt(1 << 7) +) + +type RuleResponse struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Status RuleResponseStatus `json:"status" url:"status"` + ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (r *RuleResponse) GetCreatedBy() *string { + if r == nil { + return nil + } + return r.CreatedBy +} + +func (r *RuleResponse) GetCreatedDateTime() *time.Time { + if r == nil { + return nil + } + return r.CreatedDateTime +} + +func (r *RuleResponse) GetModifiedBy() *string { + if r == nil { + return nil + } + return r.ModifiedBy +} + +func (r *RuleResponse) GetModifiedDateTime() *time.Time { + if r == nil { + return nil + } + return r.ModifiedDateTime +} + +func (r *RuleResponse) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleResponse) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleResponse) GetStatus() RuleResponseStatus { + if r == nil { + return "" + } + return r.Status +} + +func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { + if r == nil { + return nil + } + return r.ExecutionContext +} + +func (r *RuleResponse) GetExtraProperties() map[string]interface{} { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleResponse) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetCreatedBy sets the CreatedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetCreatedBy(createdBy *string) { + r.CreatedBy = createdBy + r.require(ruleResponseFieldCreatedBy) +} + +// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetCreatedDateTime(createdDateTime *time.Time) { + r.CreatedDateTime = createdDateTime + r.require(ruleResponseFieldCreatedDateTime) +} + +// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetModifiedBy(modifiedBy *string) { + r.ModifiedBy = modifiedBy + r.require(ruleResponseFieldModifiedBy) +} + +// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetModifiedDateTime(modifiedDateTime *time.Time) { + r.ModifiedDateTime = modifiedDateTime + r.require(ruleResponseFieldModifiedDateTime) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetID(id string) { + r.ID = id + r.require(ruleResponseFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetName(name string) { + r.Name = name + r.require(ruleResponseFieldName) +} + +// SetStatus sets the Status field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetStatus(status RuleResponseStatus) { + r.Status = status + r.require(ruleResponseFieldStatus) +} + +// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleResponse) SetExecutionContext(executionContext *RuleExecutionContext) { + r.ExecutionContext = executionContext + r.require(ruleResponseFieldExecutionContext) +} + +func (r *RuleResponse) UnmarshalJSON(data []byte) error { + type embed RuleResponse + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*r), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *r = RuleResponse(unmarshaler.embed) + r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleResponse) MarshalJSON() ([]byte, error) { + type embed RuleResponse + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` + }{ + embed: embed(*r), + CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (r *RuleResponse) String() string { + if r == nil { + return "" + } + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type RuleResponseStatus string + +const ( + RuleResponseStatusActive RuleResponseStatus = "active" + RuleResponseStatusInactive RuleResponseStatus = "inactive" + RuleResponseStatusDraft RuleResponseStatus = "draft" +) + +func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { + switch s { + case "active": + return RuleResponseStatusActive, nil + case "inactive": + return RuleResponseStatusInactive, nil + case "draft": + return RuleResponseStatusDraft, nil + } + var t RuleResponseStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleResponseStatus) Ptr() *RuleResponseStatus { + return &r +} + +var ( + ruleTypeFieldID = big.NewInt(1 << 0) + ruleTypeFieldName = big.NewInt(1 << 1) + ruleTypeFieldDescription = big.NewInt(1 << 2) +) + +type RuleType struct { + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Description *string `json:"description,omitempty" url:"description,omitempty"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (r *RuleType) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleType) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleType) GetDescription() *string { + if r == nil { + return nil + } + return r.Description +} + +func (r *RuleType) GetExtraProperties() map[string]interface{} { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleType) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleType) SetID(id string) { + r.ID = id + r.require(ruleTypeFieldID) +} + +// SetName sets the Name field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleType) SetName(name string) { + r.Name = name + r.require(ruleTypeFieldName) +} + +// SetDescription sets the Description field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleType) SetDescription(description *string) { + r.Description = description + r.require(ruleTypeFieldDescription) +} + +func (r *RuleType) UnmarshalJSON(data []byte) error { + type unmarshaler RuleType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleType(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleType) MarshalJSON() ([]byte, error) { + type embed RuleType + var marshaler = struct { + embed + }{ + embed: embed(*r), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (r *RuleType) String() string { + if r == nil { + return "" + } + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +var ( + ruleTypeSearchResponseFieldResults = big.NewInt(1 << 0) + ruleTypeSearchResponseFieldPaging = big.NewInt(1 << 1) +) + +type RuleTypeSearchResponse struct { + // Current page of results from the requested resource. + Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` + Paging *PagingCursors `json:"paging" url:"paging"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (r *RuleTypeSearchResponse) GetResults() []*RuleType { + if r == nil { + return nil + } + return r.Results +} + +func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { + if r == nil { + return nil + } + return r.Paging +} + +func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]interface{} { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleTypeSearchResponse) require(field *big.Int) { + if r.explicitFields == nil { + r.explicitFields = big.NewInt(0) + } + r.explicitFields.Or(r.explicitFields, field) +} + +// SetResults sets the Results field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleTypeSearchResponse) SetResults(results []*RuleType) { + r.Results = results + r.require(ruleTypeSearchResponseFieldResults) +} + +// SetPaging sets the Paging field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (r *RuleTypeSearchResponse) SetPaging(paging *PagingCursors) { + r.Paging = paging + r.require(ruleTypeSearchResponseFieldPaging) +} + +func (r *RuleTypeSearchResponse) UnmarshalJSON(data []byte) error { + type unmarshaler RuleTypeSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleTypeSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleTypeSearchResponse) MarshalJSON() ([]byte, error) { + type embed RuleTypeSearchResponse + var marshaler = struct { + embed + }{ + embed: embed(*r), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (r *RuleTypeSearchResponse) String() string { + if r == nil { + return "" + } + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +var ( + userFieldID = big.NewInt(1 << 0) + userFieldEmail = big.NewInt(1 << 1) +) + +type User struct { + ID string `json:"id" url:"id"` + Email string `json:"email" url:"email"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (u *User) GetID() string { + if u == nil { + return "" + } + return u.ID +} + +func (u *User) GetEmail() string { + if u == nil { + return "" + } + return u.Email +} + +func (u *User) GetExtraProperties() map[string]interface{} { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *User) require(field *big.Int) { + if u.explicitFields == nil { + u.explicitFields = big.NewInt(0) + } + u.explicitFields.Or(u.explicitFields, field) +} + +// SetID sets the ID field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *User) SetID(id string) { + u.ID = id + u.require(userFieldID) +} + +// SetEmail sets the Email field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *User) SetEmail(email string) { + u.Email = email + u.require(userFieldEmail) +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *User) MarshalJSON() ([]byte, error) { + type embed User + var marshaler = struct { + embed + }{ + embed: embed(*u), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (u *User) String() string { + if u == nil { + return "" + } + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} + +var ( + userSearchResponseFieldResults = big.NewInt(1 << 0) + userSearchResponseFieldPaging = big.NewInt(1 << 1) +) + +type UserSearchResponse struct { + // Current page of results from the requested resource. + Results []*User `json:"results,omitempty" url:"results,omitempty"` + Paging *PagingCursors `json:"paging" url:"paging"` + + // Private bitmask of fields set to an explicit value and therefore not to be omitted + explicitFields *big.Int `json:"-" url:"-"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (u *UserSearchResponse) GetResults() []*User { + if u == nil { + return nil + } + return u.Results +} + +func (u *UserSearchResponse) GetPaging() *PagingCursors { + if u == nil { + return nil + } + return u.Paging +} + +func (u *UserSearchResponse) GetExtraProperties() map[string]interface{} { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *UserSearchResponse) require(field *big.Int) { + if u.explicitFields == nil { + u.explicitFields = big.NewInt(0) + } + u.explicitFields.Or(u.explicitFields, field) +} + +// SetResults sets the Results field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *UserSearchResponse) SetResults(results []*User) { + u.Results = results + u.require(userSearchResponseFieldResults) +} + +// SetPaging sets the Paging field and marks it as non-optional; +// this prevents an empty or null value for this field from being omitted during serialization. +func (u *UserSearchResponse) SetPaging(paging *PagingCursors) { + u.Paging = paging + u.require(userSearchResponseFieldPaging) +} + +func (u *UserSearchResponse) UnmarshalJSON(data []byte) error { + type unmarshaler UserSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UserSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *UserSearchResponse) MarshalJSON() ([]byte, error) { + type embed UserSearchResponse + var marshaler = struct { + embed + }{ + embed: embed(*u), + } + explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) + return json.Marshal(explicitMarshaler) +} + +func (u *UserSearchResponse) String() string { + if u == nil { + return "" + } + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/allof/types_test.go b/seed/go-sdk/allof/types_test.go new file mode 100644 index 000000000000..bbbf7f274167 --- /dev/null +++ b/seed/go-sdk/allof/types_test.go @@ -0,0 +1,4473 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + json "encoding/json" + assert "github.com/stretchr/testify/assert" + require "github.com/stretchr/testify/require" + testing "testing" + time "time" +) + +func TestSettersRuleCreateRequest(t *testing.T) { + t.Run("SetName", func(t *testing.T) { + obj := &RuleCreateRequest{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetExecutionContext", func(t *testing.T) { + obj := &RuleCreateRequest{} + var fernTestValueExecutionContext RuleExecutionContext + obj.SetExecutionContext(fernTestValueExecutionContext) + assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestSettersMarkExplicitRuleCreateRequest(t *testing.T) { + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleCreateRequest{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleCreateRequest{} + var fernTestValueExecutionContext RuleExecutionContext + + // Act + obj.SetExecutionContext(fernTestValueExecutionContext) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersSearchRuleTypesRequest(t *testing.T) { + t.Run("SetQuery", func(t *testing.T) { + obj := &SearchRuleTypesRequest{} + var fernTestValueQuery *string + obj.SetQuery(fernTestValueQuery) + assert.Equal(t, fernTestValueQuery, obj.Query) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestSettersMarkExplicitSearchRuleTypesRequest(t *testing.T) { + t.Run("SetQuery_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &SearchRuleTypesRequest{} + var fernTestValueQuery *string + + // Act + obj.SetQuery(fernTestValueQuery) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersAuditInfo(t *testing.T) { + t.Run("SetCreatedBy", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueCreatedBy *string + obj.SetCreatedBy(fernTestValueCreatedBy) + assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetCreatedDateTime", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueCreatedDateTime *time.Time + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedBy", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueModifiedBy *string + obj.SetModifiedBy(fernTestValueModifiedBy) + assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedDateTime", func(t *testing.T) { + obj := &AuditInfo{} + var fernTestValueModifiedDateTime *time.Time + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersAuditInfo(t *testing.T) { + t.Run("GetCreatedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *string + obj.CreatedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") + }) + + t.Run("GetCreatedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.CreatedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedBy() // Should return zero value + }) + + t.Run("GetCreatedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *time.Time + obj.CreatedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") + }) + + t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.CreatedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedDateTime() // Should return zero value + }) + + t.Run("GetModifiedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *string + obj.ModifiedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") + }) + + t.Run("GetModifiedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.ModifiedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedBy() // Should return zero value + }) + + t.Run("GetModifiedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var expected *time.Time + obj.ModifiedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") + }) + + t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + obj.ModifiedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedDateTime() // Should return zero value + }) + +} + +func TestSettersMarkExplicitAuditInfo(t *testing.T) { + t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueCreatedBy *string + + // Act + obj.SetCreatedBy(fernTestValueCreatedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueCreatedDateTime *time.Time + + // Act + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueModifiedBy *string + + // Act + obj.SetModifiedBy(fernTestValueModifiedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + var fernTestValueModifiedDateTime *time.Time + + // Act + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersBaseOrg(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &BaseOrg{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetMetadata", func(t *testing.T) { + obj := &BaseOrg{} + var fernTestValueMetadata *BaseOrgMetadata + obj.SetMetadata(fernTestValueMetadata) + assert.Equal(t, fernTestValueMetadata, obj.Metadata) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersBaseOrg(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetMetadata", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var expected *BaseOrgMetadata + obj.Metadata = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") + }) + + t.Run("GetMetadata_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + obj.Metadata = nil + + // Act & Assert + assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") + }) + + t.Run("GetMetadata_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetMetadata() // Should return zero value + }) + +} + +func TestSettersMarkExplicitBaseOrg(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + var fernTestValueMetadata *BaseOrgMetadata + + // Act + obj.SetMetadata(fernTestValueMetadata) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersBaseOrgMetadata(t *testing.T) { + t.Run("SetRegion", func(t *testing.T) { + obj := &BaseOrgMetadata{} + var fernTestValueRegion string + obj.SetRegion(fernTestValueRegion) + assert.Equal(t, fernTestValueRegion, obj.Region) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetTier", func(t *testing.T) { + obj := &BaseOrgMetadata{} + var fernTestValueTier *string + obj.SetTier(fernTestValueTier) + assert.Equal(t, fernTestValueTier, obj.Tier) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersBaseOrgMetadata(t *testing.T) { + t.Run("GetRegion", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var expected string + obj.Region = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") + }) + + t.Run("GetRegion_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetRegion() // Should return zero value + }) + + t.Run("GetTier", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var expected *string + obj.Tier = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetTier(), "getter should return the property value") + }) + + t.Run("GetTier_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + obj.Tier = nil + + // Act & Assert + assert.Nil(t, obj.GetTier(), "getter should return nil when property is nil") + }) + + t.Run("GetTier_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetTier() // Should return zero value + }) + +} + +func TestSettersMarkExplicitBaseOrgMetadata(t *testing.T) { + t.Run("SetRegion_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var fernTestValueRegion string + + // Act + obj.SetRegion(fernTestValueRegion) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetTier_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + var fernTestValueTier *string + + // Act + obj.SetTier(fernTestValueTier) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersCombinedEntity(t *testing.T) { + t.Run("SetStatus", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueStatus CombinedEntityStatus + obj.SetStatus(fernTestValueStatus) + assert.Equal(t, fernTestValueStatus, obj.Status) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetID", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueName *string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetSummary", func(t *testing.T) { + obj := &CombinedEntity{} + var fernTestValueSummary *string + obj.SetSummary(fernTestValueSummary) + assert.Equal(t, fernTestValueSummary, obj.Summary) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersCombinedEntity(t *testing.T) { + t.Run("GetStatus", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected CombinedEntityStatus + obj.Status = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") + }) + + t.Run("GetStatus_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetStatus() // Should return zero value + }) + + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected *string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + obj.Name = nil + + // Act & Assert + assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetSummary", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var expected *string + obj.Summary = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") + }) + + t.Run("GetSummary_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + obj.Summary = nil + + // Act & Assert + assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") + }) + + t.Run("GetSummary_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetSummary() // Should return zero value + }) + +} + +func TestSettersMarkExplicitCombinedEntity(t *testing.T) { + t.Run("SetStatus_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueStatus CombinedEntityStatus + + // Act + obj.SetStatus(fernTestValueStatus) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueName *string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetSummary_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + var fernTestValueSummary *string + + // Act + obj.SetSummary(fernTestValueSummary) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersDescribable(t *testing.T) { + t.Run("SetName", func(t *testing.T) { + obj := &Describable{} + var fernTestValueName *string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetSummary", func(t *testing.T) { + obj := &Describable{} + var fernTestValueSummary *string + obj.SetSummary(fernTestValueSummary) + assert.Equal(t, fernTestValueSummary, obj.Summary) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersDescribable(t *testing.T) { + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var expected *string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + obj.Name = nil + + // Act & Assert + assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetSummary", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var expected *string + obj.Summary = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") + }) + + t.Run("GetSummary_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + obj.Summary = nil + + // Act & Assert + assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") + }) + + t.Run("GetSummary_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetSummary() // Should return zero value + }) + +} + +func TestSettersMarkExplicitDescribable(t *testing.T) { + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var fernTestValueName *string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetSummary_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + var fernTestValueSummary *string + + // Act + obj.SetSummary(fernTestValueSummary) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersDetailedOrg(t *testing.T) { + t.Run("SetMetadata", func(t *testing.T) { + obj := &DetailedOrg{} + var fernTestValueMetadata *DetailedOrgMetadata + obj.SetMetadata(fernTestValueMetadata) + assert.Equal(t, fernTestValueMetadata, obj.Metadata) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersDetailedOrg(t *testing.T) { + t.Run("GetMetadata", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + var expected *DetailedOrgMetadata + obj.Metadata = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") + }) + + t.Run("GetMetadata_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + obj.Metadata = nil + + // Act & Assert + assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") + }) + + t.Run("GetMetadata_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrg + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetMetadata() // Should return zero value + }) + +} + +func TestSettersMarkExplicitDetailedOrg(t *testing.T) { + t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + var fernTestValueMetadata *DetailedOrgMetadata + + // Act + obj.SetMetadata(fernTestValueMetadata) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersDetailedOrgMetadata(t *testing.T) { + t.Run("SetRegion", func(t *testing.T) { + obj := &DetailedOrgMetadata{} + var fernTestValueRegion string + obj.SetRegion(fernTestValueRegion) + assert.Equal(t, fernTestValueRegion, obj.Region) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetDomain", func(t *testing.T) { + obj := &DetailedOrgMetadata{} + var fernTestValueDomain *string + obj.SetDomain(fernTestValueDomain) + assert.Equal(t, fernTestValueDomain, obj.Domain) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersDetailedOrgMetadata(t *testing.T) { + t.Run("GetRegion", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var expected string + obj.Region = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") + }) + + t.Run("GetRegion_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetRegion() // Should return zero value + }) + + t.Run("GetDomain", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var expected *string + obj.Domain = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetDomain(), "getter should return the property value") + }) + + t.Run("GetDomain_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + obj.Domain = nil + + // Act & Assert + assert.Nil(t, obj.GetDomain(), "getter should return nil when property is nil") + }) + + t.Run("GetDomain_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetDomain() // Should return zero value + }) + +} + +func TestSettersMarkExplicitDetailedOrgMetadata(t *testing.T) { + t.Run("SetRegion_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var fernTestValueRegion string + + // Act + obj.SetRegion(fernTestValueRegion) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetDomain_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + var fernTestValueDomain *string + + // Act + obj.SetDomain(fernTestValueDomain) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersIdentifiable(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &Identifiable{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &Identifiable{} + var fernTestValueName *string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersIdentifiable(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var expected *string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + obj.Name = nil + + // Act & Assert + assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + +} + +func TestSettersMarkExplicitIdentifiable(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + var fernTestValueName *string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersOrganization(t *testing.T) { + t.Run("SetName", func(t *testing.T) { + obj := &Organization{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetID", func(t *testing.T) { + obj := &Organization{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetMetadata", func(t *testing.T) { + obj := &Organization{} + var fernTestValueMetadata *BaseOrgMetadata + obj.SetMetadata(fernTestValueMetadata) + assert.Equal(t, fernTestValueMetadata, obj.Metadata) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersOrganization(t *testing.T) { + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var expected string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetMetadata", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var expected *BaseOrgMetadata + obj.Metadata = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") + }) + + t.Run("GetMetadata_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + obj.Metadata = nil + + // Act & Assert + assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") + }) + + t.Run("GetMetadata_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetMetadata() // Should return zero value + }) + +} + +func TestSettersMarkExplicitOrganization(t *testing.T) { + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + var fernTestValueMetadata *BaseOrgMetadata + + // Act + obj.SetMetadata(fernTestValueMetadata) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersPaginatedResult(t *testing.T) { + t.Run("SetPaging", func(t *testing.T) { + obj := &PaginatedResult{} + var fernTestValuePaging *PagingCursors + obj.SetPaging(fernTestValuePaging) + assert.Equal(t, fernTestValuePaging, obj.Paging) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetResults", func(t *testing.T) { + obj := &PaginatedResult{} + var fernTestValueResults []any + obj.SetResults(fernTestValueResults) + assert.Equal(t, fernTestValueResults, obj.Results) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersPaginatedResult(t *testing.T) { + t.Run("GetPaging", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var expected *PagingCursors + obj.Paging = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") + }) + + t.Run("GetPaging_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + obj.Paging = nil + + // Act & Assert + assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") + }) + + t.Run("GetPaging_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPaging() // Should return zero value + }) + + t.Run("GetResults", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var expected []any + obj.Results = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") + }) + + t.Run("GetResults_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + obj.Results = nil + + // Act & Assert + assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") + }) + + t.Run("GetResults_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetResults() // Should return zero value + }) + +} + +func TestSettersMarkExplicitPaginatedResult(t *testing.T) { + t.Run("SetPaging_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var fernTestValuePaging *PagingCursors + + // Act + obj.SetPaging(fernTestValuePaging) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetResults_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + var fernTestValueResults []any + + // Act + obj.SetResults(fernTestValueResults) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersPagingCursors(t *testing.T) { + t.Run("SetNext", func(t *testing.T) { + obj := &PagingCursors{} + var fernTestValueNext string + obj.SetNext(fernTestValueNext) + assert.Equal(t, fernTestValueNext, obj.Next) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetPrevious", func(t *testing.T) { + obj := &PagingCursors{} + var fernTestValuePrevious *string + obj.SetPrevious(fernTestValuePrevious) + assert.Equal(t, fernTestValuePrevious, obj.Previous) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersPagingCursors(t *testing.T) { + t.Run("GetNext", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var expected string + obj.Next = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetNext(), "getter should return the property value") + }) + + t.Run("GetNext_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetNext() // Should return zero value + }) + + t.Run("GetPrevious", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var expected *string + obj.Previous = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPrevious(), "getter should return the property value") + }) + + t.Run("GetPrevious_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + obj.Previous = nil + + // Act & Assert + assert.Nil(t, obj.GetPrevious(), "getter should return nil when property is nil") + }) + + t.Run("GetPrevious_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPrevious() // Should return zero value + }) + +} + +func TestSettersMarkExplicitPagingCursors(t *testing.T) { + t.Run("SetNext_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var fernTestValueNext string + + // Act + obj.SetNext(fernTestValueNext) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetPrevious_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + var fernTestValuePrevious *string + + // Act + obj.SetPrevious(fernTestValuePrevious) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersRuleResponse(t *testing.T) { + t.Run("SetCreatedBy", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueCreatedBy *string + obj.SetCreatedBy(fernTestValueCreatedBy) + assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetCreatedDateTime", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueCreatedDateTime *time.Time + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedBy", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueModifiedBy *string + obj.SetModifiedBy(fernTestValueModifiedBy) + assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetModifiedDateTime", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueModifiedDateTime *time.Time + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetID", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetStatus", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueStatus RuleResponseStatus + obj.SetStatus(fernTestValueStatus) + assert.Equal(t, fernTestValueStatus, obj.Status) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetExecutionContext", func(t *testing.T) { + obj := &RuleResponse{} + var fernTestValueExecutionContext *RuleExecutionContext + obj.SetExecutionContext(fernTestValueExecutionContext) + assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersRuleResponse(t *testing.T) { + t.Run("GetCreatedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *string + obj.CreatedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") + }) + + t.Run("GetCreatedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.CreatedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedBy() // Should return zero value + }) + + t.Run("GetCreatedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *time.Time + obj.CreatedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") + }) + + t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.CreatedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetCreatedDateTime() // Should return zero value + }) + + t.Run("GetModifiedBy", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *string + obj.ModifiedBy = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") + }) + + t.Run("GetModifiedBy_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.ModifiedBy = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedBy() // Should return zero value + }) + + t.Run("GetModifiedDateTime", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *time.Time + obj.ModifiedDateTime = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") + }) + + t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.ModifiedDateTime = nil + + // Act & Assert + assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") + }) + + t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetModifiedDateTime() // Should return zero value + }) + + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetStatus", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected RuleResponseStatus + obj.Status = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") + }) + + t.Run("GetStatus_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetStatus() // Should return zero value + }) + + t.Run("GetExecutionContext", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var expected *RuleExecutionContext + obj.ExecutionContext = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetExecutionContext(), "getter should return the property value") + }) + + t.Run("GetExecutionContext_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + obj.ExecutionContext = nil + + // Act & Assert + assert.Nil(t, obj.GetExecutionContext(), "getter should return nil when property is nil") + }) + + t.Run("GetExecutionContext_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetExecutionContext() // Should return zero value + }) + +} + +func TestSettersMarkExplicitRuleResponse(t *testing.T) { + t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueCreatedBy *string + + // Act + obj.SetCreatedBy(fernTestValueCreatedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueCreatedDateTime *time.Time + + // Act + obj.SetCreatedDateTime(fernTestValueCreatedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueModifiedBy *string + + // Act + obj.SetModifiedBy(fernTestValueModifiedBy) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueModifiedDateTime *time.Time + + // Act + obj.SetModifiedDateTime(fernTestValueModifiedDateTime) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetStatus_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueStatus RuleResponseStatus + + // Act + obj.SetStatus(fernTestValueStatus) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + var fernTestValueExecutionContext *RuleExecutionContext + + // Act + obj.SetExecutionContext(fernTestValueExecutionContext) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersRuleType(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &RuleType{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetName", func(t *testing.T) { + obj := &RuleType{} + var fernTestValueName string + obj.SetName(fernTestValueName) + assert.Equal(t, fernTestValueName, obj.Name) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetDescription", func(t *testing.T) { + obj := &RuleType{} + var fernTestValueDescription *string + obj.SetDescription(fernTestValueDescription) + assert.Equal(t, fernTestValueDescription, obj.Description) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersRuleType(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetName", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var expected string + obj.Name = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetName(), "getter should return the property value") + }) + + t.Run("GetName_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetName() // Should return zero value + }) + + t.Run("GetDescription", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var expected *string + obj.Description = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetDescription(), "getter should return the property value") + }) + + t.Run("GetDescription_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + obj.Description = nil + + // Act & Assert + assert.Nil(t, obj.GetDescription(), "getter should return nil when property is nil") + }) + + t.Run("GetDescription_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetDescription() // Should return zero value + }) + +} + +func TestSettersMarkExplicitRuleType(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetName_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var fernTestValueName string + + // Act + obj.SetName(fernTestValueName) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetDescription_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + var fernTestValueDescription *string + + // Act + obj.SetDescription(fernTestValueDescription) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersRuleTypeSearchResponse(t *testing.T) { + t.Run("SetResults", func(t *testing.T) { + obj := &RuleTypeSearchResponse{} + var fernTestValueResults []*RuleType + obj.SetResults(fernTestValueResults) + assert.Equal(t, fernTestValueResults, obj.Results) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetPaging", func(t *testing.T) { + obj := &RuleTypeSearchResponse{} + var fernTestValuePaging *PagingCursors + obj.SetPaging(fernTestValuePaging) + assert.Equal(t, fernTestValuePaging, obj.Paging) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersRuleTypeSearchResponse(t *testing.T) { + t.Run("GetResults", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var expected []*RuleType + obj.Results = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") + }) + + t.Run("GetResults_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + obj.Results = nil + + // Act & Assert + assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") + }) + + t.Run("GetResults_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetResults() // Should return zero value + }) + + t.Run("GetPaging", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var expected *PagingCursors + obj.Paging = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") + }) + + t.Run("GetPaging_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + obj.Paging = nil + + // Act & Assert + assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") + }) + + t.Run("GetPaging_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPaging() // Should return zero value + }) + +} + +func TestSettersMarkExplicitRuleTypeSearchResponse(t *testing.T) { + t.Run("SetResults_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var fernTestValueResults []*RuleType + + // Act + obj.SetResults(fernTestValueResults) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetPaging_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + var fernTestValuePaging *PagingCursors + + // Act + obj.SetPaging(fernTestValuePaging) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersUser(t *testing.T) { + t.Run("SetID", func(t *testing.T) { + obj := &User{} + var fernTestValueID string + obj.SetID(fernTestValueID) + assert.Equal(t, fernTestValueID, obj.ID) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetEmail", func(t *testing.T) { + obj := &User{} + var fernTestValueEmail string + obj.SetEmail(fernTestValueEmail) + assert.Equal(t, fernTestValueEmail, obj.Email) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersUser(t *testing.T) { + t.Run("GetID", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var expected string + obj.ID = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetID(), "getter should return the property value") + }) + + t.Run("GetID_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetID() // Should return zero value + }) + + t.Run("GetEmail", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var expected string + obj.Email = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetEmail(), "getter should return the property value") + }) + + t.Run("GetEmail_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetEmail() // Should return zero value + }) + +} + +func TestSettersMarkExplicitUser(t *testing.T) { + t.Run("SetID_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var fernTestValueID string + + // Act + obj.SetID(fernTestValueID) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetEmail_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + var fernTestValueEmail string + + // Act + obj.SetEmail(fernTestValueEmail) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestSettersUserSearchResponse(t *testing.T) { + t.Run("SetResults", func(t *testing.T) { + obj := &UserSearchResponse{} + var fernTestValueResults []*User + obj.SetResults(fernTestValueResults) + assert.Equal(t, fernTestValueResults, obj.Results) + assert.NotNil(t, obj.explicitFields) + }) + + t.Run("SetPaging", func(t *testing.T) { + obj := &UserSearchResponse{} + var fernTestValuePaging *PagingCursors + obj.SetPaging(fernTestValuePaging) + assert.Equal(t, fernTestValuePaging, obj.Paging) + assert.NotNil(t, obj.explicitFields) + }) + +} + +func TestGettersUserSearchResponse(t *testing.T) { + t.Run("GetResults", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var expected []*User + obj.Results = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") + }) + + t.Run("GetResults_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + obj.Results = nil + + // Act & Assert + assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") + }) + + t.Run("GetResults_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetResults() // Should return zero value + }) + + t.Run("GetPaging", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var expected *PagingCursors + obj.Paging = expected + + // Act & Assert + assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") + }) + + t.Run("GetPaging_NilValue", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + obj.Paging = nil + + // Act & Assert + assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") + }) + + t.Run("GetPaging_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + // Should not panic - getters should handle nil receiver gracefully + defer func() { + if r := recover(); r != nil { + t.Errorf("Getter panicked on nil receiver: %v", r) + } + }() + _ = obj.GetPaging() // Should return zero value + }) + +} + +func TestSettersMarkExplicitUserSearchResponse(t *testing.T) { + t.Run("SetResults_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var fernTestValueResults []*User + + // Act + obj.SetResults(fernTestValueResults) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + + t.Run("SetPaging_MarksExplicit", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + var fernTestValuePaging *PagingCursors + + // Act + obj.SetPaging(fernTestValuePaging) + + // Assert - object with explicitly set field can be marshaled/unmarshaled + bytes, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed for test setup") + + // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value + // Detect if marshaled JSON is an object or primitive to use correct unmarshal target + if len(bytes) > 0 && bytes[0] == '{' { + // JSON object - unmarshal into map + var unmarshaled map[string]interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } else { + // JSON primitive (string, number, boolean, null) - unmarshal into interface{} + var unmarshaled interface{} + err = json.Unmarshal(bytes, &unmarshaled) + require.NoError(t, err, "unmarshaling should succeed for test verification") + } + + // Note: This does not explicitly assert the presence of a specific JSON field + // It verifies that setting a field via setter allows successful JSON round-trip + }) + +} + +func TestJSONMarshalingAuditInfo(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &AuditInfo{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled AuditInfo + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj AuditInfo + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj AuditInfo + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingBaseOrg(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrg{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled BaseOrg + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj BaseOrg + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj BaseOrg + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingBaseOrgMetadata(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &BaseOrgMetadata{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled BaseOrgMetadata + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj BaseOrgMetadata + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj BaseOrgMetadata + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingCombinedEntity(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &CombinedEntity{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled CombinedEntity + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj CombinedEntity + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj CombinedEntity + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingDescribable(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Describable{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled Describable + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj Describable + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj Describable + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingDetailedOrg(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrg{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled DetailedOrg + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj DetailedOrg + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj DetailedOrg + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingDetailedOrgMetadata(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &DetailedOrgMetadata{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled DetailedOrgMetadata + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj DetailedOrgMetadata + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj DetailedOrgMetadata + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingIdentifiable(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Identifiable{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled Identifiable + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj Identifiable + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj Identifiable + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingOrganization(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &Organization{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled Organization + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj Organization + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj Organization + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingPaginatedResult(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PaginatedResult{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled PaginatedResult + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj PaginatedResult + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj PaginatedResult + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingPagingCursors(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &PagingCursors{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled PagingCursors + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj PagingCursors + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj PagingCursors + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingRuleResponse(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleResponse{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled RuleResponse + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj RuleResponse + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj RuleResponse + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingRuleType(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleType{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled RuleType + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj RuleType + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj RuleType + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingRuleTypeSearchResponse(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &RuleTypeSearchResponse{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled RuleTypeSearchResponse + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj RuleTypeSearchResponse + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj RuleTypeSearchResponse + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingUser(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &User{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled User + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj User + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj User + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestJSONMarshalingUserSearchResponse(t *testing.T) { + t.Run("MarshalUnmarshal", func(t *testing.T) { + t.Parallel() + // Arrange + obj := &UserSearchResponse{} + + // Act - Marshal to JSON + data, err := json.Marshal(obj) + require.NoError(t, err, "marshaling should succeed") + assert.NotNil(t, data, "marshaled data should not be nil") + assert.NotEmpty(t, data, "marshaled data should not be empty") + + // Unmarshal back and verify round-trip + var unmarshaled UserSearchResponse + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err, "round-trip unmarshal should succeed") + }) + + t.Run("UnmarshalInvalidJSON", func(t *testing.T) { + t.Parallel() + var obj UserSearchResponse + err := json.Unmarshal([]byte(`{invalid json}`), &obj) + assert.Error(t, err, "unmarshaling invalid JSON should return an error") + }) + + t.Run("UnmarshalEmptyObject", func(t *testing.T) { + t.Parallel() + var obj UserSearchResponse + err := json.Unmarshal([]byte(`{}`), &obj) + assert.NoError(t, err, "unmarshaling empty object should succeed") + }) +} + +func TestStringAuditInfo(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &AuditInfo{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringBaseOrg(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &BaseOrg{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringBaseOrgMetadata(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &BaseOrgMetadata{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringCombinedEntity(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &CombinedEntity{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringDescribable(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &Describable{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringDetailedOrg(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrg{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrg + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringDetailedOrgMetadata(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrgMetadata{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringIdentifiable(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &Identifiable{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringOrganization(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &Organization{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringPaginatedResult(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &PaginatedResult{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringPagingCursors(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &PagingCursors{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringRuleResponse(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &RuleResponse{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringRuleType(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &RuleType{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringRuleTypeSearchResponse(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &RuleTypeSearchResponse{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringUser(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &User{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestStringUserSearchResponse(t *testing.T) { + t.Run("StringMethod", func(t *testing.T) { + t.Parallel() + obj := &UserSearchResponse{} + result := obj.String() + assert.NotEmpty(t, result, "String() should return a non-empty representation") + }) + + t.Run("StringMethod_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + result := obj.String() + assert.Equal(t, "", result, "String() should return for nil receiver") + }) +} + +func TestEnumCombinedEntityStatus(t *testing.T) { + t.Run("NewFromString_active", func(t *testing.T) { + t.Parallel() + val, err := NewCombinedEntityStatusFromString("active") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, CombinedEntityStatus("active"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_archived", func(t *testing.T) { + t.Parallel() + val, err := NewCombinedEntityStatusFromString("archived") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, CombinedEntityStatus("archived"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_Invalid", func(t *testing.T) { + _, err := NewCombinedEntityStatusFromString("invalid_value_that_does_not_exist") + assert.Error(t, err) + }) + + t.Run("Ptr", func(t *testing.T) { + val, err := NewCombinedEntityStatusFromString("active") + assert.NoError(t, err) + ptr := val.Ptr() + assert.NotNil(t, ptr) + assert.Equal(t, val, *ptr) + }) +} + +func TestEnumRuleExecutionContext(t *testing.T) { + t.Run("NewFromString_prod", func(t *testing.T) { + t.Parallel() + val, err := NewRuleExecutionContextFromString("prod") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleExecutionContext("prod"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_staging", func(t *testing.T) { + t.Parallel() + val, err := NewRuleExecutionContextFromString("staging") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleExecutionContext("staging"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_dev", func(t *testing.T) { + t.Parallel() + val, err := NewRuleExecutionContextFromString("dev") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleExecutionContext("dev"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_Invalid", func(t *testing.T) { + _, err := NewRuleExecutionContextFromString("invalid_value_that_does_not_exist") + assert.Error(t, err) + }) + + t.Run("Ptr", func(t *testing.T) { + val, err := NewRuleExecutionContextFromString("prod") + assert.NoError(t, err) + ptr := val.Ptr() + assert.NotNil(t, ptr) + assert.Equal(t, val, *ptr) + }) +} + +func TestEnumRuleResponseStatus(t *testing.T) { + t.Run("NewFromString_active", func(t *testing.T) { + t.Parallel() + val, err := NewRuleResponseStatusFromString("active") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleResponseStatus("active"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_inactive", func(t *testing.T) { + t.Parallel() + val, err := NewRuleResponseStatusFromString("inactive") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleResponseStatus("inactive"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_draft", func(t *testing.T) { + t.Parallel() + val, err := NewRuleResponseStatusFromString("draft") + assert.NoError(t, err, "valid enum value should not return error") + assert.Equal(t, RuleResponseStatus("draft"), val, "enum value should match expected wire value") + }) + + t.Run("NewFromString_Invalid", func(t *testing.T) { + _, err := NewRuleResponseStatusFromString("invalid_value_that_does_not_exist") + assert.Error(t, err) + }) + + t.Run("Ptr", func(t *testing.T) { + val, err := NewRuleResponseStatusFromString("active") + assert.NoError(t, err) + ptr := val.Ptr() + assert.NotNil(t, ptr) + assert.Equal(t, val, *ptr) + }) +} + +func TestExtraPropertiesAuditInfo(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &AuditInfo{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *AuditInfo + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesBaseOrg(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &BaseOrg{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrg + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesBaseOrgMetadata(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &BaseOrgMetadata{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *BaseOrgMetadata + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesCombinedEntity(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &CombinedEntity{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *CombinedEntity + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesDescribable(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &Describable{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Describable + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesDetailedOrg(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrg{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrg + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesDetailedOrgMetadata(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &DetailedOrgMetadata{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *DetailedOrgMetadata + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesIdentifiable(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &Identifiable{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Identifiable + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesOrganization(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &Organization{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *Organization + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesPaginatedResult(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &PaginatedResult{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PaginatedResult + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesPagingCursors(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &PagingCursors{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *PagingCursors + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesRuleResponse(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &RuleResponse{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleResponse + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesRuleType(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &RuleType{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleType + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesRuleTypeSearchResponse(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &RuleTypeSearchResponse{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *RuleTypeSearchResponse + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesUser(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &User{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *User + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} + +func TestExtraPropertiesUserSearchResponse(t *testing.T) { + t.Run("GetExtraProperties", func(t *testing.T) { + t.Parallel() + obj := &UserSearchResponse{} + // Should not panic when calling GetExtraProperties() + defer func() { + if r := recover(); r != nil { + t.Errorf("GetExtraProperties() panicked: %v", r) + } + }() + extraProps := obj.GetExtraProperties() + // Result can be nil or an empty/non-empty map + _ = extraProps + }) + + t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { + t.Parallel() + var obj *UserSearchResponse + extraProps := obj.GetExtraProperties() + assert.Nil(t, extraProps, "nil receiver should return nil without panicking") + }) +} diff --git a/seed/java-sdk/allof-inline/.fern/metadata.json b/seed/java-sdk/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..6077ef8b5f57 --- /dev/null +++ b/seed/java-sdk/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-java-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/.github/workflows/ci.yml b/seed/java-sdk/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..09c8c666ad73 --- /dev/null +++ b/seed/java-sdk/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/allof-inline/.gitignore b/seed/java-sdk/allof-inline/.gitignore new file mode 100644 index 000000000000..d4199abc2cd4 --- /dev/null +++ b/seed/java-sdk/allof-inline/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/README.md b/seed/java-sdk/allof-inline/README.md new file mode 100644 index 000000000000..de880cbffb2d --- /dev/null +++ b/seed/java-sdk/allof-inline/README.md @@ -0,0 +1,235 @@ +# Seed Java Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FJava) +[![Maven Central](https://img.shields.io/maven-central/v/com.fern/allof-inline)](https://central.sonatype.com/artifact/com.fern/allof-inline) + +The Seed Java library provides convenient access to the Seed APIs from Java. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Base Url](#base-url) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Custom Client](#custom-client) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom Headers](#custom-headers) + - [Access Raw Response Data](#access-raw-response-data) +- [Contributing](#contributing) + +## Installation + +### Gradle + +Add the dependency in your `build.gradle` file: + +```groovy +dependencies { + implementation 'com.fern:allof-inline:0.0.1' +} +``` + +### Maven + +Add the dependency in your `pom.xml` file: + +```xml + + com.fern + allof-inline + 0.0.1 + +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```java +package com.example.usage; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.types.RuleExecutionContext; + +public class Example { + public static void main(String[] args) { + SeedApiClient client = SeedApiClient + .builder() + .build(); + + client.createRule( + RuleCreateRequest + .builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build() + ); + } +} +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.Environment; + +SeedApiClient client = SeedApiClient + .builder() + .environment(Environment.Default) + .build(); +``` + +## Base Url + +You can set a custom base URL when constructing the client. + +```java +import com.seed.api.SeedApiClient; + +SeedApiClient client = SeedApiClient + .builder() + .url("https://example.com") + .build(); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), an API exception will be thrown. + +```java +import com.seed.api.core.SeedApiApiException; + +try{ + client.createRule(...); +} catch (SeedApiApiException e){ + // Do something with the API exception... +} +``` + +## Advanced + +### Custom Client + +This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one. +However, you can pass your own client like so: + +```java +import com.seed.api.SeedApiClient; +import okhttp3.OkHttpClient; + +OkHttpClient customClient = ...; + +SeedApiClient client = SeedApiClient + .builder() + .httpClient(customClient) + .build(); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). Before defaulting to exponential backoff, the SDK will first attempt to respect +the `Retry-After` header (as either in seconds or as an HTTP date), and then the `X-RateLimit-Reset` header +(as a Unix timestamp in epoch seconds); failing both of those, it will fall back to exponential backoff. + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` client option to configure this behavior. + +```java +import com.seed.api.SeedApiClient; + +SeedApiClient client = SeedApiClient + .builder() + .maxRetries(1) + .build(); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.RequestOptions; + +// Client level +SeedApiClient client = SeedApiClient + .builder() + .timeout(60) + .build(); + +// Request level +client.createRule( + ..., + RequestOptions + .builder() + .timeout(60) + .build() +); +``` + +### Custom Headers + +The SDK allows you to add custom headers to requests. You can configure headers at the client level or at the request level. + +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.RequestOptions; + +// Client level +SeedApiClient client = SeedApiClient + .builder() + .addHeader("X-Custom-Header", "custom-value") + .addHeader("X-Request-Id", "abc-123") + .build(); +; + +// Request level +client.createRule( + ..., + RequestOptions + .builder() + .addHeader("X-Request-Header", "request-value") + .build() +); +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `withRawResponse()` method. +The `withRawResponse()` method returns a raw client that wraps all responses with `body()` and `headers()` methods. +(A normal client's `response` is identical to a raw client's `response.body()`.) + +```java +SeedApiHttpResponse response = client.withRawResponse().createRule(...); + +System.out.println(response.body()); +System.out.println(response.headers().get("X-My-Header")); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/java-sdk/allof-inline/build.gradle b/seed/java-sdk/allof-inline/build.gradle new file mode 100644 index 000000000000..46dc64a52ca1 --- /dev/null +++ b/seed/java-sdk/allof-inline/build.gradle @@ -0,0 +1,102 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:5.2.1' + api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(Javadoc) { + failOnError false + options.addStringOption('Xdoclint:none', '-quiet') +} + +spotless { + java { + palantirJavaFormat() + } +} + + +java { + withSourcesJar() + withJavadocJar() +} + + +group = 'com.fern' + +version = '0.0.1' + +jar { + dependsOn(":generatePomFileForMavenPublication") + archiveBaseName = "allof-inline" +} + +sourcesJar { + archiveBaseName = "allof-inline" +} + +javadocJar { + archiveBaseName = "allof-inline" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'allof-inline' + version = '0.0.1' + from components.java + pom { + licenses { + license { + name = 'The MIT License (MIT)' + url = 'https://mit-license.org/' + } + } + scm { + connection = 'scm:git:git://github.com/allof-inline/fern.git' + developerConnection = 'scm:git:git://github.com/allof-inline/fern.git' + url = 'https://github.com/allof-inline/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/allof-inline/reference.md b/seed/java-sdk/allof-inline/reference.md new file mode 100644 index 000000000000..c37cf3e44a84 --- /dev/null +++ b/seed/java-sdk/allof-inline/reference.md @@ -0,0 +1,174 @@ +# Reference +
client.searchRuleTypes() -> RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.searchRuleTypes( + SearchRuleTypesRequest + .builder() + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `Optional` + +
+
+
+
+ + +
+
+
+ +
client.createRule(request) -> RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.createRule( + RuleCreateRequest + .builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `String` + +
+
+ +
+
+ +**executionContext:** `RuleExecutionContext` + +
+
+
+
+ + +
+
+
+ +
client.listUsers() -> UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.listUsers(); +``` +
+
+
+
+ + +
+
+
+ +
client.getEntity() -> CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.getEntity(); +``` +
+
+
+
+ + +
+
+
+ +
client.getOrganization() -> Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.getOrganization(); +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/java-sdk/allof-inline/sample-app/build.gradle b/seed/java-sdk/allof-inline/sample-app/build.gradle new file mode 100644 index 000000000000..4ee8f227b7af --- /dev/null +++ b/seed/java-sdk/allof-inline/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java b/seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java new file mode 100644 index 000000000000..8d293789008c --- /dev/null +++ b/seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.api.AsyncSeedApiClient + } +} diff --git a/seed/java-sdk/allof-inline/settings.gradle b/seed/java-sdk/allof-inline/settings.gradle new file mode 100644 index 000000000000..a22219aed8c4 --- /dev/null +++ b/seed/java-sdk/allof-inline/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'allof-inline' + +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/snippet.json b/seed/java-sdk/allof-inline/snippet.json new file mode 100644 index 000000000000..92723c195d93 --- /dev/null +++ b/seed/java-sdk/allof-inline/snippet.json @@ -0,0 +1,135 @@ +{ + "endpoints": [ + { + "example_identifier": "b6434d4c", + "id": { + "method": "GET", + "path": "/rule-types", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "bfaefd0c", + "id": { + "method": "GET", + "path": "/rule-types", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "c1cf878e", + "id": { + "method": "POST", + "path": "/rules", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "af661062", + "id": { + "method": "POST", + "path": "/rules", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "55942cbc", + "id": { + "method": "GET", + "path": "/users", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" + } + }, + { + "example_identifier": "2bf3205", + "id": { + "method": "GET", + "path": "/users", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" + } + }, + { + "example_identifier": "b2b07150", + "id": { + "method": "GET", + "path": "/entities", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" + } + }, + { + "example_identifier": "a351d465", + "id": { + "method": "GET", + "path": "/entities", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" + } + }, + { + "example_identifier": "dfb0bc71", + "id": { + "method": "GET", + "path": "/organizations", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" + } + }, + { + "example_identifier": "c30ff6be", + "id": { + "method": "GET", + "path": "/organizations", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java new file mode 100644 index 000000000000..046deb483a7f --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java @@ -0,0 +1,323 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.QueryStringMapper; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.core.SeedApiHttpResponse; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawSeedApiClient { + protected final ClientOptions clientOptions; + + public AsyncRawSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CompletableFuture> searchRuleTypes() { + return searchRuleTypes(SearchRuleTypesRequest.builder().build()); + } + + public CompletableFuture> searchRuleTypes( + RequestOptions requestOptions) { + return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); + } + + public CompletableFuture> searchRuleTypes( + SearchRuleTypesRequest request) { + return searchRuleTypes(request, null); + } + + public CompletableFuture> searchRuleTypes( + SearchRuleTypesRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rule-types"); + if (request.getQuery().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "query", request.getQuery().get(), false); + } + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), + response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> createRule(RuleCreateRequest request) { + return createRule(request, null); + } + + public CompletableFuture> createRule( + RuleCreateRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rules"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> listUsers() { + return listUsers(null); + } + + public CompletableFuture> listUsers(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("users"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), + response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> getEntity() { + return getEntity(null); + } + + public CompletableFuture> getEntity(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("entities"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), + response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> getOrganization() { + return getOrganization(null); + } + + public CompletableFuture> getOrganization(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("organizations"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java new file mode 100644 index 000000000000..45b759db04ed --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; +import java.util.concurrent.CompletableFuture; + +public class AsyncSeedApiClient { + protected final ClientOptions clientOptions; + + private final AsyncRawSeedApiClient rawClient; + + public AsyncSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawSeedApiClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawSeedApiClient withRawResponse() { + return this.rawClient; + } + + public CompletableFuture searchRuleTypes() { + return this.rawClient.searchRuleTypes().thenApply(response -> response.body()); + } + + public CompletableFuture searchRuleTypes(RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture searchRuleTypes(SearchRuleTypesRequest request) { + return this.rawClient.searchRuleTypes(request).thenApply(response -> response.body()); + } + + public CompletableFuture searchRuleTypes( + SearchRuleTypesRequest request, RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture createRule(RuleCreateRequest request) { + return this.rawClient.createRule(request).thenApply(response -> response.body()); + } + + public CompletableFuture createRule(RuleCreateRequest request, RequestOptions requestOptions) { + return this.rawClient.createRule(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture listUsers() { + return this.rawClient.listUsers().thenApply(response -> response.body()); + } + + public CompletableFuture listUsers(RequestOptions requestOptions) { + return this.rawClient.listUsers(requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture getEntity() { + return this.rawClient.getEntity().thenApply(response -> response.body()); + } + + public CompletableFuture getEntity(RequestOptions requestOptions) { + return this.rawClient.getEntity(requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture getOrganization() { + return this.rawClient.getOrganization().thenApply(response -> response.body()); + } + + public CompletableFuture getOrganization(RequestOptions requestOptions) { + return this.rawClient.getOrganization(requestOptions).thenApply(response -> response.body()); + } + + public static AsyncSeedApiClientBuilder builder() { + return new AsyncSeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java new file mode 100644 index 000000000000..bc66923d39bc --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java @@ -0,0 +1,194 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import com.seed.api.core.LogConfig; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class AsyncSeedApiClientBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment = Environment.DEFAULT; + + private OkHttpClient httpClient; + + private Optional logging = Optional.empty(); + + public AsyncSeedApiClientBuilder environment(Environment environment) { + this.environment = environment; + return this; + } + + public AsyncSeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public AsyncSeedApiClientBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public AsyncSeedApiClientBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public AsyncSeedApiClientBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public AsyncSeedApiClientBuilder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public AsyncSeedApiClientBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + setLogging(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Sets the logging configuration for the SDK. + * Override this method to customize logging behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setLogging(ClientOptions.Builder builder) { + if (this.logging.isPresent()) { + builder.logging(this.logging.get()); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public AsyncSeedApiClient build() { + validateConfiguration(); + return new AsyncSeedApiClient(buildClientOptions()); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java new file mode 100644 index 000000000000..f9a763a744c4 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java @@ -0,0 +1,249 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.QueryStringMapper; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.core.SeedApiHttpResponse; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class RawSeedApiClient { + protected final ClientOptions clientOptions; + + public RawSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public SeedApiHttpResponse searchRuleTypes() { + return searchRuleTypes(SearchRuleTypesRequest.builder().build()); + } + + public SeedApiHttpResponse searchRuleTypes(RequestOptions requestOptions) { + return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); + } + + public SeedApiHttpResponse searchRuleTypes(SearchRuleTypesRequest request) { + return searchRuleTypes(request, null); + } + + public SeedApiHttpResponse searchRuleTypes( + SearchRuleTypesRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rule-types"); + if (request.getQuery().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "query", request.getQuery().get(), false); + } + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), + response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse createRule(RuleCreateRequest request) { + return createRule(request, null); + } + + public SeedApiHttpResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rules"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse listUsers() { + return listUsers(null); + } + + public SeedApiHttpResponse listUsers(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("users"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse getEntity() { + return getEntity(null); + } + + public SeedApiHttpResponse getEntity(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("entities"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse getOrganization() { + return getOrganization(null); + } + + public SeedApiHttpResponse getOrganization(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("organizations"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java new file mode 100644 index 000000000000..73a73c0a87a2 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java @@ -0,0 +1,84 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; + +public class SeedApiClient { + protected final ClientOptions clientOptions; + + private final RawSeedApiClient rawClient; + + public SeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawSeedApiClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawSeedApiClient withRawResponse() { + return this.rawClient; + } + + public RuleTypeSearchResponse searchRuleTypes() { + return this.rawClient.searchRuleTypes().body(); + } + + public RuleTypeSearchResponse searchRuleTypes(RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(requestOptions).body(); + } + + public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request) { + return this.rawClient.searchRuleTypes(request).body(); + } + + public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request, RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(request, requestOptions).body(); + } + + public RuleResponse createRule(RuleCreateRequest request) { + return this.rawClient.createRule(request).body(); + } + + public RuleResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { + return this.rawClient.createRule(request, requestOptions).body(); + } + + public UserSearchResponse listUsers() { + return this.rawClient.listUsers().body(); + } + + public UserSearchResponse listUsers(RequestOptions requestOptions) { + return this.rawClient.listUsers(requestOptions).body(); + } + + public CombinedEntity getEntity() { + return this.rawClient.getEntity().body(); + } + + public CombinedEntity getEntity(RequestOptions requestOptions) { + return this.rawClient.getEntity(requestOptions).body(); + } + + public Organization getOrganization() { + return this.rawClient.getOrganization().body(); + } + + public Organization getOrganization(RequestOptions requestOptions) { + return this.rawClient.getOrganization(requestOptions).body(); + } + + public static SeedApiClientBuilder builder() { + return new SeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java new file mode 100644 index 000000000000..5f458e5b960a --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java @@ -0,0 +1,194 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import com.seed.api.core.LogConfig; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class SeedApiClientBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment = Environment.DEFAULT; + + private OkHttpClient httpClient; + + private Optional logging = Optional.empty(); + + public SeedApiClientBuilder environment(Environment environment) { + this.environment = environment; + return this; + } + + public SeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public SeedApiClientBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public SeedApiClientBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public SeedApiClientBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public SeedApiClientBuilder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public SeedApiClientBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + setLogging(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Sets the logging configuration for the SDK. + * Override this method to customize logging behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setLogging(ClientOptions.Builder builder) { + if (this.logging.isPresent()) { + builder.logging(this.logging.get()); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public SeedApiClient build() { + validateConfiguration(); + return new SeedApiClient(buildClientOptions()); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java new file mode 100644 index 000000000000..185b8126621c --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java @@ -0,0 +1,221 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final int timeout; + + private final int maxRetries; + + private final Optional logging; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout, + int maxRetries, + Optional logging) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("User-Agent", "com.fern:allof-inline/0.0.1"); + put("X-Fern-Language", "JAVA"); + put("X-Fern-SDK-Name", "com.seed.fern:api-sdk"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + this.maxRetries = maxRetries; + this.logging = logging; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public int maxRetries() { + return this.maxRetries; + } + + public Optional logging() { + return this.logging; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + private Optional logging = Optional.empty(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public Builder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor(this.maxRetries)); + } + + Logger logger = Logger.from(this.logging); + httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions( + environment, + headers, + headerSuppliers, + httpClient, + this.timeout.get(), + this.maxRetries, + this.logging); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + builder.headers.putAll(clientOptions.headers); + builder.headerSuppliers.putAll(clientOptions.headerSuppliers); + builder.maxRetries = clientOptions.maxRetries(); + builder.logging = clientOptions.logging(); + return builder; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java new file mode 100644 index 000000000000..0deabc0b79f7 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java @@ -0,0 +1,51 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.logging.Level; + +/** + * Default logger implementation that writes to the console using {@link java.util.logging.Logger}. + * + *

Uses the "fern" logger name with a simple format of "LEVEL - message". + */ +public final class ConsoleLogger implements ILogger { + + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger("fern"); + + static { + if (logger.getHandlers().length == 0) { + java.util.logging.ConsoleHandler handler = new java.util.logging.ConsoleHandler(); + handler.setFormatter(new java.util.logging.SimpleFormatter() { + @Override + public String format(java.util.logging.LogRecord record) { + return record.getLevel() + " - " + record.getMessage() + System.lineSeparator(); + } + }); + logger.addHandler(handler); + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + } + } + + @Override + public void debug(String message) { + logger.log(Level.FINE, message); + } + + @Override + public void info(String message) { + logger.log(Level.INFO, message); + } + + @Override + public void warn(String message) { + logger.log(Level.WARNING, message); + } + + @Override + public void error(String message) { + logger.log(Level.SEVERE, message); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 000000000000..eac7d50c71ae --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java new file mode 100644 index 000000000000..2390bfbe5690 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java @@ -0,0 +1,43 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; + +/** + * Custom serializer that writes integer-valued doubles without a decimal point. + * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. + * Non-integer values like {@code 3.14} are serialized normally. + */ +class DoubleSerializer extends JsonSerializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule() + .addSerializer(Double.class, new DoubleSerializer()) + .addSerializer(double.class, new DoubleSerializer()); + } + + /** + * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { + gen.writeNumber(value.longValue()); + } else { + gen.writeNumber(value); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java new file mode 100644 index 000000000000..c612dbc31093 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +public final class Environment { + public static final Environment DEFAULT = new Environment("https://api.example.com"); + + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java new file mode 100644 index 000000000000..a71e79469869 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java new file mode 100644 index 000000000000..866d4814dd5c --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java @@ -0,0 +1,38 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * Interface for custom logger implementations. + * + *

Implement this interface to provide a custom logging backend for the SDK. + * The SDK will call the appropriate method based on the log level. + * + *

Example: + *

{@code
+ * public class MyCustomLogger implements ILogger {
+ *     public void debug(String message) {
+ *         System.out.println("[DBG] " + message);
+ *     }
+ *     public void info(String message) {
+ *         System.out.println("[INF] " + message);
+ *     }
+ *     public void warn(String message) {
+ *         System.out.println("[WRN] " + message);
+ *     }
+ *     public void error(String message) {
+ *         System.out.println("[ERR] " + message);
+ *     }
+ * }
+ * }
+ */ +public interface ILogger { + void debug(String message); + + void info(String message); + + void warn(String message); + + void error(String message); +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java new file mode 100644 index 000000000000..a1e136889aaa --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java @@ -0,0 +1,74 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + try (Source source = Okio.source(inputStream)) { + sink.writeAll(source); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java new file mode 100644 index 000000000000..dd31bc7a6545 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java @@ -0,0 +1,98 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * Configuration for SDK logging. + * + *

Use the builder to configure logging behavior: + *

{@code
+ * LogConfig config = LogConfig.builder()
+ *     .level(LogLevel.DEBUG)
+ *     .silent(false)
+ *     .build();
+ * }
+ * + *

Or with a custom logger: + *

{@code
+ * LogConfig config = LogConfig.builder()
+ *     .level(LogLevel.DEBUG)
+ *     .logger(new MyCustomLogger())
+ *     .silent(false)
+ *     .build();
+ * }
+ * + *

Defaults: + *

    + *
  • {@code level} — {@link LogLevel#INFO}
  • + *
  • {@code logger} — {@link ConsoleLogger} (writes to stderr via java.util.logging)
  • + *
  • {@code silent} — {@code true} (no output unless explicitly enabled)
  • + *
+ */ +public final class LogConfig { + + private final LogLevel level; + private final ILogger logger; + private final boolean silent; + + private LogConfig(LogLevel level, ILogger logger, boolean silent) { + this.level = level; + this.logger = logger; + this.silent = silent; + } + + public LogLevel level() { + return level; + } + + public ILogger logger() { + return logger; + } + + public boolean silent() { + return silent; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private LogLevel level = LogLevel.INFO; + private ILogger logger = new ConsoleLogger(); + private boolean silent = true; + + private Builder() {} + + /** + * Set the minimum log level. Only messages at this level or above will be logged. + * Defaults to {@link LogLevel#INFO}. + */ + public Builder level(LogLevel level) { + this.level = level; + return this; + } + + /** + * Set a custom logger implementation. Defaults to {@link ConsoleLogger}. + */ + public Builder logger(ILogger logger) { + this.logger = logger; + return this; + } + + /** + * Set whether logging is silent (disabled). Defaults to {@code true}. + * Set to {@code false} to enable log output. + */ + public Builder silent(boolean silent) { + this.silent = silent; + return this; + } + + public LogConfig build() { + return new LogConfig(level, logger, silent); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java new file mode 100644 index 000000000000..2ef88f853eae --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * Log levels for SDK logging configuration. + * Silent by default — no log output unless explicitly configured. + */ +public enum LogLevel { + DEBUG(1), + INFO(2), + WARN(3), + ERROR(4); + + private final int value; + + LogLevel(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + /** + * Parse a log level from a string (case-insensitive). + * + * @param level the level string (debug, info, warn, error) + * @return the corresponding LogLevel + * @throws IllegalArgumentException if the string does not match any level + */ + public static LogLevel fromString(String level) { + return LogLevel.valueOf(level.toUpperCase()); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java new file mode 100644 index 000000000000..353f696fcf3f --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * SDK logger that filters messages based on level and silent mode. + * + *

Silent by default — no log output unless explicitly configured. + * Create via {@link LogConfig} or directly: + *

{@code
+ * Logger logger = new Logger(LogLevel.DEBUG, new ConsoleLogger(), false);
+ * logger.debug("request sent");
+ * }
+ */ +public final class Logger { + + private static final Logger DEFAULT = new Logger(LogLevel.INFO, new ConsoleLogger(), true); + + private final LogLevel level; + private final ILogger logger; + private final boolean silent; + + public Logger(LogLevel level, ILogger logger, boolean silent) { + this.level = level; + this.logger = logger; + this.silent = silent; + } + + /** + * Returns a default silent logger (no output). + */ + public static Logger getDefault() { + return DEFAULT; + } + + /** + * Creates a Logger from a {@link LogConfig}. If config is {@code null}, returns the default silent logger. + */ + public static Logger from(LogConfig config) { + if (config == null) { + return DEFAULT; + } + return new Logger(config.level(), config.logger(), config.silent()); + } + + /** + * Creates a Logger from an {@code Optional}. If empty, returns the default silent logger. + */ + public static Logger from(java.util.Optional config) { + return config.map(Logger::from).orElse(DEFAULT); + } + + private boolean shouldLog(LogLevel messageLevel) { + return !silent && level.getValue() <= messageLevel.getValue(); + } + + public boolean isDebug() { + return shouldLog(LogLevel.DEBUG); + } + + public boolean isInfo() { + return shouldLog(LogLevel.INFO); + } + + public boolean isWarn() { + return shouldLog(LogLevel.WARN); + } + + public boolean isError() { + return shouldLog(LogLevel.ERROR); + } + + public void debug(String message) { + if (isDebug()) { + logger.debug(message); + } + } + + public void info(String message) { + if (isInfo()) { + logger.info(message); + } + } + + public void warn(String message) { + if (isWarn()) { + logger.warn(message); + } + } + + public void error(String message) { + if (isError()) { + logger.error(message); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java new file mode 100644 index 000000000000..39a8eadeb905 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java @@ -0,0 +1,104 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * OkHttp interceptor that logs HTTP requests and responses. + * + *

Logs request method, URL, and headers (with sensitive values redacted) at debug level. + * Logs response status at debug level, and 4xx/5xx responses at error level. + * Does nothing if the logger is silent. + */ +public final class LoggingInterceptor implements Interceptor { + + private static final Set SENSITIVE_HEADERS = new HashSet<>(Arrays.asList( + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "proxy-authenticate", + "proxy-authorization", + "cookie", + "set-cookie", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token")); + + private final Logger logger; + + public LoggingInterceptor(Logger logger) { + this.logger = logger; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + if (logger.isDebug()) { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP Request: ").append(request.method()).append(" ").append(request.url()); + sb.append(" headers={"); + boolean first = true; + for (String name : request.headers().names()) { + if (!first) { + sb.append(", "); + } + sb.append(name).append("="); + if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { + sb.append("[REDACTED]"); + } else { + sb.append(request.header(name)); + } + first = false; + } + sb.append("}"); + sb.append(" has_body=").append(request.body() != null); + logger.debug(sb.toString()); + } + + Response response = chain.proceed(request); + + if (logger.isDebug()) { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP Response: status=").append(response.code()); + sb.append(" url=").append(response.request().url()); + sb.append(" headers={"); + boolean first = true; + for (String name : response.headers().names()) { + if (!first) { + sb.append(", "); + } + sb.append(name).append("="); + if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { + sb.append("[REDACTED]"); + } else { + sb.append(response.header(name)); + } + first = false; + } + sb.append("}"); + logger.debug(sb.toString()); + } + + if (response.code() >= 400 && logger.isError()) { + logger.error("HTTP Error: status=" + response.code() + " url=" + + response.request().url()); + } + + return response; + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java new file mode 100644 index 000000000000..4a8d1cf301d4 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java new file mode 100644 index 000000000000..ca50b2d8d50a --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; +import java.util.function.Function; + +public final class Nullable { + + private final Either, Null> value; + + private Nullable() { + this.value = Either.left(Optional.empty()); + } + + private Nullable(T value) { + if (value == null) { + this.value = Either.right(Null.INSTANCE); + } else { + this.value = Either.left(Optional.of(value)); + } + } + + public static Nullable ofNull() { + return new Nullable<>(null); + } + + public static Nullable of(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(); + } + + public static Nullable ofOptional(Optional value) { + if (value.isPresent()) { + return of(value.get()); + } else { + return empty(); + } + } + + public boolean isNull() { + return this.value.isRight(); + } + + public boolean isEmpty() { + return this.value.isLeft() && !this.value.getLeft().isPresent(); + } + + public T get() { + if (this.isNull()) { + return null; + } + + return this.value.getLeft().get(); + } + + public Nullable map(Function mapper) { + if (this.isNull()) { + return Nullable.ofNull(); + } + + return Nullable.ofOptional(this.value.getLeft().map(mapper)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Nullable)) { + return false; + } + + if (((Nullable) other).isNull() && this.isNull()) { + return true; + } + + return this.value.getLeft().equals(((Nullable) other).value.getLeft()); + } + + private static final class Either { + private L left = null; + private R right = null; + + private Either(L left, R right) { + if (left != null && right != null) { + throw new IllegalArgumentException("Left and right argument cannot both be non-null."); + } + + if (left == null && right == null) { + throw new IllegalArgumentException("Left and right argument cannot both be null."); + } + + if (left != null) { + this.left = left; + } + + if (right != null) { + this.right = right; + } + } + + public static Either left(L left) { + return new Either<>(left, null); + } + + public static Either right(R right) { + return new Either<>(null, right); + } + + public boolean isLeft() { + return this.left != null; + } + + public boolean isRight() { + return this.right != null; + } + + public L getLeft() { + if (!this.isLeft()) { + throw new IllegalArgumentException("Cannot get left from right Either."); + } + return this.left; + } + + public R getRight() { + if (!this.isRight()) { + throw new IllegalArgumentException("Cannot get right from left Either."); + } + return this.right; + } + } + + private static final class Null { + private static final Null INSTANCE = new Null(); + + private Null() {} + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java new file mode 100644 index 000000000000..dea5d181d48c --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; + +public final class NullableNonemptyFilter { + @Override + public boolean equals(Object o) { + boolean isOptionalEmpty = isOptionalEmpty(o); + + return isOptionalEmpty; + } + + private boolean isOptionalEmpty(Object o) { + if (o instanceof Optional) { + return !((Optional) o).isPresent(); + } + return false; + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 000000000000..2e443b07d910 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,46 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .addModule(DoubleSerializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + + public static Object parseErrorBody(String responseBodyString) { + try { + return JSON_MAPPER.readValue(responseBodyString, Object.class); + } catch (JsonProcessingException ignored) { + return responseBodyString; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java new file mode 100644 index 000000000000..3e364e6f3d5f --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java new file mode 100644 index 000000000000..cd95a27201b2 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Map headers; + + private final Map> headerSuppliers; + + private final Map queryParameters; + + private final Map> queryParameterSuppliers; + + private RequestOptions( + Optional timeout, + TimeUnit timeoutTimeUnit, + Map headers, + Map> headerSuppliers, + Map queryParameters, + Map> queryParameterSuppliers) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + this.queryParameters = queryParameters; + this.queryParameterSuppliers = queryParameterSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public Map getQueryParameters() { + Map queryParameters = new HashMap<>(this.queryParameters); + this.queryParameterSuppliers.forEach((key, supplier) -> { + queryParameters.put(key, supplier.get()); + }); + return queryParameters; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private final Map queryParameters = new HashMap<>(); + + private final Map> queryParameterSuppliers = new HashMap<>(); + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public Builder addQueryParameter(String key, String value) { + this.queryParameters.put(key, value); + return this; + } + + public Builder addQueryParameter(String key, Supplier value) { + this.queryParameterSuppliers.put(key, value); + return this; + } + + public RequestOptions build() { + return new RequestOptions( + timeout, timeoutTimeUnit, headers, headerSuppliers, queryParameters, queryParameterSuppliers); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java new file mode 100644 index 000000000000..db05d538255d --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java new file mode 100644 index 000000000000..97fcf7a0efbd --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java new file mode 100644 index 000000000000..0d331c152477 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java @@ -0,0 +1,181 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration INITIAL_RETRY_DELAY = Duration.ofMillis(1000); + private static final Duration MAX_RETRY_DELAY = Duration.ofMillis(60000); + private static final double JITTER_FACTOR = 0.2; + + private final int maxRetries; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.maxRetries = maxRetries; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries); + Optional nextBackoff = backoff.nextBackoff(response); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = backoff.nextBackoff(response); + } else { + return response; + } + } + + return response; + } + + /** + * Calculates the retry delay from response headers, with fallback to exponential backoff. + * Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff + */ + private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) { + // Check for Retry-After header first (RFC 7231), with no jitter + String retryAfter = response.header("Retry-After"); + if (retryAfter != null) { + // Parse as number of seconds... + Optional secondsDelay = tryParseLong(retryAfter) + .map(seconds -> seconds * 1000) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) + .map(Duration::ofMillis); + if (secondsDelay.isPresent()) { + return secondsDelay.get(); + } + + // ...or as an HTTP date; both are valid + Optional dateDelay = tryParseHttpDate(retryAfter) + .map(resetTime -> resetTime.toInstant().toEpochMilli() - System.currentTimeMillis()) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) + .map(Duration::ofMillis); + if (dateDelay.isPresent()) { + return dateDelay.get(); + } + } + + // Then check for industry-standard X-RateLimit-Reset header, with positive jitter + String rateLimitReset = response.header("X-RateLimit-Reset"); + if (rateLimitReset != null) { + // Assume Unix timestamp in epoch seconds + Optional rateLimitDelay = tryParseLong(rateLimitReset) + .map(resetTimeSeconds -> (resetTimeSeconds * 1000) - System.currentTimeMillis()) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) + .map(this::addPositiveJitter) + .map(Duration::ofMillis); + if (rateLimitDelay.isPresent()) { + return rateLimitDelay.get(); + } + } + + // Fall back to exponential backoff, with symmetric jitter + long baseDelay = INITIAL_RETRY_DELAY.toMillis() * (1L << retryAttempt); // 2^retryAttempt + long cappedDelay = Math.min(baseDelay, MAX_RETRY_DELAY.toMillis()); + return Duration.ofMillis(addSymmetricJitter(cappedDelay)); + } + + /** + * Attempts to parse a string as a long, returning empty Optional on failure. + */ + private Optional tryParseLong(String value) { + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(Long.parseLong(value)); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + /** + * Attempts to parse a string as an HTTP date (RFC 1123), returning empty Optional on failure. + */ + private Optional tryParseHttpDate(String value) { + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME)); + } catch (DateTimeParseException e) { + return Optional.empty(); + } + } + + /** + * Adds positive jitter (100-120% of original value) to prevent thundering herd. + * Used for X-RateLimit-Reset header delays. + */ + private long addPositiveJitter(long delayMs) { + double jitterMultiplier = 1.0 + (random.nextDouble() * JITTER_FACTOR); + return (long) (delayMs * jitterMultiplier); + } + + /** + * Adds symmetric jitter (90-110% of original value) to prevent thundering herd. + * Used for exponential backoff delays. + */ + private long addSymmetricJitter(long delayMs) { + double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * JITTER_FACTOR); + return (long) (delayMs * jitterMultiplier); + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff(Response response) { + if (retryNumber >= maxNumRetries) { + return Optional.empty(); + } + + Duration delay = getRetryDelayFromHeaders(response, retryNumber); + retryNumber += 1; + return Optional.of(delay); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java new file mode 100644 index 000000000000..268edd8c55be --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. + * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. + */ +public class Rfc2822DateTimeDeserializer extends JsonDeserializer { + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + String raw = parser.getValueAsString(); + return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java new file mode 100644 index 000000000000..ab4985c15f96 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java @@ -0,0 +1,73 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class SeedApiApiException extends SeedApiException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + private final Map> headers; + + public SeedApiApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public SeedApiApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @Override + public String toString() { + return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + + ObjectMappers.stringify(body) + "}"; + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java new file mode 100644 index 000000000000..cf8236066674 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class SeedApiException extends RuntimeException { + public SeedApiException(String message) { + super(message); + } + + public SeedApiException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java new file mode 100644 index 000000000000..814561a79e08 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +public final class SeedApiHttpResponse { + + private final T body; + + private final Map> headers; + + public SeedApiHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java new file mode 100644 index 000000000000..9775f2110865 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java @@ -0,0 +1,114 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; +import java.util.Optional; + +/** + * Represents a Server-Sent Event with all standard fields. + * Used for event-level discrimination where the discriminator is at the SSE envelope level. + * + * @param The type of the data field + */ +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonIgnoreProperties(ignoreUnknown = true) +public final class SseEvent { + private final String event; + private final T data; + private final String id; + private final Long retry; + + private SseEvent(String event, T data, String id, Long retry) { + this.event = event; + this.data = data; + this.id = id; + this.retry = retry; + } + + @JsonProperty("event") + public Optional getEvent() { + return Optional.ofNullable(event); + } + + @JsonProperty("data") + public T getData() { + return data; + } + + @JsonProperty("id") + public Optional getId() { + return Optional.ofNullable(id); + } + + @JsonProperty("retry") + public Optional getRetry() { + return Optional.ofNullable(retry); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SseEvent sseEvent = (SseEvent) o; + return Objects.equals(event, sseEvent.event) + && Objects.equals(data, sseEvent.data) + && Objects.equals(id, sseEvent.id) + && Objects.equals(retry, sseEvent.retry); + } + + @Override + public int hashCode() { + return Objects.hash(event, data, id, retry); + } + + @Override + public String toString() { + return "SseEvent{" + "event='" + + event + '\'' + ", data=" + + data + ", id='" + + id + '\'' + ", retry=" + + retry + '}'; + } + + public static Builder builder() { + return new Builder<>(); + } + + public static final class Builder { + private String event; + private T data; + private String id; + private Long retry; + + private Builder() {} + + public Builder event(String event) { + this.event = event; + return this; + } + + public Builder data(T data) { + this.data = data; + return this; + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder retry(Long retry) { + this.retry = retry; + return this; + } + + public SseEvent build() { + return new SseEvent<>(event, data, id, retry); + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java new file mode 100644 index 000000000000..b8a888d0dda0 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java @@ -0,0 +1,228 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.type.TypeReference; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Utility class for parsing Server-Sent Events with support for discriminated unions. + *

+ * Handles two discrimination patterns: + *

    + *
  1. Data-level discrimination: The discriminator (e.g., 'type') is inside the JSON data payload. + * Jackson's polymorphic deserialization handles this automatically.
  2. + *
  3. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE envelope level. + * This requires constructing the full SSE envelope for Jackson to process.
  4. + *
+ */ +public final class SseEventParser { + + private static final Set SSE_ENVELOPE_FIELDS = new HashSet<>(Arrays.asList("event", "data", "id", "retry")); + + private SseEventParser() { + // Utility class + } + + /** + * Parse an SSE event using event-level discrimination. + *

+ * Constructs the full SSE envelope object with event, data, id, and retry fields, + * then deserializes it to the target union type. + * + * @param eventType The SSE event type (from event: field) + * @param data The SSE data content (from data: field) + * @param id The SSE event ID (from id: field), may be null + * @param retry The SSE retry value (from retry: field), may be null + * @param unionClass The target union class + * @param discriminatorProperty The property name used for discrimination (e.g., "event") + * @param The target type + * @return The deserialized object + */ + public static T parseEventLevelUnion( + String eventType, String data, String id, Long retry, Class unionClass, String discriminatorProperty) { + try { + // Determine if data should be parsed as JSON based on the variant's expected type + Object parsedData = parseDataForVariant(eventType, data, unionClass, discriminatorProperty); + + // Construct the SSE envelope object + Map envelope = new HashMap<>(); + envelope.put(discriminatorProperty, eventType); + envelope.put("data", parsedData); + if (id != null) { + envelope.put("id", id); + } + if (retry != null) { + envelope.put("retry", retry); + } + + // Serialize to JSON and deserialize to target type + String envelopeJson = ObjectMappers.JSON_MAPPER.writeValueAsString(envelope); + return ObjectMappers.JSON_MAPPER.readValue(envelopeJson, unionClass); + } catch (Exception e) { + throw new RuntimeException("Failed to parse SSE event with event-level discrimination", e); + } + } + + /** + * Parse an SSE event using data-level discrimination. + *

+ * Simply parses the data field as JSON and deserializes it to the target type. + * Jackson's polymorphic deserialization handles the discrimination automatically. + * + * @param data The SSE data content (from data: field) + * @param valueType The target type + * @param The target type + * @return The deserialized object + */ + public static T parseDataLevelUnion(String data, Class valueType) { + try { + return ObjectMappers.JSON_MAPPER.readValue(data, valueType); + } catch (Exception e) { + throw new RuntimeException("Failed to parse SSE data with data-level discrimination", e); + } + } + + /** + * Determines if the given discriminator property indicates event-level discrimination. + * Event-level discrimination occurs when the discriminator is an SSE envelope field. + * + * @param discriminatorProperty The discriminator property name + * @return true if event-level discrimination, false otherwise + */ + public static boolean isEventLevelDiscrimination(String discriminatorProperty) { + return SSE_ENVELOPE_FIELDS.contains(discriminatorProperty); + } + + /** + * Attempts to find the discriminator property from the union class's Jackson annotations. + * + * @param unionClass The union class to inspect + * @return The discriminator property name, or empty if not found + */ + public static Optional findDiscriminatorProperty(Class unionClass) { + try { + // Look for JsonTypeInfo on the class itself + JsonTypeInfo typeInfo = unionClass.getAnnotation(JsonTypeInfo.class); + if (typeInfo != null && !typeInfo.property().isEmpty()) { + return Optional.of(typeInfo.property()); + } + + // Look for inner Value interface with JsonTypeInfo + for (Class innerClass : unionClass.getDeclaredClasses()) { + typeInfo = innerClass.getAnnotation(JsonTypeInfo.class); + if (typeInfo != null && !typeInfo.property().isEmpty()) { + return Optional.of(typeInfo.property()); + } + } + } catch (Exception e) { + // Ignore reflection errors + } + return Optional.empty(); + } + + /** + * Parse the data field based on what the matching variant expects. + * If the variant expects a String for its data field, returns the raw string. + * Otherwise, parses the data as JSON. + */ + private static Object parseDataForVariant( + String eventType, String data, Class unionClass, String discriminatorProperty) { + if (data == null || data.isEmpty()) { + return data; + } + + try { + // Try to find the variant class that matches this event type + Class variantClass = findVariantClass(unionClass, eventType, discriminatorProperty); + if (variantClass != null) { + // Check if the variant expects a String for the data field + Field dataField = findField(variantClass, "data"); + if (dataField != null && String.class.equals(dataField.getType())) { + // Variant expects String - return raw data + return data; + } + } + + // Try to parse as JSON + return ObjectMappers.JSON_MAPPER.readValue(data, new TypeReference>() {}); + } catch (Exception e) { + // If JSON parsing fails, return as string + return data; + } + } + + /** + * Find the variant class that matches the given discriminator value. + */ + private static Class findVariantClass( + Class unionClass, String discriminatorValue, String discriminatorProperty) { + try { + // Look for JsonSubTypes annotation + JsonSubTypes subTypes = findJsonSubTypes(unionClass); + if (subTypes == null) { + return null; + } + + for (JsonSubTypes.Type subType : subTypes.value()) { + JsonTypeName typeName = subType.value().getAnnotation(JsonTypeName.class); + if (typeName != null && typeName.value().equals(discriminatorValue)) { + return subType.value(); + } + // Also check the name attribute of @JsonSubTypes.Type + if (subType.name().equals(discriminatorValue)) { + return subType.value(); + } + } + } catch (Exception e) { + // Ignore reflection errors + } + return null; + } + + /** + * Find JsonSubTypes annotation on the class or its inner classes. + */ + private static JsonSubTypes findJsonSubTypes(Class unionClass) { + // Check the class itself + JsonSubTypes subTypes = unionClass.getAnnotation(JsonSubTypes.class); + if (subTypes != null) { + return subTypes; + } + + // Check inner classes (for Fern-style unions with inner Value interface) + for (Class innerClass : unionClass.getDeclaredClasses()) { + subTypes = innerClass.getAnnotation(JsonSubTypes.class); + if (subTypes != null) { + return subTypes; + } + } + return null; + } + + /** + * Find a field by name in a class, including private fields. + */ + private static Field findField(Class clazz, String fieldName) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + // Check superclass + Class superClass = clazz.getSuperclass(); + if (superClass != null && superClass != Object.class) { + return findField(superClass, fieldName); + } + return null; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java new file mode 100644 index 000000000000..b4350ef9a699 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java @@ -0,0 +1,513 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implements {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * Iterable stream for parsing JSON and Server-Sent Events (SSE) data. + * Supports both newline-delimited JSON and SSE with optional stream termination. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable, Closeable { + + private static final String NEWLINE = "\n"; + private static final String DATA_PREFIX = "data:"; + + public enum StreamType { + JSON, + SSE, + SSE_EVENT_DISCRIMINATED + } + + private final Class valueType; + private final Scanner scanner; + private final StreamType streamType; + private final String messageTerminator; + private final String streamTerminator; + private final Reader sseReader; + private final String discriminatorProperty; + private boolean isClosed = false; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.valueType = valueType; + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.streamType = StreamType.JSON; + this.messageTerminator = delimiter; + this.streamTerminator = null; + this.sseReader = null; + this.discriminatorProperty = null; + } + + private Stream(Class valueType, StreamType type, Reader reader, String terminator) { + this(valueType, type, reader, terminator, null); + } + + private Stream( + Class valueType, StreamType type, Reader reader, String terminator, String discriminatorProperty) { + this.valueType = valueType; + this.streamType = type; + this.discriminatorProperty = discriminatorProperty; + if (type == StreamType.JSON) { + this.scanner = new Scanner(reader).useDelimiter(terminator); + this.messageTerminator = terminator; + this.streamTerminator = null; + this.sseReader = null; + } else { + this.scanner = null; + this.messageTerminator = NEWLINE; + this.streamTerminator = terminator; + this.sseReader = reader; + } + } + + public static Stream fromJson(Class valueType, Reader reader, String delimiter) { + return new Stream<>(valueType, reader, delimiter); + } + + public static Stream fromJson(Class valueType, Reader reader) { + return new Stream<>(valueType, reader, NEWLINE); + } + + public static Stream fromSse(Class valueType, Reader sseReader) { + return new Stream<>(valueType, StreamType.SSE, sseReader, null); + } + + public static Stream fromSse(Class valueType, Reader sseReader, String streamTerminator) { + return new Stream<>(valueType, StreamType.SSE, sseReader, streamTerminator); + } + + /** + * Creates a stream from SSE data with event-level discrimination support. + * Use this when the SSE payload is a discriminated union where the discriminator + * is an SSE envelope field (e.g., 'event'). + * + * @param valueType The class of the objects in the stream. + * @param sseReader The reader that provides the SSE data. + * @param discriminatorProperty The property name used for discrimination (e.g., "event"). + * @param The type of objects in the stream. + * @return A new Stream instance configured for SSE with event-level discrimination. + */ + public static Stream fromSseWithEventDiscrimination( + Class valueType, Reader sseReader, String discriminatorProperty) { + return new Stream<>(valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, null, discriminatorProperty); + } + + /** + * Creates a stream from SSE data with event-level discrimination support and a stream terminator. + * + * @param valueType The class of the objects in the stream. + * @param sseReader The reader that provides the SSE data. + * @param discriminatorProperty The property name used for discrimination (e.g., "event"). + * @param streamTerminator The terminator string that signals end of stream (e.g., "[DONE]"). + * @param The type of objects in the stream. + * @return A new Stream instance configured for SSE with event-level discrimination. + */ + public static Stream fromSseWithEventDiscrimination( + Class valueType, Reader sseReader, String discriminatorProperty, String streamTerminator) { + return new Stream<>( + valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, streamTerminator, discriminatorProperty); + } + + @Override + public void close() throws IOException { + if (!isClosed) { + isClosed = true; + if (scanner != null) { + scanner.close(); + } + if (sseReader != null) { + sseReader.close(); + } + } + } + + private boolean isStreamClosed() { + return isClosed; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + switch (streamType) { + case SSE: + return new SSEIterator(); + case SSE_EVENT_DISCRIMINATED: + return new SSEEventDiscriminatedIterator(); + case JSON: + default: + return new JsonIterator(); + } + } + + private final class JsonIterator implements Iterator { + + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + if (isStreamClosed()) { + return false; + } + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (isStreamClosed()) { + throw new NoSuchElementException("Stream is closed"); + } + + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = + ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class SSEIterator implements Iterator { + private Scanner sseScanner; + private T nextItem; + private boolean hasNextItem = false; + private boolean endOfStream = false; + private StringBuilder eventDataBuffer = new StringBuilder(); + private String currentEventType = null; + + private SSEIterator() { + if (sseReader != null && !isStreamClosed()) { + this.sseScanner = new Scanner(sseReader); + } else { + this.endOfStream = true; + } + } + + @Override + public boolean hasNext() { + if (isStreamClosed() || endOfStream) { + return false; + } + + if (hasNextItem) { + return true; + } + + return readNextMessage(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements in stream"); + } + + T result = nextItem; + nextItem = null; + hasNextItem = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private boolean readNextMessage() { + if (sseScanner == null || isStreamClosed()) { + endOfStream = true; + return false; + } + + try { + while (sseScanner.hasNextLine()) { + String line = sseScanner.nextLine(); + + if (line.trim().isEmpty()) { + if (eventDataBuffer.length() > 0) { + try { + nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); + hasNextItem = true; + eventDataBuffer.setLength(0); + currentEventType = null; + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); + eventDataBuffer.setLength(0); + currentEventType = null; + continue; + } + } + continue; + } + + if (line.startsWith(DATA_PREFIX)) { + String dataContent = line.substring(DATA_PREFIX.length()); + if (dataContent.startsWith(" ")) { + dataContent = dataContent.substring(1); + } + + if (eventDataBuffer.length() == 0 + && streamTerminator != null + && dataContent.trim().equals(streamTerminator)) { + endOfStream = true; + return false; + } + + if (eventDataBuffer.length() > 0) { + eventDataBuffer.append('\n'); + } + eventDataBuffer.append(dataContent); + } else if (line.startsWith("event:")) { + String eventValue = line.length() > 6 ? line.substring(6) : ""; + if (eventValue.startsWith(" ")) { + eventValue = eventValue.substring(1); + } + currentEventType = eventValue; + } else if (line.startsWith("id:")) { + // Event ID field (ignored) + } else if (line.startsWith("retry:")) { + // Retry field (ignored) + } else if (line.startsWith(":")) { + // Comment line (ignored) + } + } + + if (eventDataBuffer.length() > 0) { + try { + nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); + hasNextItem = true; + eventDataBuffer.setLength(0); + currentEventType = null; + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); + eventDataBuffer.setLength(0); + currentEventType = null; + } + } + + endOfStream = true; + return false; + + } catch (Exception e) { + System.err.println("Failed to parse SSE stream: " + e.getMessage()); + endOfStream = true; + return false; + } + } + } + + /** + * Iterator for SSE streams with event-level discrimination. + * Uses SseEventParser to construct the full SSE envelope for Jackson deserialization. + */ + private final class SSEEventDiscriminatedIterator implements Iterator { + private Scanner sseScanner; + private T nextItem; + private boolean hasNextItem = false; + private boolean endOfStream = false; + private StringBuilder eventDataBuffer = new StringBuilder(); + private String currentEventType = null; + private String currentEventId = null; + private Long currentRetry = null; + + private SSEEventDiscriminatedIterator() { + if (sseReader != null && !isStreamClosed()) { + this.sseScanner = new Scanner(sseReader); + } else { + this.endOfStream = true; + } + } + + @Override + public boolean hasNext() { + if (isStreamClosed() || endOfStream) { + return false; + } + + if (hasNextItem) { + return true; + } + + return readNextMessage(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements in stream"); + } + + T result = nextItem; + nextItem = null; + hasNextItem = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private boolean readNextMessage() { + if (sseScanner == null || isStreamClosed()) { + endOfStream = true; + return false; + } + + try { + while (sseScanner.hasNextLine()) { + String line = sseScanner.nextLine(); + + if (line.trim().isEmpty()) { + if (eventDataBuffer.length() > 0 || currentEventType != null) { + try { + // Use SseEventParser for event-level discrimination + nextItem = SseEventParser.parseEventLevelUnion( + currentEventType, + eventDataBuffer.toString(), + currentEventId, + currentRetry, + valueType, + discriminatorProperty); + hasNextItem = true; + resetEventState(); + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); + resetEventState(); + continue; + } + } + continue; + } + + if (line.startsWith(DATA_PREFIX)) { + String dataContent = line.substring(DATA_PREFIX.length()); + if (dataContent.startsWith(" ")) { + dataContent = dataContent.substring(1); + } + + if (eventDataBuffer.length() == 0 + && streamTerminator != null + && dataContent.trim().equals(streamTerminator)) { + endOfStream = true; + return false; + } + + if (eventDataBuffer.length() > 0) { + eventDataBuffer.append('\n'); + } + eventDataBuffer.append(dataContent); + } else if (line.startsWith("event:")) { + String eventValue = line.length() > 6 ? line.substring(6) : ""; + if (eventValue.startsWith(" ")) { + eventValue = eventValue.substring(1); + } + currentEventType = eventValue; + } else if (line.startsWith("id:")) { + String idValue = line.length() > 3 ? line.substring(3) : ""; + if (idValue.startsWith(" ")) { + idValue = idValue.substring(1); + } + currentEventId = idValue; + } else if (line.startsWith("retry:")) { + String retryValue = line.length() > 6 ? line.substring(6) : ""; + if (retryValue.startsWith(" ")) { + retryValue = retryValue.substring(1); + } + try { + currentRetry = Long.parseLong(retryValue.trim()); + } catch (NumberFormatException e) { + // Ignore invalid retry values + } + } else if (line.startsWith(":")) { + // Comment line (ignored) + } + } + + // Handle any remaining buffered data at end of stream + if (eventDataBuffer.length() > 0 || currentEventType != null) { + try { + nextItem = SseEventParser.parseEventLevelUnion( + currentEventType, + eventDataBuffer.toString(), + currentEventId, + currentRetry, + valueType, + discriminatorProperty); + hasNextItem = true; + resetEventState(); + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); + resetEventState(); + } + } + + endOfStream = true; + return false; + + } catch (Exception e) { + System.err.println("Failed to parse SSE stream: " + e.getMessage()); + endOfStream = true; + return false; + } + } + + private void resetEventState() { + eventDataBuffer.setLength(0); + currentEventType = null; + currentEventId = null; + currentRetry = null; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java new file mode 100644 index 000000000000..a3c24e968576 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java new file mode 100644 index 000000000000..0f39ad2d3cf4 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import com.seed.api.types.RuleExecutionContext; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleCreateRequest.Builder.class) +public final class RuleCreateRequest { + private final String name; + + private final RuleExecutionContext executionContext; + + private final Map additionalProperties; + + private RuleCreateRequest( + String name, RuleExecutionContext executionContext, Map additionalProperties) { + this.name = name; + this.executionContext = executionContext; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("executionContext") + public RuleExecutionContext getExecutionContext() { + return executionContext; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleCreateRequest && equalTo((RuleCreateRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleCreateRequest other) { + return name.equals(other.name) && executionContext.equals(other.executionContext); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.executionContext); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NameStage builder() { + return new Builder(); + } + + public interface NameStage { + ExecutionContextStage name(@NotNull String name); + + Builder from(RuleCreateRequest other); + } + + public interface ExecutionContextStage { + _FinalStage executionContext(@NotNull RuleExecutionContext executionContext); + } + + public interface _FinalStage { + RuleCreateRequest build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements NameStage, ExecutionContextStage, _FinalStage { + private String name; + + private RuleExecutionContext executionContext; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleCreateRequest other) { + name(other.getName()); + executionContext(other.getExecutionContext()); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public ExecutionContextStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("executionContext") + public _FinalStage executionContext(@NotNull RuleExecutionContext executionContext) { + this.executionContext = Objects.requireNonNull(executionContext, "executionContext must not be null"); + return this; + } + + @java.lang.Override + public RuleCreateRequest build() { + return new RuleCreateRequest(name, executionContext, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java new file mode 100644 index 000000000000..80659000defc --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java @@ -0,0 +1,105 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = SearchRuleTypesRequest.Builder.class) +public final class SearchRuleTypesRequest { + private final Optional query; + + private final Map additionalProperties; + + private SearchRuleTypesRequest(Optional query, Map additionalProperties) { + this.query = query; + this.additionalProperties = additionalProperties; + } + + @JsonIgnore + public Optional getQuery() { + return query; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof SearchRuleTypesRequest && equalTo((SearchRuleTypesRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(SearchRuleTypesRequest other) { + return query.equals(other.query); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.query); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional query = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(SearchRuleTypesRequest other) { + query(other.getQuery()); + return this; + } + + @JsonSetter(value = "query", nulls = Nulls.SKIP) + public Builder query(Optional query) { + this.query = query; + return this; + } + + public Builder query(String query) { + this.query = Optional.ofNullable(query); + return this; + } + + public SearchRuleTypesRequest build() { + return new SearchRuleTypesRequest(query, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java new file mode 100644 index 000000000000..472220038b70 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java @@ -0,0 +1,204 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AuditInfo.Builder.class) +public final class AuditInfo { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private final Map additionalProperties; + + private AuditInfo( + Optional createdBy, + Optional createdDateTime, + Optional modifiedBy, + Optional modifiedDateTime, + Map additionalProperties) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + this.additionalProperties = additionalProperties; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AuditInfo && equalTo((AuditInfo) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(AuditInfo other) { + return createdBy.equals(other.createdBy) + && createdDateTime.equals(other.createdDateTime) + && modifiedBy.equals(other.modifiedBy) + && modifiedDateTime.equals(other.modifiedDateTime); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional createdBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AuditInfo other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + return this; + } + + /** + *

The user who created this resource.

+ */ + @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) + public Builder createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + public Builder createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

When this resource was created.

+ */ + @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) + public Builder createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + public Builder createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) + public Builder modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + public Builder modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) + public Builder modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + public AuditInfo build() { + return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java new file mode 100644 index 000000000000..2ff2e67bb3b4 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java @@ -0,0 +1,148 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = BaseOrg.Builder.class) +public final class BaseOrg { + private final String id; + + private final Optional metadata; + + private final Map additionalProperties; + + private BaseOrg(String id, Optional metadata, Map additionalProperties) { + this.id = id; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrg && equalTo((BaseOrg) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(BaseOrg other) { + return id.equals(other.id) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(@NotNull String id); + + Builder from(BaseOrg other); + } + + public interface _FinalStage { + BaseOrg build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(BaseOrgMetadata metadata); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(BaseOrg other) { + id(other.getId()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(BaseOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public BaseOrg build() { + return new BaseOrg(id, metadata, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java new file mode 100644 index 000000000000..a444b87ccc24 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = BaseOrgMetadata.Builder.class) +public final class BaseOrgMetadata { + private final String region; + + private final Optional tier; + + private final Map additionalProperties; + + private BaseOrgMetadata(String region, Optional tier, Map additionalProperties) { + this.region = region; + this.tier = tier; + this.additionalProperties = additionalProperties; + } + + /** + * @return Deployment region from BaseOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Subscription tier. + */ + @JsonProperty("tier") + public Optional getTier() { + return tier; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(BaseOrgMetadata other) { + return region.equals(other.region) && tier.equals(other.tier); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.tier); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from BaseOrg.

+ */ + _FinalStage region(@NotNull String region); + + Builder from(BaseOrgMetadata other); + } + + public interface _FinalStage { + BaseOrgMetadata build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Subscription tier.

+ */ + _FinalStage tier(Optional tier); + + _FinalStage tier(String tier); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional tier = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(BaseOrgMetadata other) { + region(other.getRegion()); + tier(other.getTier()); + return this; + } + + /** + *

Deployment region from BaseOrg.

+ *

Deployment region from BaseOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(@NotNull String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Subscription tier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage tier(String tier) { + this.tier = Optional.ofNullable(tier); + return this; + } + + /** + *

Subscription tier.

+ */ + @java.lang.Override + @JsonSetter(value = "tier", nulls = Nulls.SKIP) + public _FinalStage tier(Optional tier) { + this.tier = tier; + return this; + } + + @java.lang.Override + public BaseOrgMetadata build() { + return new BaseOrgMetadata(region, tier, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java new file mode 100644 index 000000000000..c10996083da5 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java @@ -0,0 +1,243 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CombinedEntity.Builder.class) +public final class CombinedEntity { + private final String id; + + private final Optional name; + + private final Optional summary; + + private final CombinedEntityStatus status; + + private final Map additionalProperties; + + private CombinedEntity( + String id, + Optional name, + Optional summary, + CombinedEntityStatus status, + Map additionalProperties) { + this.id = id; + this.name = name; + this.summary = summary; + this.status = status; + this.additionalProperties = additionalProperties; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Describable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @JsonProperty("status") + public CombinedEntityStatus getStatus() { + return status; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CombinedEntity && equalTo((CombinedEntity) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(CombinedEntity other) { + return id.equals(other.id) + && name.equals(other.name) + && summary.equals(other.summary) + && status.equals(other.status); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.summary, this.status); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + StatusStage id(@NotNull String id); + + Builder from(CombinedEntity other); + } + + public interface StatusStage { + _FinalStage status(@NotNull CombinedEntityStatus status); + } + + public interface _FinalStage { + CombinedEntity build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Display name from Describable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + + /** + *

A short summary.

+ */ + _FinalStage summary(Optional summary); + + _FinalStage summary(String summary); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, StatusStage, _FinalStage { + private String id; + + private CombinedEntityStatus status; + + private Optional summary = Optional.empty(); + + private Optional name = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(CombinedEntity other) { + id(other.getId()); + name(other.getName()); + summary(other.getSummary()); + status(other.getStatus()); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public StatusStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public _FinalStage status(@NotNull CombinedEntityStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + /** + *

A short summary.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + /** + *

A short summary.

+ */ + @java.lang.Override + @JsonSetter(value = "summary", nulls = Nulls.SKIP) + public _FinalStage summary(Optional summary) { + this.summary = summary; + return this; + } + + /** + *

Display name from Describable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Describable.

+ */ + @java.lang.Override + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public CombinedEntity build() { + return new CombinedEntity(id, name, summary, status, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java new file mode 100644 index 000000000000..86e82fb63f85 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java @@ -0,0 +1,83 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class CombinedEntityStatus { + public static final CombinedEntityStatus ARCHIVED = new CombinedEntityStatus(Value.ARCHIVED, "archived"); + + public static final CombinedEntityStatus ACTIVE = new CombinedEntityStatus(Value.ACTIVE, "active"); + + private final Value value; + + private final String string; + + CombinedEntityStatus(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof CombinedEntityStatus && this.string.equals(((CombinedEntityStatus) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case ARCHIVED: + return visitor.visitArchived(); + case ACTIVE: + return visitor.visitActive(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static CombinedEntityStatus valueOf(String value) { + switch (value) { + case "archived": + return ARCHIVED; + case "active": + return ACTIVE; + default: + return new CombinedEntityStatus(Value.UNKNOWN, value); + } + } + + public enum Value { + ACTIVE, + + ARCHIVED, + + UNKNOWN + } + + public interface Visitor { + T visitActive(); + + T visitArchived(); + + T visitUnknown(String unknownType); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java new file mode 100644 index 000000000000..b16e91712b0e --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java @@ -0,0 +1,139 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Describable.Builder.class) +public final class Describable { + private final Optional name; + + private final Optional summary; + + private final Map additionalProperties; + + private Describable(Optional name, Optional summary, Map additionalProperties) { + this.name = name; + this.summary = summary; + this.additionalProperties = additionalProperties; + } + + /** + * @return Display name from Describable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Describable && equalTo((Describable) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Describable other) { + return name.equals(other.name) && summary.equals(other.summary); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.summary); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional name = Optional.empty(); + + private Optional summary = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(Describable other) { + name(other.getName()); + summary(other.getSummary()); + return this; + } + + /** + *

Display name from Describable.

+ */ + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

A short summary.

+ */ + @JsonSetter(value = "summary", nulls = Nulls.SKIP) + public Builder summary(Optional summary) { + this.summary = summary; + return this; + } + + public Builder summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + public Describable build() { + return new Describable(name, summary, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java new file mode 100644 index 000000000000..ef605e7427da --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java @@ -0,0 +1,105 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetailedOrg.Builder.class) +public final class DetailedOrg { + private final Optional metadata; + + private final Map additionalProperties; + + private DetailedOrg(Optional metadata, Map additionalProperties) { + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrg && equalTo((DetailedOrg) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetailedOrg other) { + return metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DetailedOrg other) { + metadata(other.getMetadata()); + return this; + } + + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public Builder metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(DetailedOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public DetailedOrg build() { + return new DetailedOrg(metadata, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java new file mode 100644 index 000000000000..e65ef4975e85 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetailedOrgMetadata.Builder.class) +public final class DetailedOrgMetadata { + private final String region; + + private final Optional domain; + + private final Map additionalProperties; + + private DetailedOrgMetadata(String region, Optional domain, Map additionalProperties) { + this.region = region; + this.domain = domain; + this.additionalProperties = additionalProperties; + } + + /** + * @return Deployment region from DetailedOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Custom domain name. + */ + @JsonProperty("domain") + public Optional getDomain() { + return domain; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetailedOrgMetadata other) { + return region.equals(other.region) && domain.equals(other.domain); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.domain); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from DetailedOrg.

+ */ + _FinalStage region(@NotNull String region); + + Builder from(DetailedOrgMetadata other); + } + + public interface _FinalStage { + DetailedOrgMetadata build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Custom domain name.

+ */ + _FinalStage domain(Optional domain); + + _FinalStage domain(String domain); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional domain = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(DetailedOrgMetadata other) { + region(other.getRegion()); + domain(other.getDomain()); + return this; + } + + /** + *

Deployment region from DetailedOrg.

+ *

Deployment region from DetailedOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(@NotNull String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Custom domain name.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage domain(String domain) { + this.domain = Optional.ofNullable(domain); + return this; + } + + /** + *

Custom domain name.

+ */ + @java.lang.Override + @JsonSetter(value = "domain", nulls = Nulls.SKIP) + public _FinalStage domain(Optional domain) { + this.domain = domain; + return this; + } + + @java.lang.Override + public DetailedOrgMetadata build() { + return new DetailedOrgMetadata(region, domain, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java new file mode 100644 index 000000000000..8a35f505a9bf --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Identifiable.Builder.class) +public final class Identifiable { + private final String id; + + private final Optional name; + + private final Map additionalProperties; + + private Identifiable(String id, Optional name, Map additionalProperties) { + this.id = id; + this.name = name; + this.additionalProperties = additionalProperties; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Identifiable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Identifiable && equalTo((Identifiable) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Identifiable other) { + return id.equals(other.id) && name.equals(other.name); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + _FinalStage id(@NotNull String id); + + Builder from(Identifiable other); + } + + public interface _FinalStage { + Identifiable build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Display name from Identifiable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional name = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(Identifiable other) { + id(other.getId()); + name(other.getName()); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + /** + *

Display name from Identifiable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Identifiable.

+ */ + @java.lang.Override + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public Identifiable build() { + return new Identifiable(id, name, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java new file mode 100644 index 000000000000..2d890a4bdb53 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java @@ -0,0 +1,171 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Organization.Builder.class) +public final class Organization { + private final String id; + + private final Optional metadata; + + private final String name; + + private final Map additionalProperties; + + private Organization( + String id, Optional metadata, String name, Map additionalProperties) { + this.id = id; + this.metadata = metadata; + this.name = name; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Organization && equalTo((Organization) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Organization other) { + return id.equals(other.id) && metadata.equals(other.metadata) && name.equals(other.name); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.metadata, this.name); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(@NotNull String id); + + Builder from(Organization other); + } + + public interface NameStage { + _FinalStage name(@NotNull String name); + } + + public interface _FinalStage { + Organization build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(OrganizationMetadata metadata); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, NameStage, _FinalStage { + private String id; + + private String name; + + private Optional metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(Organization other) { + id(other.getId()); + metadata(other.getMetadata()); + name(other.getName()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(OrganizationMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public Organization build() { + return new Organization(id, metadata, name, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java new file mode 100644 index 000000000000..c1665b373e03 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = OrganizationMetadata.Builder.class) +public final class OrganizationMetadata { + private final String region; + + private final Optional domain; + + private final Map additionalProperties; + + private OrganizationMetadata(String region, Optional domain, Map additionalProperties) { + this.region = region; + this.domain = domain; + this.additionalProperties = additionalProperties; + } + + /** + * @return Deployment region from DetailedOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Custom domain name. + */ + @JsonProperty("domain") + public Optional getDomain() { + return domain; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof OrganizationMetadata && equalTo((OrganizationMetadata) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(OrganizationMetadata other) { + return region.equals(other.region) && domain.equals(other.domain); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.domain); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from DetailedOrg.

+ */ + _FinalStage region(@NotNull String region); + + Builder from(OrganizationMetadata other); + } + + public interface _FinalStage { + OrganizationMetadata build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Custom domain name.

+ */ + _FinalStage domain(Optional domain); + + _FinalStage domain(String domain); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional domain = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(OrganizationMetadata other) { + region(other.getRegion()); + domain(other.getDomain()); + return this; + } + + /** + *

Deployment region from DetailedOrg.

+ *

Deployment region from DetailedOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(@NotNull String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Custom domain name.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage domain(String domain) { + this.domain = Optional.ofNullable(domain); + return this; + } + + /** + *

Custom domain name.

+ */ + @java.lang.Override + @JsonSetter(value = "domain", nulls = Nulls.SKIP) + public _FinalStage domain(Optional domain) { + this.domain = domain; + return this; + } + + @java.lang.Override + public OrganizationMetadata build() { + return new OrganizationMetadata(region, domain, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java new file mode 100644 index 000000000000..2994a6d8c2fd --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java @@ -0,0 +1,179 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = PaginatedResult.Builder.class) +public final class PaginatedResult { + private final PagingCursors paging; + + private final List results; + + private final Map additionalProperties; + + private PaginatedResult(PagingCursors paging, List results, Map additionalProperties) { + this.paging = paging; + this.results = results; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public List getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PaginatedResult && equalTo((PaginatedResult) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(PaginatedResult other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(@NotNull PagingCursors paging); + + Builder from(PaginatedResult other); + } + + public interface _FinalStage { + PaginatedResult build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(List results); + + _FinalStage addResults(Object results); + + _FinalStage addAllResults(List results); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private List results = new ArrayList<>(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(PaginatedResult other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(@NotNull PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addAllResults(List results) { + if (results != null) { + this.results.addAll(results); + } + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addResults(Object results) { + this.results.add(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter(value = "results", nulls = Nulls.SKIP) + public _FinalStage results(List results) { + this.results.clear(); + if (results != null) { + this.results.addAll(results); + } + return this; + } + + @java.lang.Override + public PaginatedResult build() { + return new PaginatedResult(paging, results, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java new file mode 100644 index 000000000000..5453abc20ef3 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = PagingCursors.Builder.class) +public final class PagingCursors { + private final String next; + + private final Optional previous; + + private final Map additionalProperties; + + private PagingCursors(String next, Optional previous, Map additionalProperties) { + this.next = next; + this.previous = previous; + this.additionalProperties = additionalProperties; + } + + /** + * @return Cursor for the next page of results. + */ + @JsonProperty("next") + public String getNext() { + return next; + } + + /** + * @return Cursor for the previous page of results. + */ + @JsonProperty("previous") + public Optional getPrevious() { + return previous; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PagingCursors && equalTo((PagingCursors) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(PagingCursors other) { + return next.equals(other.next) && previous.equals(other.previous); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.next, this.previous); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NextStage builder() { + return new Builder(); + } + + public interface NextStage { + /** + *

Cursor for the next page of results.

+ */ + _FinalStage next(@NotNull String next); + + Builder from(PagingCursors other); + } + + public interface _FinalStage { + PagingCursors build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Cursor for the previous page of results.

+ */ + _FinalStage previous(Optional previous); + + _FinalStage previous(String previous); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements NextStage, _FinalStage { + private String next; + + private Optional previous = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(PagingCursors other) { + next(other.getNext()); + previous(other.getPrevious()); + return this; + } + + /** + *

Cursor for the next page of results.

+ *

Cursor for the next page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("next") + public _FinalStage next(@NotNull String next) { + this.next = Objects.requireNonNull(next, "next must not be null"); + return this; + } + + /** + *

Cursor for the previous page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage previous(String previous) { + this.previous = Optional.ofNullable(previous); + return this; + } + + /** + *

Cursor for the previous page of results.

+ */ + @java.lang.Override + @JsonSetter(value = "previous", nulls = Nulls.SKIP) + public _FinalStage previous(Optional previous) { + this.previous = previous; + return this; + } + + @java.lang.Override + public PagingCursors build() { + return new PagingCursors(next, previous, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java new file mode 100644 index 000000000000..46c429ed089e --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class RuleExecutionContext { + public static final RuleExecutionContext PROD = new RuleExecutionContext(Value.PROD, "prod"); + + public static final RuleExecutionContext DEV = new RuleExecutionContext(Value.DEV, "dev"); + + public static final RuleExecutionContext STAGING = new RuleExecutionContext(Value.STAGING, "staging"); + + private final Value value; + + private final String string; + + RuleExecutionContext(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof RuleExecutionContext && this.string.equals(((RuleExecutionContext) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case PROD: + return visitor.visitProd(); + case DEV: + return visitor.visitDev(); + case STAGING: + return visitor.visitStaging(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static RuleExecutionContext valueOf(String value) { + switch (value) { + case "prod": + return PROD; + case "dev": + return DEV; + case "staging": + return STAGING; + default: + return new RuleExecutionContext(Value.UNKNOWN, value); + } + } + + public enum Value { + PROD, + + STAGING, + + DEV, + + UNKNOWN + } + + public interface Visitor { + T visitProd(); + + T visitStaging(); + + T visitDev(); + + T visitUnknown(String unknownType); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java new file mode 100644 index 000000000000..1cda0fe9805e --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java @@ -0,0 +1,390 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleResponse.Builder.class) +public final class RuleResponse { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private final String id; + + private final String name; + + private final RuleResponseStatus status; + + private final Optional executionContext; + + private final Map additionalProperties; + + private RuleResponse( + Optional createdBy, + Optional createdDateTime, + Optional modifiedBy, + Optional modifiedDateTime, + String id, + String name, + RuleResponseStatus status, + Optional executionContext, + Map additionalProperties) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + this.id = id; + this.name = name; + this.status = status; + this.executionContext = executionContext; + this.additionalProperties = additionalProperties; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("status") + public RuleResponseStatus getStatus() { + return status; + } + + @JsonProperty("executionContext") + public Optional getExecutionContext() { + return executionContext; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleResponse && equalTo((RuleResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleResponse other) { + return createdBy.equals(other.createdBy) + && createdDateTime.equals(other.createdDateTime) + && modifiedBy.equals(other.modifiedBy) + && modifiedDateTime.equals(other.modifiedDateTime) + && id.equals(other.id) + && name.equals(other.name) + && status.equals(other.status) + && executionContext.equals(other.executionContext); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash( + this.createdBy, + this.createdDateTime, + this.modifiedBy, + this.modifiedDateTime, + this.id, + this.name, + this.status, + this.executionContext); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(@NotNull String id); + + Builder from(RuleResponse other); + } + + public interface NameStage { + StatusStage name(@NotNull String name); + } + + public interface StatusStage { + _FinalStage status(@NotNull RuleResponseStatus status); + } + + public interface _FinalStage { + RuleResponse build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

The user who created this resource.

+ */ + _FinalStage createdBy(Optional createdBy); + + _FinalStage createdBy(String createdBy); + + /** + *

When this resource was created.

+ */ + _FinalStage createdDateTime(Optional createdDateTime); + + _FinalStage createdDateTime(OffsetDateTime createdDateTime); + + /** + *

The user who last modified this resource.

+ */ + _FinalStage modifiedBy(Optional modifiedBy); + + _FinalStage modifiedBy(String modifiedBy); + + /** + *

When this resource was last modified.

+ */ + _FinalStage modifiedDateTime(Optional modifiedDateTime); + + _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); + + _FinalStage executionContext(Optional executionContext); + + _FinalStage executionContext(RuleExecutionContext executionContext); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { + private String id; + + private String name; + + private RuleResponseStatus status; + + private Optional executionContext = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional createdBy = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleResponse other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + id(other.getId()); + name(other.getName()); + status(other.getStatus()); + executionContext(other.getExecutionContext()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public StatusStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public _FinalStage status(@NotNull RuleResponseStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage executionContext(RuleExecutionContext executionContext) { + this.executionContext = Optional.ofNullable(executionContext); + return this; + } + + @java.lang.Override + @JsonSetter(value = "executionContext", nulls = Nulls.SKIP) + public _FinalStage executionContext(Optional executionContext) { + this.executionContext = executionContext; + return this; + } + + /** + *

When this resource was last modified.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @java.lang.Override + @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) + public _FinalStage modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + /** + *

The user who last modified this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @java.lang.Override + @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) + public _FinalStage modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + /** + *

When this resource was created.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

When this resource was created.

+ */ + @java.lang.Override + @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) + public _FinalStage createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + /** + *

The user who created this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

The user who created this resource.

+ */ + @java.lang.Override + @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) + public _FinalStage createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + @java.lang.Override + public RuleResponse build() { + return new RuleResponse( + createdBy, + createdDateTime, + modifiedBy, + modifiedDateTime, + id, + name, + status, + executionContext, + additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java new file mode 100644 index 000000000000..b771bb61005f --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class RuleResponseStatus { + public static final RuleResponseStatus INACTIVE = new RuleResponseStatus(Value.INACTIVE, "inactive"); + + public static final RuleResponseStatus ACTIVE = new RuleResponseStatus(Value.ACTIVE, "active"); + + public static final RuleResponseStatus DRAFT = new RuleResponseStatus(Value.DRAFT, "draft"); + + private final Value value; + + private final String string; + + RuleResponseStatus(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof RuleResponseStatus && this.string.equals(((RuleResponseStatus) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case INACTIVE: + return visitor.visitInactive(); + case ACTIVE: + return visitor.visitActive(); + case DRAFT: + return visitor.visitDraft(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static RuleResponseStatus valueOf(String value) { + switch (value) { + case "inactive": + return INACTIVE; + case "active": + return ACTIVE; + case "draft": + return DRAFT; + default: + return new RuleResponseStatus(Value.UNKNOWN, value); + } + } + + public enum Value { + ACTIVE, + + INACTIVE, + + DRAFT, + + UNKNOWN + } + + public interface Visitor { + T visitActive(); + + T visitInactive(); + + T visitDraft(); + + T visitUnknown(String unknownType); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java new file mode 100644 index 000000000000..18d016fde1bd --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java @@ -0,0 +1,170 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleType.Builder.class) +public final class RuleType { + private final String id; + + private final String name; + + private final Optional description; + + private final Map additionalProperties; + + private RuleType(String id, String name, Optional description, Map additionalProperties) { + this.id = id; + this.name = name; + this.description = description; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("description") + public Optional getDescription() { + return description; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleType && equalTo((RuleType) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleType other) { + return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.description); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(@NotNull String id); + + Builder from(RuleType other); + } + + public interface NameStage { + _FinalStage name(@NotNull String name); + } + + public interface _FinalStage { + RuleType build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + _FinalStage description(Optional description); + + _FinalStage description(String description); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, NameStage, _FinalStage { + private String id; + + private String name; + + private Optional description = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleType other) { + id(other.getId()); + name(other.getName()); + description(other.getDescription()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage description(String description) { + this.description = Optional.ofNullable(description); + return this; + } + + @java.lang.Override + @JsonSetter(value = "description", nulls = Nulls.SKIP) + public _FinalStage description(Optional description) { + this.description = description; + return this; + } + + @java.lang.Override + public RuleType build() { + return new RuleType(id, name, description, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java new file mode 100644 index 000000000000..fca3c6ec8009 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java @@ -0,0 +1,163 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleTypeSearchResponse.Builder.class) +public final class RuleTypeSearchResponse { + private final PagingCursors paging; + + private final Optional> results; + + private final Map additionalProperties; + + private RuleTypeSearchResponse( + PagingCursors paging, Optional> results, Map additionalProperties) { + this.paging = paging; + this.results = results; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleTypeSearchResponse other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(@NotNull PagingCursors paging); + + Builder from(RuleTypeSearchResponse other); + } + + public interface _FinalStage { + RuleTypeSearchResponse build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleTypeSearchResponse other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(@NotNull PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter(value = "results", nulls = Nulls.SKIP) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public RuleTypeSearchResponse build() { + return new RuleTypeSearchResponse(paging, results, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java new file mode 100644 index 000000000000..cb2b9f73c34e --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = User.Builder.class) +public final class User { + private final String id; + + private final String email; + + private final Map additionalProperties; + + private User(String id, String email, Map additionalProperties) { + this.id = id; + this.email = email; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("email") + public String getEmail() { + return email; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(User other) { + return id.equals(other.id) && email.equals(other.email); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.email); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + EmailStage id(@NotNull String id); + + Builder from(User other); + } + + public interface EmailStage { + _FinalStage email(@NotNull String email); + } + + public interface _FinalStage { + User build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, EmailStage, _FinalStage { + private String id; + + private String email; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(User other) { + id(other.getId()); + email(other.getEmail()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public EmailStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("email") + public _FinalStage email(@NotNull String email) { + this.email = Objects.requireNonNull(email, "email must not be null"); + return this; + } + + @java.lang.Override + public User build() { + return new User(id, email, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java new file mode 100644 index 000000000000..0fc2c8e793aa --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java @@ -0,0 +1,163 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = UserSearchResponse.Builder.class) +public final class UserSearchResponse { + private final PagingCursors paging; + + private final Optional> results; + + private final Map additionalProperties; + + private UserSearchResponse( + PagingCursors paging, Optional> results, Map additionalProperties) { + this.paging = paging; + this.results = results; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(UserSearchResponse other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(@NotNull PagingCursors paging); + + Builder from(UserSearchResponse other); + } + + public interface _FinalStage { + UserSearchResponse build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(UserSearchResponse other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(@NotNull PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter(value = "results", nulls = Nulls.SKIP) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public UserSearchResponse build() { + return new UserSearchResponse(paging, results, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java new file mode 100644 index 000000000000..22ad146af59c --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java @@ -0,0 +1,13 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.SearchRuleTypesRequest; + +public class Example0 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.searchRuleTypes(SearchRuleTypesRequest.builder().build()); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java new file mode 100644 index 000000000000..20488261da14 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java @@ -0,0 +1,13 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.SearchRuleTypesRequest; + +public class Example1 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.searchRuleTypes(SearchRuleTypesRequest.builder().query("query").build()); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java new file mode 100644 index 000000000000..e0f6cdddaea0 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java @@ -0,0 +1,17 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.types.RuleExecutionContext; + +public class Example2 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.createRule(RuleCreateRequest.builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build()); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java new file mode 100644 index 000000000000..7f1d77d08358 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java @@ -0,0 +1,17 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.types.RuleExecutionContext; + +public class Example3 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.createRule(RuleCreateRequest.builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build()); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java new file mode 100644 index 000000000000..e680b39ebabe --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example4 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.listUsers(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java new file mode 100644 index 000000000000..83e4c14bbe78 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example5 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.listUsers(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java new file mode 100644 index 000000000000..e1757715676e --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example6 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getEntity(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java new file mode 100644 index 000000000000..e180edb8e8a3 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example7 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getEntity(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java new file mode 100644 index 000000000000..e80deb53f21a --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example8 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getOrganization(); + } +} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java new file mode 100644 index 000000000000..f5ab1ab4544d --- /dev/null +++ b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example9 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getOrganization(); + } +} diff --git a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java new file mode 100644 index 000000000000..9bf64b7a1cf5 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java @@ -0,0 +1,120 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import static org.junit.jupiter.api.Assertions.*; + +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.Stream; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public final class StreamTest { + @Test + public void testJsonStream() { + List> messages = + Arrays.asList(createMap("message", "hello"), createMap("message", "world")); + List jsonStrings = messages.stream().map(StreamTest::mapToJson).collect(Collectors.toList()); + String input = String.join("\n", jsonStrings); + StringReader jsonInput = new StringReader(input); + Stream jsonStream = Stream.fromJson(Map.class, jsonInput); + int expectedMessages = 2; + int actualMessages = 0; + for (Map jsonObject : jsonStream) { + actualMessages++; + assertTrue(jsonObject.containsKey("message")); + } + assertEquals(expectedMessages, actualMessages); + } + + @Test + public void testSseStream() { + List> events = Arrays.asList(createMap("event", "start"), createMap("event", "end")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("event")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testSseStreamWithTerminator() { + List> events = Arrays.asList(createMap("message", "first"), createMap("message", "second")); + List sseStrings = + new ArrayList<>(events.stream().map(StreamTest::mapToSse).collect(Collectors.toList())); + sseStrings.add("data: [DONE]"); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput, "[DONE]"); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("message")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testSseEventDiscriminatedStream() { + List sseStrings = Arrays.asList( + mapToSseWithEvent("start", createMap("status", "pending")), + mapToSseWithEvent("end", createMap("status", "complete"))); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSseWithEventDiscrimination(Map.class, sseInput, "event"); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + // Event-level discrimination includes the event field in the parsed result + assertTrue(eventData.containsKey("event")); + assertTrue(eventData.containsKey("data")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testStreamResourceManagement() throws IOException { + StringReader testInput = new StringReader("{\"test\":\"data\"}"); + Stream testStream = Stream.fromJson(Map.class, testInput); + testStream.close(); + assertFalse(testStream.iterator().hasNext()); + } + + private static String mapToJson(Map map) { + try { + return ObjectMappers.JSON_MAPPER.writeValueAsString(map); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String mapToSse(Map map) { + return "data: " + mapToJson(map); + } + + private static String mapToSseWithEvent(String eventType, Map data) { + return "event: " + eventType + "\n" + "data: " + mapToJson(data); + } + + private static Map createMap(String key, String value) { + Map map = new HashMap<>(); + map.put(key, value); + return map; + } +} diff --git a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java new file mode 100644 index 000000000000..1686cfd803c1 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java new file mode 100644 index 000000000000..ead1a49af7a2 --- /dev/null +++ b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java @@ -0,0 +1,339 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public final class QueryStringMapperTest { + @Test + public void testObjectWithQuotedString_indexedArrays() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithQuotedString_arraysAsRepeats() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_indexedArrays() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_arraysAsRepeats() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_indexedArrays() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_arraysAsRepeats() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_indexedArrays() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_arraysAsRepeats() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_indexedArrays() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = "objects%5B0%5D%5Bvalue%5D=world&objects%5B0%5D%5Bkey%5D=hello&objects%5B1%5D" + + "%5Bvalue%5D=bar&objects%5B1%5D%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_arraysAsRepeats() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = + "objects%5Bvalue%5D=world&objects%5Bkey%5D=hello&objects%5Bvalue" + "%5D=bar&objects%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_indexedArrays() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = + "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds%5D%5B0%5D=id1&objectwitharray" + + "%5BcontactIds%5D%5B1%5D=id2&objectwitharray%5BcontactIds%5D%5B2%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_arraysAsRepeats() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds" + + "%5D=id1&objectwitharray%5BcontactIds%5D=id2&objectwitharray%5BcontactIds%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + private static String queryString(Map params, boolean arraysAsRepeats) { + HttpUrl.Builder httpUrl = HttpUrl.parse("http://www.fakewebsite.com/").newBuilder(); + params.forEach((paramName, paramValue) -> + QueryStringMapper.addQueryParameter(httpUrl, paramName, paramValue, arraysAsRepeats)); + return httpUrl.build().encodedQuery(); + } +} diff --git a/seed/java-sdk/allof/.fern/metadata.json b/seed/java-sdk/allof/.fern/metadata.json new file mode 100644 index 000000000000..6077ef8b5f57 --- /dev/null +++ b/seed/java-sdk/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-java-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/java-sdk/allof/.github/workflows/ci.yml b/seed/java-sdk/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..09c8c666ad73 --- /dev/null +++ b/seed/java-sdk/allof/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/allof/.gitignore b/seed/java-sdk/allof/.gitignore new file mode 100644 index 000000000000..d4199abc2cd4 --- /dev/null +++ b/seed/java-sdk/allof/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/allof/README.md b/seed/java-sdk/allof/README.md new file mode 100644 index 000000000000..c880c4c6d68d --- /dev/null +++ b/seed/java-sdk/allof/README.md @@ -0,0 +1,235 @@ +# Seed Java Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FJava) +[![Maven Central](https://img.shields.io/maven-central/v/com.fern/allof)](https://central.sonatype.com/artifact/com.fern/allof) + +The Seed Java library provides convenient access to the Seed APIs from Java. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Base Url](#base-url) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Custom Client](#custom-client) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom Headers](#custom-headers) + - [Access Raw Response Data](#access-raw-response-data) +- [Contributing](#contributing) + +## Installation + +### Gradle + +Add the dependency in your `build.gradle` file: + +```groovy +dependencies { + implementation 'com.fern:allof:0.0.1' +} +``` + +### Maven + +Add the dependency in your `pom.xml` file: + +```xml + + com.fern + allof + 0.0.1 + +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```java +package com.example.usage; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.types.RuleExecutionContext; + +public class Example { + public static void main(String[] args) { + SeedApiClient client = SeedApiClient + .builder() + .build(); + + client.createRule( + RuleCreateRequest + .builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build() + ); + } +} +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.Environment; + +SeedApiClient client = SeedApiClient + .builder() + .environment(Environment.Default) + .build(); +``` + +## Base Url + +You can set a custom base URL when constructing the client. + +```java +import com.seed.api.SeedApiClient; + +SeedApiClient client = SeedApiClient + .builder() + .url("https://example.com") + .build(); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), an API exception will be thrown. + +```java +import com.seed.api.core.SeedApiApiException; + +try{ + client.createRule(...); +} catch (SeedApiApiException e){ + // Do something with the API exception... +} +``` + +## Advanced + +### Custom Client + +This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one. +However, you can pass your own client like so: + +```java +import com.seed.api.SeedApiClient; +import okhttp3.OkHttpClient; + +OkHttpClient customClient = ...; + +SeedApiClient client = SeedApiClient + .builder() + .httpClient(customClient) + .build(); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). Before defaulting to exponential backoff, the SDK will first attempt to respect +the `Retry-After` header (as either in seconds or as an HTTP date), and then the `X-RateLimit-Reset` header +(as a Unix timestamp in epoch seconds); failing both of those, it will fall back to exponential backoff. + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` client option to configure this behavior. + +```java +import com.seed.api.SeedApiClient; + +SeedApiClient client = SeedApiClient + .builder() + .maxRetries(1) + .build(); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.RequestOptions; + +// Client level +SeedApiClient client = SeedApiClient + .builder() + .timeout(60) + .build(); + +// Request level +client.createRule( + ..., + RequestOptions + .builder() + .timeout(60) + .build() +); +``` + +### Custom Headers + +The SDK allows you to add custom headers to requests. You can configure headers at the client level or at the request level. + +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.RequestOptions; + +// Client level +SeedApiClient client = SeedApiClient + .builder() + .addHeader("X-Custom-Header", "custom-value") + .addHeader("X-Request-Id", "abc-123") + .build(); +; + +// Request level +client.createRule( + ..., + RequestOptions + .builder() + .addHeader("X-Request-Header", "request-value") + .build() +); +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `withRawResponse()` method. +The `withRawResponse()` method returns a raw client that wraps all responses with `body()` and `headers()` methods. +(A normal client's `response` is identical to a raw client's `response.body()`.) + +```java +SeedApiHttpResponse response = client.withRawResponse().createRule(...); + +System.out.println(response.body()); +System.out.println(response.headers().get("X-My-Header")); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/java-sdk/allof/build.gradle b/seed/java-sdk/allof/build.gradle new file mode 100644 index 000000000000..33626ee04bbe --- /dev/null +++ b/seed/java-sdk/allof/build.gradle @@ -0,0 +1,102 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:5.2.1' + api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(Javadoc) { + failOnError false + options.addStringOption('Xdoclint:none', '-quiet') +} + +spotless { + java { + palantirJavaFormat() + } +} + + +java { + withSourcesJar() + withJavadocJar() +} + + +group = 'com.fern' + +version = '0.0.1' + +jar { + dependsOn(":generatePomFileForMavenPublication") + archiveBaseName = "allof" +} + +sourcesJar { + archiveBaseName = "allof" +} + +javadocJar { + archiveBaseName = "allof" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'allof' + version = '0.0.1' + from components.java + pom { + licenses { + license { + name = 'The MIT License (MIT)' + url = 'https://mit-license.org/' + } + } + scm { + connection = 'scm:git:git://github.com/allof/fern.git' + developerConnection = 'scm:git:git://github.com/allof/fern.git' + url = 'https://github.com/allof/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/allof/reference.md b/seed/java-sdk/allof/reference.md new file mode 100644 index 000000000000..c37cf3e44a84 --- /dev/null +++ b/seed/java-sdk/allof/reference.md @@ -0,0 +1,174 @@ +# Reference +
client.searchRuleTypes() -> RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.searchRuleTypes( + SearchRuleTypesRequest + .builder() + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `Optional` + +
+
+
+
+ + +
+
+
+ +
client.createRule(request) -> RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.createRule( + RuleCreateRequest + .builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `String` + +
+
+ +
+
+ +**executionContext:** `RuleExecutionContext` + +
+
+
+
+ + +
+
+
+ +
client.listUsers() -> UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.listUsers(); +``` +
+
+
+
+ + +
+
+
+ +
client.getEntity() -> CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.getEntity(); +``` +
+
+
+
+ + +
+
+
+ +
client.getOrganization() -> Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.getOrganization(); +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/java-sdk/allof/sample-app/build.gradle b/seed/java-sdk/allof/sample-app/build.gradle new file mode 100644 index 000000000000..4ee8f227b7af --- /dev/null +++ b/seed/java-sdk/allof/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/allof/sample-app/src/main/java/sample/App.java b/seed/java-sdk/allof/sample-app/src/main/java/sample/App.java new file mode 100644 index 000000000000..8d293789008c --- /dev/null +++ b/seed/java-sdk/allof/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.api.AsyncSeedApiClient + } +} diff --git a/seed/java-sdk/allof/settings.gradle b/seed/java-sdk/allof/settings.gradle new file mode 100644 index 000000000000..841ce1f693b0 --- /dev/null +++ b/seed/java-sdk/allof/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'allof' + +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/allof/snippet.json b/seed/java-sdk/allof/snippet.json new file mode 100644 index 000000000000..4ed7337cd530 --- /dev/null +++ b/seed/java-sdk/allof/snippet.json @@ -0,0 +1,135 @@ +{ + "endpoints": [ + { + "example_identifier": "b6434d4c", + "id": { + "method": "GET", + "path": "/rule-types", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "72136c9a", + "id": { + "method": "GET", + "path": "/rule-types", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "c1cf878e", + "id": { + "method": "POST", + "path": "/rules", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "1581df40", + "id": { + "method": "POST", + "path": "/rules", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "55942cbc", + "id": { + "method": "GET", + "path": "/users", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" + } + }, + { + "example_identifier": "d95b49df", + "id": { + "method": "GET", + "path": "/users", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" + } + }, + { + "example_identifier": "b2b07150", + "id": { + "method": "GET", + "path": "/entities", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" + } + }, + { + "example_identifier": "51ca69d", + "id": { + "method": "GET", + "path": "/entities", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" + } + }, + { + "example_identifier": "fd2e2e41", + "id": { + "method": "GET", + "path": "/organizations", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" + } + }, + { + "example_identifier": "6b857a2c", + "id": { + "method": "GET", + "path": "/organizations", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java new file mode 100644 index 000000000000..046deb483a7f --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java @@ -0,0 +1,323 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.QueryStringMapper; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.core.SeedApiHttpResponse; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawSeedApiClient { + protected final ClientOptions clientOptions; + + public AsyncRawSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CompletableFuture> searchRuleTypes() { + return searchRuleTypes(SearchRuleTypesRequest.builder().build()); + } + + public CompletableFuture> searchRuleTypes( + RequestOptions requestOptions) { + return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); + } + + public CompletableFuture> searchRuleTypes( + SearchRuleTypesRequest request) { + return searchRuleTypes(request, null); + } + + public CompletableFuture> searchRuleTypes( + SearchRuleTypesRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rule-types"); + if (request.getQuery().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "query", request.getQuery().get(), false); + } + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), + response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> createRule(RuleCreateRequest request) { + return createRule(request, null); + } + + public CompletableFuture> createRule( + RuleCreateRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rules"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> listUsers() { + return listUsers(null); + } + + public CompletableFuture> listUsers(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("users"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), + response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> getEntity() { + return getEntity(null); + } + + public CompletableFuture> getEntity(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("entities"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), + response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> getOrganization() { + return getOrganization(null); + } + + public CompletableFuture> getOrganization(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("organizations"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response)); + return; + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java new file mode 100644 index 000000000000..45b759db04ed --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; +import java.util.concurrent.CompletableFuture; + +public class AsyncSeedApiClient { + protected final ClientOptions clientOptions; + + private final AsyncRawSeedApiClient rawClient; + + public AsyncSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawSeedApiClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawSeedApiClient withRawResponse() { + return this.rawClient; + } + + public CompletableFuture searchRuleTypes() { + return this.rawClient.searchRuleTypes().thenApply(response -> response.body()); + } + + public CompletableFuture searchRuleTypes(RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture searchRuleTypes(SearchRuleTypesRequest request) { + return this.rawClient.searchRuleTypes(request).thenApply(response -> response.body()); + } + + public CompletableFuture searchRuleTypes( + SearchRuleTypesRequest request, RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture createRule(RuleCreateRequest request) { + return this.rawClient.createRule(request).thenApply(response -> response.body()); + } + + public CompletableFuture createRule(RuleCreateRequest request, RequestOptions requestOptions) { + return this.rawClient.createRule(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture listUsers() { + return this.rawClient.listUsers().thenApply(response -> response.body()); + } + + public CompletableFuture listUsers(RequestOptions requestOptions) { + return this.rawClient.listUsers(requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture getEntity() { + return this.rawClient.getEntity().thenApply(response -> response.body()); + } + + public CompletableFuture getEntity(RequestOptions requestOptions) { + return this.rawClient.getEntity(requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture getOrganization() { + return this.rawClient.getOrganization().thenApply(response -> response.body()); + } + + public CompletableFuture getOrganization(RequestOptions requestOptions) { + return this.rawClient.getOrganization(requestOptions).thenApply(response -> response.body()); + } + + public static AsyncSeedApiClientBuilder builder() { + return new AsyncSeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java new file mode 100644 index 000000000000..bc66923d39bc --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java @@ -0,0 +1,194 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import com.seed.api.core.LogConfig; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class AsyncSeedApiClientBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment = Environment.DEFAULT; + + private OkHttpClient httpClient; + + private Optional logging = Optional.empty(); + + public AsyncSeedApiClientBuilder environment(Environment environment) { + this.environment = environment; + return this; + } + + public AsyncSeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public AsyncSeedApiClientBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public AsyncSeedApiClientBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public AsyncSeedApiClientBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public AsyncSeedApiClientBuilder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public AsyncSeedApiClientBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + setLogging(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Sets the logging configuration for the SDK. + * Override this method to customize logging behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setLogging(ClientOptions.Builder builder) { + if (this.logging.isPresent()) { + builder.logging(this.logging.get()); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public AsyncSeedApiClient build() { + validateConfiguration(); + return new AsyncSeedApiClient(buildClientOptions()); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java new file mode 100644 index 000000000000..f9a763a744c4 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java @@ -0,0 +1,249 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.QueryStringMapper; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.core.SeedApiHttpResponse; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class RawSeedApiClient { + protected final ClientOptions clientOptions; + + public RawSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public SeedApiHttpResponse searchRuleTypes() { + return searchRuleTypes(SearchRuleTypesRequest.builder().build()); + } + + public SeedApiHttpResponse searchRuleTypes(RequestOptions requestOptions) { + return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); + } + + public SeedApiHttpResponse searchRuleTypes(SearchRuleTypesRequest request) { + return searchRuleTypes(request, null); + } + + public SeedApiHttpResponse searchRuleTypes( + SearchRuleTypesRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rule-types"); + if (request.getQuery().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "query", request.getQuery().get(), false); + } + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), + response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse createRule(RuleCreateRequest request) { + return createRule(request, null); + } + + public SeedApiHttpResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("rules"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse listUsers() { + return listUsers(null); + } + + public SeedApiHttpResponse listUsers(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("users"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse getEntity() { + return getEntity(null); + } + + public SeedApiHttpResponse getEntity(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("entities"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse getOrganization() { + return getOrganization(null); + } + + public SeedApiHttpResponse getOrganization(RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("organizations"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response); + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new SeedApiApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java new file mode 100644 index 000000000000..73a73c0a87a2 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java @@ -0,0 +1,84 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.requests.SearchRuleTypesRequest; +import com.seed.api.types.CombinedEntity; +import com.seed.api.types.Organization; +import com.seed.api.types.RuleResponse; +import com.seed.api.types.RuleTypeSearchResponse; +import com.seed.api.types.UserSearchResponse; + +public class SeedApiClient { + protected final ClientOptions clientOptions; + + private final RawSeedApiClient rawClient; + + public SeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawSeedApiClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawSeedApiClient withRawResponse() { + return this.rawClient; + } + + public RuleTypeSearchResponse searchRuleTypes() { + return this.rawClient.searchRuleTypes().body(); + } + + public RuleTypeSearchResponse searchRuleTypes(RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(requestOptions).body(); + } + + public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request) { + return this.rawClient.searchRuleTypes(request).body(); + } + + public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request, RequestOptions requestOptions) { + return this.rawClient.searchRuleTypes(request, requestOptions).body(); + } + + public RuleResponse createRule(RuleCreateRequest request) { + return this.rawClient.createRule(request).body(); + } + + public RuleResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { + return this.rawClient.createRule(request, requestOptions).body(); + } + + public UserSearchResponse listUsers() { + return this.rawClient.listUsers().body(); + } + + public UserSearchResponse listUsers(RequestOptions requestOptions) { + return this.rawClient.listUsers(requestOptions).body(); + } + + public CombinedEntity getEntity() { + return this.rawClient.getEntity().body(); + } + + public CombinedEntity getEntity(RequestOptions requestOptions) { + return this.rawClient.getEntity(requestOptions).body(); + } + + public Organization getOrganization() { + return this.rawClient.getOrganization().body(); + } + + public Organization getOrganization(RequestOptions requestOptions) { + return this.rawClient.getOrganization(requestOptions).body(); + } + + public static SeedApiClientBuilder builder() { + return new SeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java b/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java new file mode 100644 index 000000000000..5f458e5b960a --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java @@ -0,0 +1,194 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import com.seed.api.core.LogConfig; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class SeedApiClientBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment = Environment.DEFAULT; + + private OkHttpClient httpClient; + + private Optional logging = Optional.empty(); + + public SeedApiClientBuilder environment(Environment environment) { + this.environment = environment; + return this; + } + + public SeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public SeedApiClientBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public SeedApiClientBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public SeedApiClientBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public SeedApiClientBuilder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public SeedApiClientBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + setLogging(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Sets the logging configuration for the SDK. + * Override this method to customize logging behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setLogging(ClientOptions.Builder builder) { + if (this.logging.isPresent()) { + builder.logging(this.logging.get()); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public SeedApiClient build() { + validateConfiguration(); + return new SeedApiClient(buildClientOptions()); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java new file mode 100644 index 000000000000..d5be33a0645f --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java @@ -0,0 +1,221 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final int timeout; + + private final int maxRetries; + + private final Optional logging; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout, + int maxRetries, + Optional logging) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("User-Agent", "com.fern:allof/0.0.1"); + put("X-Fern-Language", "JAVA"); + put("X-Fern-SDK-Name", "com.seed.fern:api-sdk"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + this.maxRetries = maxRetries; + this.logging = logging; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public int maxRetries() { + return this.maxRetries; + } + + public Optional logging() { + return this.logging; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + private Optional logging = Optional.empty(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public Builder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor(this.maxRetries)); + } + + Logger logger = Logger.from(this.logging); + httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions( + environment, + headers, + headerSuppliers, + httpClient, + this.timeout.get(), + this.maxRetries, + this.logging); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + builder.headers.putAll(clientOptions.headers); + builder.headerSuppliers.putAll(clientOptions.headerSuppliers); + builder.maxRetries = clientOptions.maxRetries(); + builder.logging = clientOptions.logging(); + return builder; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java new file mode 100644 index 000000000000..0deabc0b79f7 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java @@ -0,0 +1,51 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.logging.Level; + +/** + * Default logger implementation that writes to the console using {@link java.util.logging.Logger}. + * + *

Uses the "fern" logger name with a simple format of "LEVEL - message". + */ +public final class ConsoleLogger implements ILogger { + + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger("fern"); + + static { + if (logger.getHandlers().length == 0) { + java.util.logging.ConsoleHandler handler = new java.util.logging.ConsoleHandler(); + handler.setFormatter(new java.util.logging.SimpleFormatter() { + @Override + public String format(java.util.logging.LogRecord record) { + return record.getLevel() + " - " + record.getMessage() + System.lineSeparator(); + } + }); + logger.addHandler(handler); + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + } + } + + @Override + public void debug(String message) { + logger.log(Level.FINE, message); + } + + @Override + public void info(String message) { + logger.log(Level.INFO, message); + } + + @Override + public void warn(String message) { + logger.log(Level.WARNING, message); + } + + @Override + public void error(String message) { + logger.log(Level.SEVERE, message); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 000000000000..eac7d50c71ae --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java new file mode 100644 index 000000000000..2390bfbe5690 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java @@ -0,0 +1,43 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; + +/** + * Custom serializer that writes integer-valued doubles without a decimal point. + * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. + * Non-integer values like {@code 3.14} are serialized normally. + */ +class DoubleSerializer extends JsonSerializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule() + .addSerializer(Double.class, new DoubleSerializer()) + .addSerializer(double.class, new DoubleSerializer()); + } + + /** + * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { + gen.writeNumber(value.longValue()); + } else { + gen.writeNumber(value); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java new file mode 100644 index 000000000000..c612dbc31093 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +public final class Environment { + public static final Environment DEFAULT = new Environment("https://api.example.com"); + + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java new file mode 100644 index 000000000000..a71e79469869 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java new file mode 100644 index 000000000000..866d4814dd5c --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java @@ -0,0 +1,38 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * Interface for custom logger implementations. + * + *

Implement this interface to provide a custom logging backend for the SDK. + * The SDK will call the appropriate method based on the log level. + * + *

Example: + *

{@code
+ * public class MyCustomLogger implements ILogger {
+ *     public void debug(String message) {
+ *         System.out.println("[DBG] " + message);
+ *     }
+ *     public void info(String message) {
+ *         System.out.println("[INF] " + message);
+ *     }
+ *     public void warn(String message) {
+ *         System.out.println("[WRN] " + message);
+ *     }
+ *     public void error(String message) {
+ *         System.out.println("[ERR] " + message);
+ *     }
+ * }
+ * }
+ */ +public interface ILogger { + void debug(String message); + + void info(String message); + + void warn(String message); + + void error(String message); +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java new file mode 100644 index 000000000000..a1e136889aaa --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java @@ -0,0 +1,74 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + try (Source source = Okio.source(inputStream)) { + sink.writeAll(source); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java new file mode 100644 index 000000000000..dd31bc7a6545 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java @@ -0,0 +1,98 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * Configuration for SDK logging. + * + *

Use the builder to configure logging behavior: + *

{@code
+ * LogConfig config = LogConfig.builder()
+ *     .level(LogLevel.DEBUG)
+ *     .silent(false)
+ *     .build();
+ * }
+ * + *

Or with a custom logger: + *

{@code
+ * LogConfig config = LogConfig.builder()
+ *     .level(LogLevel.DEBUG)
+ *     .logger(new MyCustomLogger())
+ *     .silent(false)
+ *     .build();
+ * }
+ * + *

Defaults: + *

    + *
  • {@code level} — {@link LogLevel#INFO}
  • + *
  • {@code logger} — {@link ConsoleLogger} (writes to stderr via java.util.logging)
  • + *
  • {@code silent} — {@code true} (no output unless explicitly enabled)
  • + *
+ */ +public final class LogConfig { + + private final LogLevel level; + private final ILogger logger; + private final boolean silent; + + private LogConfig(LogLevel level, ILogger logger, boolean silent) { + this.level = level; + this.logger = logger; + this.silent = silent; + } + + public LogLevel level() { + return level; + } + + public ILogger logger() { + return logger; + } + + public boolean silent() { + return silent; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private LogLevel level = LogLevel.INFO; + private ILogger logger = new ConsoleLogger(); + private boolean silent = true; + + private Builder() {} + + /** + * Set the minimum log level. Only messages at this level or above will be logged. + * Defaults to {@link LogLevel#INFO}. + */ + public Builder level(LogLevel level) { + this.level = level; + return this; + } + + /** + * Set a custom logger implementation. Defaults to {@link ConsoleLogger}. + */ + public Builder logger(ILogger logger) { + this.logger = logger; + return this; + } + + /** + * Set whether logging is silent (disabled). Defaults to {@code true}. + * Set to {@code false} to enable log output. + */ + public Builder silent(boolean silent) { + this.silent = silent; + return this; + } + + public LogConfig build() { + return new LogConfig(level, logger, silent); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java new file mode 100644 index 000000000000..2ef88f853eae --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * Log levels for SDK logging configuration. + * Silent by default — no log output unless explicitly configured. + */ +public enum LogLevel { + DEBUG(1), + INFO(2), + WARN(3), + ERROR(4); + + private final int value; + + LogLevel(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + /** + * Parse a log level from a string (case-insensitive). + * + * @param level the level string (debug, info, warn, error) + * @return the corresponding LogLevel + * @throws IllegalArgumentException if the string does not match any level + */ + public static LogLevel fromString(String level) { + return LogLevel.valueOf(level.toUpperCase()); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java new file mode 100644 index 000000000000..353f696fcf3f --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * SDK logger that filters messages based on level and silent mode. + * + *

Silent by default — no log output unless explicitly configured. + * Create via {@link LogConfig} or directly: + *

{@code
+ * Logger logger = new Logger(LogLevel.DEBUG, new ConsoleLogger(), false);
+ * logger.debug("request sent");
+ * }
+ */ +public final class Logger { + + private static final Logger DEFAULT = new Logger(LogLevel.INFO, new ConsoleLogger(), true); + + private final LogLevel level; + private final ILogger logger; + private final boolean silent; + + public Logger(LogLevel level, ILogger logger, boolean silent) { + this.level = level; + this.logger = logger; + this.silent = silent; + } + + /** + * Returns a default silent logger (no output). + */ + public static Logger getDefault() { + return DEFAULT; + } + + /** + * Creates a Logger from a {@link LogConfig}. If config is {@code null}, returns the default silent logger. + */ + public static Logger from(LogConfig config) { + if (config == null) { + return DEFAULT; + } + return new Logger(config.level(), config.logger(), config.silent()); + } + + /** + * Creates a Logger from an {@code Optional}. If empty, returns the default silent logger. + */ + public static Logger from(java.util.Optional config) { + return config.map(Logger::from).orElse(DEFAULT); + } + + private boolean shouldLog(LogLevel messageLevel) { + return !silent && level.getValue() <= messageLevel.getValue(); + } + + public boolean isDebug() { + return shouldLog(LogLevel.DEBUG); + } + + public boolean isInfo() { + return shouldLog(LogLevel.INFO); + } + + public boolean isWarn() { + return shouldLog(LogLevel.WARN); + } + + public boolean isError() { + return shouldLog(LogLevel.ERROR); + } + + public void debug(String message) { + if (isDebug()) { + logger.debug(message); + } + } + + public void info(String message) { + if (isInfo()) { + logger.info(message); + } + } + + public void warn(String message) { + if (isWarn()) { + logger.warn(message); + } + } + + public void error(String message) { + if (isError()) { + logger.error(message); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java new file mode 100644 index 000000000000..39a8eadeb905 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java @@ -0,0 +1,104 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * OkHttp interceptor that logs HTTP requests and responses. + * + *

Logs request method, URL, and headers (with sensitive values redacted) at debug level. + * Logs response status at debug level, and 4xx/5xx responses at error level. + * Does nothing if the logger is silent. + */ +public final class LoggingInterceptor implements Interceptor { + + private static final Set SENSITIVE_HEADERS = new HashSet<>(Arrays.asList( + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "proxy-authenticate", + "proxy-authorization", + "cookie", + "set-cookie", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token")); + + private final Logger logger; + + public LoggingInterceptor(Logger logger) { + this.logger = logger; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + if (logger.isDebug()) { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP Request: ").append(request.method()).append(" ").append(request.url()); + sb.append(" headers={"); + boolean first = true; + for (String name : request.headers().names()) { + if (!first) { + sb.append(", "); + } + sb.append(name).append("="); + if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { + sb.append("[REDACTED]"); + } else { + sb.append(request.header(name)); + } + first = false; + } + sb.append("}"); + sb.append(" has_body=").append(request.body() != null); + logger.debug(sb.toString()); + } + + Response response = chain.proceed(request); + + if (logger.isDebug()) { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP Response: status=").append(response.code()); + sb.append(" url=").append(response.request().url()); + sb.append(" headers={"); + boolean first = true; + for (String name : response.headers().names()) { + if (!first) { + sb.append(", "); + } + sb.append(name).append("="); + if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { + sb.append("[REDACTED]"); + } else { + sb.append(response.header(name)); + } + first = false; + } + sb.append("}"); + logger.debug(sb.toString()); + } + + if (response.code() >= 400 && logger.isError()) { + logger.error("HTTP Error: status=" + response.code() + " url=" + + response.request().url()); + } + + return response; + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java new file mode 100644 index 000000000000..4a8d1cf301d4 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java new file mode 100644 index 000000000000..ca50b2d8d50a --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; +import java.util.function.Function; + +public final class Nullable { + + private final Either, Null> value; + + private Nullable() { + this.value = Either.left(Optional.empty()); + } + + private Nullable(T value) { + if (value == null) { + this.value = Either.right(Null.INSTANCE); + } else { + this.value = Either.left(Optional.of(value)); + } + } + + public static Nullable ofNull() { + return new Nullable<>(null); + } + + public static Nullable of(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(); + } + + public static Nullable ofOptional(Optional value) { + if (value.isPresent()) { + return of(value.get()); + } else { + return empty(); + } + } + + public boolean isNull() { + return this.value.isRight(); + } + + public boolean isEmpty() { + return this.value.isLeft() && !this.value.getLeft().isPresent(); + } + + public T get() { + if (this.isNull()) { + return null; + } + + return this.value.getLeft().get(); + } + + public Nullable map(Function mapper) { + if (this.isNull()) { + return Nullable.ofNull(); + } + + return Nullable.ofOptional(this.value.getLeft().map(mapper)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Nullable)) { + return false; + } + + if (((Nullable) other).isNull() && this.isNull()) { + return true; + } + + return this.value.getLeft().equals(((Nullable) other).value.getLeft()); + } + + private static final class Either { + private L left = null; + private R right = null; + + private Either(L left, R right) { + if (left != null && right != null) { + throw new IllegalArgumentException("Left and right argument cannot both be non-null."); + } + + if (left == null && right == null) { + throw new IllegalArgumentException("Left and right argument cannot both be null."); + } + + if (left != null) { + this.left = left; + } + + if (right != null) { + this.right = right; + } + } + + public static Either left(L left) { + return new Either<>(left, null); + } + + public static Either right(R right) { + return new Either<>(null, right); + } + + public boolean isLeft() { + return this.left != null; + } + + public boolean isRight() { + return this.right != null; + } + + public L getLeft() { + if (!this.isLeft()) { + throw new IllegalArgumentException("Cannot get left from right Either."); + } + return this.left; + } + + public R getRight() { + if (!this.isRight()) { + throw new IllegalArgumentException("Cannot get right from left Either."); + } + return this.right; + } + } + + private static final class Null { + private static final Null INSTANCE = new Null(); + + private Null() {} + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java new file mode 100644 index 000000000000..dea5d181d48c --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; + +public final class NullableNonemptyFilter { + @Override + public boolean equals(Object o) { + boolean isOptionalEmpty = isOptionalEmpty(o); + + return isOptionalEmpty; + } + + private boolean isOptionalEmpty(Object o) { + if (o instanceof Optional) { + return !((Optional) o).isPresent(); + } + return false; + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 000000000000..2e443b07d910 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,46 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .addModule(DoubleSerializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + + public static Object parseErrorBody(String responseBodyString) { + try { + return JSON_MAPPER.readValue(responseBodyString, Object.class); + } catch (JsonProcessingException ignored) { + return responseBodyString; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java new file mode 100644 index 000000000000..3e364e6f3d5f --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java new file mode 100644 index 000000000000..cd95a27201b2 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Map headers; + + private final Map> headerSuppliers; + + private final Map queryParameters; + + private final Map> queryParameterSuppliers; + + private RequestOptions( + Optional timeout, + TimeUnit timeoutTimeUnit, + Map headers, + Map> headerSuppliers, + Map queryParameters, + Map> queryParameterSuppliers) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + this.queryParameters = queryParameters; + this.queryParameterSuppliers = queryParameterSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public Map getQueryParameters() { + Map queryParameters = new HashMap<>(this.queryParameters); + this.queryParameterSuppliers.forEach((key, supplier) -> { + queryParameters.put(key, supplier.get()); + }); + return queryParameters; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private final Map queryParameters = new HashMap<>(); + + private final Map> queryParameterSuppliers = new HashMap<>(); + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public Builder addQueryParameter(String key, String value) { + this.queryParameters.put(key, value); + return this; + } + + public Builder addQueryParameter(String key, Supplier value) { + this.queryParameterSuppliers.put(key, value); + return this; + } + + public RequestOptions build() { + return new RequestOptions( + timeout, timeoutTimeUnit, headers, headerSuppliers, queryParameters, queryParameterSuppliers); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java new file mode 100644 index 000000000000..db05d538255d --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java new file mode 100644 index 000000000000..97fcf7a0efbd --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java new file mode 100644 index 000000000000..0d331c152477 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java @@ -0,0 +1,181 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration INITIAL_RETRY_DELAY = Duration.ofMillis(1000); + private static final Duration MAX_RETRY_DELAY = Duration.ofMillis(60000); + private static final double JITTER_FACTOR = 0.2; + + private final int maxRetries; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.maxRetries = maxRetries; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries); + Optional nextBackoff = backoff.nextBackoff(response); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = backoff.nextBackoff(response); + } else { + return response; + } + } + + return response; + } + + /** + * Calculates the retry delay from response headers, with fallback to exponential backoff. + * Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff + */ + private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) { + // Check for Retry-After header first (RFC 7231), with no jitter + String retryAfter = response.header("Retry-After"); + if (retryAfter != null) { + // Parse as number of seconds... + Optional secondsDelay = tryParseLong(retryAfter) + .map(seconds -> seconds * 1000) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) + .map(Duration::ofMillis); + if (secondsDelay.isPresent()) { + return secondsDelay.get(); + } + + // ...or as an HTTP date; both are valid + Optional dateDelay = tryParseHttpDate(retryAfter) + .map(resetTime -> resetTime.toInstant().toEpochMilli() - System.currentTimeMillis()) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) + .map(Duration::ofMillis); + if (dateDelay.isPresent()) { + return dateDelay.get(); + } + } + + // Then check for industry-standard X-RateLimit-Reset header, with positive jitter + String rateLimitReset = response.header("X-RateLimit-Reset"); + if (rateLimitReset != null) { + // Assume Unix timestamp in epoch seconds + Optional rateLimitDelay = tryParseLong(rateLimitReset) + .map(resetTimeSeconds -> (resetTimeSeconds * 1000) - System.currentTimeMillis()) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) + .map(this::addPositiveJitter) + .map(Duration::ofMillis); + if (rateLimitDelay.isPresent()) { + return rateLimitDelay.get(); + } + } + + // Fall back to exponential backoff, with symmetric jitter + long baseDelay = INITIAL_RETRY_DELAY.toMillis() * (1L << retryAttempt); // 2^retryAttempt + long cappedDelay = Math.min(baseDelay, MAX_RETRY_DELAY.toMillis()); + return Duration.ofMillis(addSymmetricJitter(cappedDelay)); + } + + /** + * Attempts to parse a string as a long, returning empty Optional on failure. + */ + private Optional tryParseLong(String value) { + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(Long.parseLong(value)); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + /** + * Attempts to parse a string as an HTTP date (RFC 1123), returning empty Optional on failure. + */ + private Optional tryParseHttpDate(String value) { + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME)); + } catch (DateTimeParseException e) { + return Optional.empty(); + } + } + + /** + * Adds positive jitter (100-120% of original value) to prevent thundering herd. + * Used for X-RateLimit-Reset header delays. + */ + private long addPositiveJitter(long delayMs) { + double jitterMultiplier = 1.0 + (random.nextDouble() * JITTER_FACTOR); + return (long) (delayMs * jitterMultiplier); + } + + /** + * Adds symmetric jitter (90-110% of original value) to prevent thundering herd. + * Used for exponential backoff delays. + */ + private long addSymmetricJitter(long delayMs) { + double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * JITTER_FACTOR); + return (long) (delayMs * jitterMultiplier); + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff(Response response) { + if (retryNumber >= maxNumRetries) { + return Optional.empty(); + } + + Duration delay = getRetryDelayFromHeaders(response, retryNumber); + retryNumber += 1; + return Optional.of(delay); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java new file mode 100644 index 000000000000..268edd8c55be --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. + * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. + */ +public class Rfc2822DateTimeDeserializer extends JsonDeserializer { + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + String raw = parser.getValueAsString(); + return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java new file mode 100644 index 000000000000..ab4985c15f96 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java @@ -0,0 +1,73 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class SeedApiApiException extends SeedApiException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + private final Map> headers; + + public SeedApiApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public SeedApiApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @Override + public String toString() { + return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + + ObjectMappers.stringify(body) + "}"; + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java new file mode 100644 index 000000000000..cf8236066674 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class SeedApiException extends RuntimeException { + public SeedApiException(String message) { + super(message); + } + + public SeedApiException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java new file mode 100644 index 000000000000..814561a79e08 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +public final class SeedApiHttpResponse { + + private final T body; + + private final Map> headers; + + public SeedApiHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java new file mode 100644 index 000000000000..9775f2110865 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java @@ -0,0 +1,114 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; +import java.util.Optional; + +/** + * Represents a Server-Sent Event with all standard fields. + * Used for event-level discrimination where the discriminator is at the SSE envelope level. + * + * @param The type of the data field + */ +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonIgnoreProperties(ignoreUnknown = true) +public final class SseEvent { + private final String event; + private final T data; + private final String id; + private final Long retry; + + private SseEvent(String event, T data, String id, Long retry) { + this.event = event; + this.data = data; + this.id = id; + this.retry = retry; + } + + @JsonProperty("event") + public Optional getEvent() { + return Optional.ofNullable(event); + } + + @JsonProperty("data") + public T getData() { + return data; + } + + @JsonProperty("id") + public Optional getId() { + return Optional.ofNullable(id); + } + + @JsonProperty("retry") + public Optional getRetry() { + return Optional.ofNullable(retry); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SseEvent sseEvent = (SseEvent) o; + return Objects.equals(event, sseEvent.event) + && Objects.equals(data, sseEvent.data) + && Objects.equals(id, sseEvent.id) + && Objects.equals(retry, sseEvent.retry); + } + + @Override + public int hashCode() { + return Objects.hash(event, data, id, retry); + } + + @Override + public String toString() { + return "SseEvent{" + "event='" + + event + '\'' + ", data=" + + data + ", id='" + + id + '\'' + ", retry=" + + retry + '}'; + } + + public static Builder builder() { + return new Builder<>(); + } + + public static final class Builder { + private String event; + private T data; + private String id; + private Long retry; + + private Builder() {} + + public Builder event(String event) { + this.event = event; + return this; + } + + public Builder data(T data) { + this.data = data; + return this; + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder retry(Long retry) { + this.retry = retry; + return this; + } + + public SseEvent build() { + return new SseEvent<>(event, data, id, retry); + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java new file mode 100644 index 000000000000..b8a888d0dda0 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java @@ -0,0 +1,228 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.type.TypeReference; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Utility class for parsing Server-Sent Events with support for discriminated unions. + *

+ * Handles two discrimination patterns: + *

    + *
  1. Data-level discrimination: The discriminator (e.g., 'type') is inside the JSON data payload. + * Jackson's polymorphic deserialization handles this automatically.
  2. + *
  3. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE envelope level. + * This requires constructing the full SSE envelope for Jackson to process.
  4. + *
+ */ +public final class SseEventParser { + + private static final Set SSE_ENVELOPE_FIELDS = new HashSet<>(Arrays.asList("event", "data", "id", "retry")); + + private SseEventParser() { + // Utility class + } + + /** + * Parse an SSE event using event-level discrimination. + *

+ * Constructs the full SSE envelope object with event, data, id, and retry fields, + * then deserializes it to the target union type. + * + * @param eventType The SSE event type (from event: field) + * @param data The SSE data content (from data: field) + * @param id The SSE event ID (from id: field), may be null + * @param retry The SSE retry value (from retry: field), may be null + * @param unionClass The target union class + * @param discriminatorProperty The property name used for discrimination (e.g., "event") + * @param The target type + * @return The deserialized object + */ + public static T parseEventLevelUnion( + String eventType, String data, String id, Long retry, Class unionClass, String discriminatorProperty) { + try { + // Determine if data should be parsed as JSON based on the variant's expected type + Object parsedData = parseDataForVariant(eventType, data, unionClass, discriminatorProperty); + + // Construct the SSE envelope object + Map envelope = new HashMap<>(); + envelope.put(discriminatorProperty, eventType); + envelope.put("data", parsedData); + if (id != null) { + envelope.put("id", id); + } + if (retry != null) { + envelope.put("retry", retry); + } + + // Serialize to JSON and deserialize to target type + String envelopeJson = ObjectMappers.JSON_MAPPER.writeValueAsString(envelope); + return ObjectMappers.JSON_MAPPER.readValue(envelopeJson, unionClass); + } catch (Exception e) { + throw new RuntimeException("Failed to parse SSE event with event-level discrimination", e); + } + } + + /** + * Parse an SSE event using data-level discrimination. + *

+ * Simply parses the data field as JSON and deserializes it to the target type. + * Jackson's polymorphic deserialization handles the discrimination automatically. + * + * @param data The SSE data content (from data: field) + * @param valueType The target type + * @param The target type + * @return The deserialized object + */ + public static T parseDataLevelUnion(String data, Class valueType) { + try { + return ObjectMappers.JSON_MAPPER.readValue(data, valueType); + } catch (Exception e) { + throw new RuntimeException("Failed to parse SSE data with data-level discrimination", e); + } + } + + /** + * Determines if the given discriminator property indicates event-level discrimination. + * Event-level discrimination occurs when the discriminator is an SSE envelope field. + * + * @param discriminatorProperty The discriminator property name + * @return true if event-level discrimination, false otherwise + */ + public static boolean isEventLevelDiscrimination(String discriminatorProperty) { + return SSE_ENVELOPE_FIELDS.contains(discriminatorProperty); + } + + /** + * Attempts to find the discriminator property from the union class's Jackson annotations. + * + * @param unionClass The union class to inspect + * @return The discriminator property name, or empty if not found + */ + public static Optional findDiscriminatorProperty(Class unionClass) { + try { + // Look for JsonTypeInfo on the class itself + JsonTypeInfo typeInfo = unionClass.getAnnotation(JsonTypeInfo.class); + if (typeInfo != null && !typeInfo.property().isEmpty()) { + return Optional.of(typeInfo.property()); + } + + // Look for inner Value interface with JsonTypeInfo + for (Class innerClass : unionClass.getDeclaredClasses()) { + typeInfo = innerClass.getAnnotation(JsonTypeInfo.class); + if (typeInfo != null && !typeInfo.property().isEmpty()) { + return Optional.of(typeInfo.property()); + } + } + } catch (Exception e) { + // Ignore reflection errors + } + return Optional.empty(); + } + + /** + * Parse the data field based on what the matching variant expects. + * If the variant expects a String for its data field, returns the raw string. + * Otherwise, parses the data as JSON. + */ + private static Object parseDataForVariant( + String eventType, String data, Class unionClass, String discriminatorProperty) { + if (data == null || data.isEmpty()) { + return data; + } + + try { + // Try to find the variant class that matches this event type + Class variantClass = findVariantClass(unionClass, eventType, discriminatorProperty); + if (variantClass != null) { + // Check if the variant expects a String for the data field + Field dataField = findField(variantClass, "data"); + if (dataField != null && String.class.equals(dataField.getType())) { + // Variant expects String - return raw data + return data; + } + } + + // Try to parse as JSON + return ObjectMappers.JSON_MAPPER.readValue(data, new TypeReference>() {}); + } catch (Exception e) { + // If JSON parsing fails, return as string + return data; + } + } + + /** + * Find the variant class that matches the given discriminator value. + */ + private static Class findVariantClass( + Class unionClass, String discriminatorValue, String discriminatorProperty) { + try { + // Look for JsonSubTypes annotation + JsonSubTypes subTypes = findJsonSubTypes(unionClass); + if (subTypes == null) { + return null; + } + + for (JsonSubTypes.Type subType : subTypes.value()) { + JsonTypeName typeName = subType.value().getAnnotation(JsonTypeName.class); + if (typeName != null && typeName.value().equals(discriminatorValue)) { + return subType.value(); + } + // Also check the name attribute of @JsonSubTypes.Type + if (subType.name().equals(discriminatorValue)) { + return subType.value(); + } + } + } catch (Exception e) { + // Ignore reflection errors + } + return null; + } + + /** + * Find JsonSubTypes annotation on the class or its inner classes. + */ + private static JsonSubTypes findJsonSubTypes(Class unionClass) { + // Check the class itself + JsonSubTypes subTypes = unionClass.getAnnotation(JsonSubTypes.class); + if (subTypes != null) { + return subTypes; + } + + // Check inner classes (for Fern-style unions with inner Value interface) + for (Class innerClass : unionClass.getDeclaredClasses()) { + subTypes = innerClass.getAnnotation(JsonSubTypes.class); + if (subTypes != null) { + return subTypes; + } + } + return null; + } + + /** + * Find a field by name in a class, including private fields. + */ + private static Field findField(Class clazz, String fieldName) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + // Check superclass + Class superClass = clazz.getSuperclass(); + if (superClass != null && superClass != Object.class) { + return findField(superClass, fieldName); + } + return null; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java new file mode 100644 index 000000000000..b4350ef9a699 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java @@ -0,0 +1,513 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implements {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * Iterable stream for parsing JSON and Server-Sent Events (SSE) data. + * Supports both newline-delimited JSON and SSE with optional stream termination. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable, Closeable { + + private static final String NEWLINE = "\n"; + private static final String DATA_PREFIX = "data:"; + + public enum StreamType { + JSON, + SSE, + SSE_EVENT_DISCRIMINATED + } + + private final Class valueType; + private final Scanner scanner; + private final StreamType streamType; + private final String messageTerminator; + private final String streamTerminator; + private final Reader sseReader; + private final String discriminatorProperty; + private boolean isClosed = false; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.valueType = valueType; + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.streamType = StreamType.JSON; + this.messageTerminator = delimiter; + this.streamTerminator = null; + this.sseReader = null; + this.discriminatorProperty = null; + } + + private Stream(Class valueType, StreamType type, Reader reader, String terminator) { + this(valueType, type, reader, terminator, null); + } + + private Stream( + Class valueType, StreamType type, Reader reader, String terminator, String discriminatorProperty) { + this.valueType = valueType; + this.streamType = type; + this.discriminatorProperty = discriminatorProperty; + if (type == StreamType.JSON) { + this.scanner = new Scanner(reader).useDelimiter(terminator); + this.messageTerminator = terminator; + this.streamTerminator = null; + this.sseReader = null; + } else { + this.scanner = null; + this.messageTerminator = NEWLINE; + this.streamTerminator = terminator; + this.sseReader = reader; + } + } + + public static Stream fromJson(Class valueType, Reader reader, String delimiter) { + return new Stream<>(valueType, reader, delimiter); + } + + public static Stream fromJson(Class valueType, Reader reader) { + return new Stream<>(valueType, reader, NEWLINE); + } + + public static Stream fromSse(Class valueType, Reader sseReader) { + return new Stream<>(valueType, StreamType.SSE, sseReader, null); + } + + public static Stream fromSse(Class valueType, Reader sseReader, String streamTerminator) { + return new Stream<>(valueType, StreamType.SSE, sseReader, streamTerminator); + } + + /** + * Creates a stream from SSE data with event-level discrimination support. + * Use this when the SSE payload is a discriminated union where the discriminator + * is an SSE envelope field (e.g., 'event'). + * + * @param valueType The class of the objects in the stream. + * @param sseReader The reader that provides the SSE data. + * @param discriminatorProperty The property name used for discrimination (e.g., "event"). + * @param The type of objects in the stream. + * @return A new Stream instance configured for SSE with event-level discrimination. + */ + public static Stream fromSseWithEventDiscrimination( + Class valueType, Reader sseReader, String discriminatorProperty) { + return new Stream<>(valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, null, discriminatorProperty); + } + + /** + * Creates a stream from SSE data with event-level discrimination support and a stream terminator. + * + * @param valueType The class of the objects in the stream. + * @param sseReader The reader that provides the SSE data. + * @param discriminatorProperty The property name used for discrimination (e.g., "event"). + * @param streamTerminator The terminator string that signals end of stream (e.g., "[DONE]"). + * @param The type of objects in the stream. + * @return A new Stream instance configured for SSE with event-level discrimination. + */ + public static Stream fromSseWithEventDiscrimination( + Class valueType, Reader sseReader, String discriminatorProperty, String streamTerminator) { + return new Stream<>( + valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, streamTerminator, discriminatorProperty); + } + + @Override + public void close() throws IOException { + if (!isClosed) { + isClosed = true; + if (scanner != null) { + scanner.close(); + } + if (sseReader != null) { + sseReader.close(); + } + } + } + + private boolean isStreamClosed() { + return isClosed; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + switch (streamType) { + case SSE: + return new SSEIterator(); + case SSE_EVENT_DISCRIMINATED: + return new SSEEventDiscriminatedIterator(); + case JSON: + default: + return new JsonIterator(); + } + } + + private final class JsonIterator implements Iterator { + + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + if (isStreamClosed()) { + return false; + } + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (isStreamClosed()) { + throw new NoSuchElementException("Stream is closed"); + } + + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = + ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class SSEIterator implements Iterator { + private Scanner sseScanner; + private T nextItem; + private boolean hasNextItem = false; + private boolean endOfStream = false; + private StringBuilder eventDataBuffer = new StringBuilder(); + private String currentEventType = null; + + private SSEIterator() { + if (sseReader != null && !isStreamClosed()) { + this.sseScanner = new Scanner(sseReader); + } else { + this.endOfStream = true; + } + } + + @Override + public boolean hasNext() { + if (isStreamClosed() || endOfStream) { + return false; + } + + if (hasNextItem) { + return true; + } + + return readNextMessage(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements in stream"); + } + + T result = nextItem; + nextItem = null; + hasNextItem = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private boolean readNextMessage() { + if (sseScanner == null || isStreamClosed()) { + endOfStream = true; + return false; + } + + try { + while (sseScanner.hasNextLine()) { + String line = sseScanner.nextLine(); + + if (line.trim().isEmpty()) { + if (eventDataBuffer.length() > 0) { + try { + nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); + hasNextItem = true; + eventDataBuffer.setLength(0); + currentEventType = null; + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); + eventDataBuffer.setLength(0); + currentEventType = null; + continue; + } + } + continue; + } + + if (line.startsWith(DATA_PREFIX)) { + String dataContent = line.substring(DATA_PREFIX.length()); + if (dataContent.startsWith(" ")) { + dataContent = dataContent.substring(1); + } + + if (eventDataBuffer.length() == 0 + && streamTerminator != null + && dataContent.trim().equals(streamTerminator)) { + endOfStream = true; + return false; + } + + if (eventDataBuffer.length() > 0) { + eventDataBuffer.append('\n'); + } + eventDataBuffer.append(dataContent); + } else if (line.startsWith("event:")) { + String eventValue = line.length() > 6 ? line.substring(6) : ""; + if (eventValue.startsWith(" ")) { + eventValue = eventValue.substring(1); + } + currentEventType = eventValue; + } else if (line.startsWith("id:")) { + // Event ID field (ignored) + } else if (line.startsWith("retry:")) { + // Retry field (ignored) + } else if (line.startsWith(":")) { + // Comment line (ignored) + } + } + + if (eventDataBuffer.length() > 0) { + try { + nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); + hasNextItem = true; + eventDataBuffer.setLength(0); + currentEventType = null; + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); + eventDataBuffer.setLength(0); + currentEventType = null; + } + } + + endOfStream = true; + return false; + + } catch (Exception e) { + System.err.println("Failed to parse SSE stream: " + e.getMessage()); + endOfStream = true; + return false; + } + } + } + + /** + * Iterator for SSE streams with event-level discrimination. + * Uses SseEventParser to construct the full SSE envelope for Jackson deserialization. + */ + private final class SSEEventDiscriminatedIterator implements Iterator { + private Scanner sseScanner; + private T nextItem; + private boolean hasNextItem = false; + private boolean endOfStream = false; + private StringBuilder eventDataBuffer = new StringBuilder(); + private String currentEventType = null; + private String currentEventId = null; + private Long currentRetry = null; + + private SSEEventDiscriminatedIterator() { + if (sseReader != null && !isStreamClosed()) { + this.sseScanner = new Scanner(sseReader); + } else { + this.endOfStream = true; + } + } + + @Override + public boolean hasNext() { + if (isStreamClosed() || endOfStream) { + return false; + } + + if (hasNextItem) { + return true; + } + + return readNextMessage(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements in stream"); + } + + T result = nextItem; + nextItem = null; + hasNextItem = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private boolean readNextMessage() { + if (sseScanner == null || isStreamClosed()) { + endOfStream = true; + return false; + } + + try { + while (sseScanner.hasNextLine()) { + String line = sseScanner.nextLine(); + + if (line.trim().isEmpty()) { + if (eventDataBuffer.length() > 0 || currentEventType != null) { + try { + // Use SseEventParser for event-level discrimination + nextItem = SseEventParser.parseEventLevelUnion( + currentEventType, + eventDataBuffer.toString(), + currentEventId, + currentRetry, + valueType, + discriminatorProperty); + hasNextItem = true; + resetEventState(); + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); + resetEventState(); + continue; + } + } + continue; + } + + if (line.startsWith(DATA_PREFIX)) { + String dataContent = line.substring(DATA_PREFIX.length()); + if (dataContent.startsWith(" ")) { + dataContent = dataContent.substring(1); + } + + if (eventDataBuffer.length() == 0 + && streamTerminator != null + && dataContent.trim().equals(streamTerminator)) { + endOfStream = true; + return false; + } + + if (eventDataBuffer.length() > 0) { + eventDataBuffer.append('\n'); + } + eventDataBuffer.append(dataContent); + } else if (line.startsWith("event:")) { + String eventValue = line.length() > 6 ? line.substring(6) : ""; + if (eventValue.startsWith(" ")) { + eventValue = eventValue.substring(1); + } + currentEventType = eventValue; + } else if (line.startsWith("id:")) { + String idValue = line.length() > 3 ? line.substring(3) : ""; + if (idValue.startsWith(" ")) { + idValue = idValue.substring(1); + } + currentEventId = idValue; + } else if (line.startsWith("retry:")) { + String retryValue = line.length() > 6 ? line.substring(6) : ""; + if (retryValue.startsWith(" ")) { + retryValue = retryValue.substring(1); + } + try { + currentRetry = Long.parseLong(retryValue.trim()); + } catch (NumberFormatException e) { + // Ignore invalid retry values + } + } else if (line.startsWith(":")) { + // Comment line (ignored) + } + } + + // Handle any remaining buffered data at end of stream + if (eventDataBuffer.length() > 0 || currentEventType != null) { + try { + nextItem = SseEventParser.parseEventLevelUnion( + currentEventType, + eventDataBuffer.toString(), + currentEventId, + currentRetry, + valueType, + discriminatorProperty); + hasNextItem = true; + resetEventState(); + return true; + } catch (Exception parseEx) { + System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); + resetEventState(); + } + } + + endOfStream = true; + return false; + + } catch (Exception e) { + System.err.println("Failed to parse SSE stream: " + e.getMessage()); + endOfStream = true; + return false; + } + } + + private void resetEventState() { + eventDataBuffer.setLength(0); + currentEventType = null; + currentEventId = null; + currentRetry = null; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java new file mode 100644 index 000000000000..a3c24e968576 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java b/seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java new file mode 100644 index 000000000000..0f39ad2d3cf4 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import com.seed.api.types.RuleExecutionContext; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleCreateRequest.Builder.class) +public final class RuleCreateRequest { + private final String name; + + private final RuleExecutionContext executionContext; + + private final Map additionalProperties; + + private RuleCreateRequest( + String name, RuleExecutionContext executionContext, Map additionalProperties) { + this.name = name; + this.executionContext = executionContext; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("executionContext") + public RuleExecutionContext getExecutionContext() { + return executionContext; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleCreateRequest && equalTo((RuleCreateRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleCreateRequest other) { + return name.equals(other.name) && executionContext.equals(other.executionContext); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.executionContext); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NameStage builder() { + return new Builder(); + } + + public interface NameStage { + ExecutionContextStage name(@NotNull String name); + + Builder from(RuleCreateRequest other); + } + + public interface ExecutionContextStage { + _FinalStage executionContext(@NotNull RuleExecutionContext executionContext); + } + + public interface _FinalStage { + RuleCreateRequest build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements NameStage, ExecutionContextStage, _FinalStage { + private String name; + + private RuleExecutionContext executionContext; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleCreateRequest other) { + name(other.getName()); + executionContext(other.getExecutionContext()); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public ExecutionContextStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("executionContext") + public _FinalStage executionContext(@NotNull RuleExecutionContext executionContext) { + this.executionContext = Objects.requireNonNull(executionContext, "executionContext must not be null"); + return this; + } + + @java.lang.Override + public RuleCreateRequest build() { + return new RuleCreateRequest(name, executionContext, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java b/seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java new file mode 100644 index 000000000000..80659000defc --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java @@ -0,0 +1,105 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = SearchRuleTypesRequest.Builder.class) +public final class SearchRuleTypesRequest { + private final Optional query; + + private final Map additionalProperties; + + private SearchRuleTypesRequest(Optional query, Map additionalProperties) { + this.query = query; + this.additionalProperties = additionalProperties; + } + + @JsonIgnore + public Optional getQuery() { + return query; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof SearchRuleTypesRequest && equalTo((SearchRuleTypesRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(SearchRuleTypesRequest other) { + return query.equals(other.query); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.query); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional query = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(SearchRuleTypesRequest other) { + query(other.getQuery()); + return this; + } + + @JsonSetter(value = "query", nulls = Nulls.SKIP) + public Builder query(Optional query) { + this.query = query; + return this; + } + + public Builder query(String query) { + this.query = Optional.ofNullable(query); + return this; + } + + public SearchRuleTypesRequest build() { + return new SearchRuleTypesRequest(query, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java new file mode 100644 index 000000000000..2502167c781a --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java @@ -0,0 +1,208 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AuditInfo.Builder.class) +public final class AuditInfo implements IAuditInfo { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private final Map additionalProperties; + + private AuditInfo( + Optional createdBy, + Optional createdDateTime, + Optional modifiedBy, + Optional modifiedDateTime, + Map additionalProperties) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + this.additionalProperties = additionalProperties; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + @java.lang.Override + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + @java.lang.Override + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + @java.lang.Override + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + @java.lang.Override + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AuditInfo && equalTo((AuditInfo) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(AuditInfo other) { + return createdBy.equals(other.createdBy) + && createdDateTime.equals(other.createdDateTime) + && modifiedBy.equals(other.modifiedBy) + && modifiedDateTime.equals(other.modifiedDateTime); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional createdBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AuditInfo other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + return this; + } + + /** + *

The user who created this resource.

+ */ + @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) + public Builder createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + public Builder createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

When this resource was created.

+ */ + @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) + public Builder createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + public Builder createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) + public Builder modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + public Builder modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) + public Builder modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + public AuditInfo build() { + return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java new file mode 100644 index 000000000000..2ff2e67bb3b4 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java @@ -0,0 +1,148 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = BaseOrg.Builder.class) +public final class BaseOrg { + private final String id; + + private final Optional metadata; + + private final Map additionalProperties; + + private BaseOrg(String id, Optional metadata, Map additionalProperties) { + this.id = id; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrg && equalTo((BaseOrg) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(BaseOrg other) { + return id.equals(other.id) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(@NotNull String id); + + Builder from(BaseOrg other); + } + + public interface _FinalStage { + BaseOrg build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(BaseOrgMetadata metadata); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(BaseOrg other) { + id(other.getId()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(BaseOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public BaseOrg build() { + return new BaseOrg(id, metadata, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java new file mode 100644 index 000000000000..a444b87ccc24 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = BaseOrgMetadata.Builder.class) +public final class BaseOrgMetadata { + private final String region; + + private final Optional tier; + + private final Map additionalProperties; + + private BaseOrgMetadata(String region, Optional tier, Map additionalProperties) { + this.region = region; + this.tier = tier; + this.additionalProperties = additionalProperties; + } + + /** + * @return Deployment region from BaseOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Subscription tier. + */ + @JsonProperty("tier") + public Optional getTier() { + return tier; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(BaseOrgMetadata other) { + return region.equals(other.region) && tier.equals(other.tier); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.tier); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from BaseOrg.

+ */ + _FinalStage region(@NotNull String region); + + Builder from(BaseOrgMetadata other); + } + + public interface _FinalStage { + BaseOrgMetadata build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Subscription tier.

+ */ + _FinalStage tier(Optional tier); + + _FinalStage tier(String tier); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional tier = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(BaseOrgMetadata other) { + region(other.getRegion()); + tier(other.getTier()); + return this; + } + + /** + *

Deployment region from BaseOrg.

+ *

Deployment region from BaseOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(@NotNull String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Subscription tier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage tier(String tier) { + this.tier = Optional.ofNullable(tier); + return this; + } + + /** + *

Subscription tier.

+ */ + @java.lang.Override + @JsonSetter(value = "tier", nulls = Nulls.SKIP) + public _FinalStage tier(Optional tier) { + this.tier = tier; + return this; + } + + @java.lang.Override + public BaseOrgMetadata build() { + return new BaseOrgMetadata(region, tier, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java new file mode 100644 index 000000000000..ca2464fede5e --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java @@ -0,0 +1,243 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CombinedEntity.Builder.class) +public final class CombinedEntity { + private final CombinedEntityStatus status; + + private final String id; + + private final Optional name; + + private final Optional summary; + + private final Map additionalProperties; + + private CombinedEntity( + CombinedEntityStatus status, + String id, + Optional name, + Optional summary, + Map additionalProperties) { + this.status = status; + this.id = id; + this.name = name; + this.summary = summary; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("status") + public CombinedEntityStatus getStatus() { + return status; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Identifiable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CombinedEntity && equalTo((CombinedEntity) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(CombinedEntity other) { + return status.equals(other.status) + && id.equals(other.id) + && name.equals(other.name) + && summary.equals(other.summary); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.status, this.id, this.name, this.summary); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static StatusStage builder() { + return new Builder(); + } + + public interface StatusStage { + IdStage status(@NotNull CombinedEntityStatus status); + + Builder from(CombinedEntity other); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + _FinalStage id(@NotNull String id); + } + + public interface _FinalStage { + CombinedEntity build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Display name from Identifiable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + + /** + *

A short summary.

+ */ + _FinalStage summary(Optional summary); + + _FinalStage summary(String summary); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements StatusStage, IdStage, _FinalStage { + private CombinedEntityStatus status; + + private String id; + + private Optional summary = Optional.empty(); + + private Optional name = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(CombinedEntity other) { + status(other.getStatus()); + id(other.getId()); + name(other.getName()); + summary(other.getSummary()); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public IdStage status(@NotNull CombinedEntityStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + /** + *

A short summary.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + /** + *

A short summary.

+ */ + @java.lang.Override + @JsonSetter(value = "summary", nulls = Nulls.SKIP) + public _FinalStage summary(Optional summary) { + this.summary = summary; + return this; + } + + /** + *

Display name from Identifiable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Identifiable.

+ */ + @java.lang.Override + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public CombinedEntity build() { + return new CombinedEntity(status, id, name, summary, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java new file mode 100644 index 000000000000..86e82fb63f85 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java @@ -0,0 +1,83 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class CombinedEntityStatus { + public static final CombinedEntityStatus ARCHIVED = new CombinedEntityStatus(Value.ARCHIVED, "archived"); + + public static final CombinedEntityStatus ACTIVE = new CombinedEntityStatus(Value.ACTIVE, "active"); + + private final Value value; + + private final String string; + + CombinedEntityStatus(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof CombinedEntityStatus && this.string.equals(((CombinedEntityStatus) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case ARCHIVED: + return visitor.visitArchived(); + case ACTIVE: + return visitor.visitActive(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static CombinedEntityStatus valueOf(String value) { + switch (value) { + case "archived": + return ARCHIVED; + case "active": + return ACTIVE; + default: + return new CombinedEntityStatus(Value.UNKNOWN, value); + } + } + + public enum Value { + ACTIVE, + + ARCHIVED, + + UNKNOWN + } + + public interface Visitor { + T visitActive(); + + T visitArchived(); + + T visitUnknown(String unknownType); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java new file mode 100644 index 000000000000..b16e91712b0e --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java @@ -0,0 +1,139 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Describable.Builder.class) +public final class Describable { + private final Optional name; + + private final Optional summary; + + private final Map additionalProperties; + + private Describable(Optional name, Optional summary, Map additionalProperties) { + this.name = name; + this.summary = summary; + this.additionalProperties = additionalProperties; + } + + /** + * @return Display name from Describable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Describable && equalTo((Describable) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Describable other) { + return name.equals(other.name) && summary.equals(other.summary); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.summary); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional name = Optional.empty(); + + private Optional summary = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(Describable other) { + name(other.getName()); + summary(other.getSummary()); + return this; + } + + /** + *

Display name from Describable.

+ */ + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

A short summary.

+ */ + @JsonSetter(value = "summary", nulls = Nulls.SKIP) + public Builder summary(Optional summary) { + this.summary = summary; + return this; + } + + public Builder summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + public Describable build() { + return new Describable(name, summary, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java new file mode 100644 index 000000000000..ef605e7427da --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java @@ -0,0 +1,105 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetailedOrg.Builder.class) +public final class DetailedOrg { + private final Optional metadata; + + private final Map additionalProperties; + + private DetailedOrg(Optional metadata, Map additionalProperties) { + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrg && equalTo((DetailedOrg) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetailedOrg other) { + return metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DetailedOrg other) { + metadata(other.getMetadata()); + return this; + } + + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public Builder metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(DetailedOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public DetailedOrg build() { + return new DetailedOrg(metadata, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java new file mode 100644 index 000000000000..e65ef4975e85 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetailedOrgMetadata.Builder.class) +public final class DetailedOrgMetadata { + private final String region; + + private final Optional domain; + + private final Map additionalProperties; + + private DetailedOrgMetadata(String region, Optional domain, Map additionalProperties) { + this.region = region; + this.domain = domain; + this.additionalProperties = additionalProperties; + } + + /** + * @return Deployment region from DetailedOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Custom domain name. + */ + @JsonProperty("domain") + public Optional getDomain() { + return domain; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetailedOrgMetadata other) { + return region.equals(other.region) && domain.equals(other.domain); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.domain); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from DetailedOrg.

+ */ + _FinalStage region(@NotNull String region); + + Builder from(DetailedOrgMetadata other); + } + + public interface _FinalStage { + DetailedOrgMetadata build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Custom domain name.

+ */ + _FinalStage domain(Optional domain); + + _FinalStage domain(String domain); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional domain = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(DetailedOrgMetadata other) { + region(other.getRegion()); + domain(other.getDomain()); + return this; + } + + /** + *

Deployment region from DetailedOrg.

+ *

Deployment region from DetailedOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(@NotNull String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Custom domain name.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage domain(String domain) { + this.domain = Optional.ofNullable(domain); + return this; + } + + /** + *

Custom domain name.

+ */ + @java.lang.Override + @JsonSetter(value = "domain", nulls = Nulls.SKIP) + public _FinalStage domain(Optional domain) { + this.domain = domain; + return this; + } + + @java.lang.Override + public DetailedOrgMetadata build() { + return new DetailedOrgMetadata(region, domain, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java new file mode 100644 index 000000000000..3270f1b1de90 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import java.time.OffsetDateTime; +import java.util.Optional; + +public interface IAuditInfo { + Optional getCreatedBy(); + + Optional getCreatedDateTime(); + + Optional getModifiedBy(); + + Optional getModifiedDateTime(); +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java new file mode 100644 index 000000000000..8a35f505a9bf --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Identifiable.Builder.class) +public final class Identifiable { + private final String id; + + private final Optional name; + + private final Map additionalProperties; + + private Identifiable(String id, Optional name, Map additionalProperties) { + this.id = id; + this.name = name; + this.additionalProperties = additionalProperties; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Identifiable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Identifiable && equalTo((Identifiable) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Identifiable other) { + return id.equals(other.id) && name.equals(other.name); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + _FinalStage id(@NotNull String id); + + Builder from(Identifiable other); + } + + public interface _FinalStage { + Identifiable build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Display name from Identifiable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional name = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(Identifiable other) { + id(other.getId()); + name(other.getName()); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + /** + *

Display name from Identifiable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Identifiable.

+ */ + @java.lang.Override + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public Identifiable build() { + return new Identifiable(id, name, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java new file mode 100644 index 000000000000..8cf63af62d4b --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java @@ -0,0 +1,171 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Organization.Builder.class) +public final class Organization { + private final String name; + + private final String id; + + private final Optional metadata; + + private final Map additionalProperties; + + private Organization( + String name, String id, Optional metadata, Map additionalProperties) { + this.name = name; + this.id = id; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Organization && equalTo((Organization) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Organization other) { + return name.equals(other.name) && id.equals(other.id) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.id, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NameStage builder() { + return new Builder(); + } + + public interface NameStage { + IdStage name(@NotNull String name); + + Builder from(Organization other); + } + + public interface IdStage { + _FinalStage id(@NotNull String id); + } + + public interface _FinalStage { + Organization build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(BaseOrgMetadata metadata); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements NameStage, IdStage, _FinalStage { + private String name; + + private String id; + + private Optional metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(Organization other) { + name(other.getName()); + id(other.getId()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public IdStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(BaseOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public Organization build() { + return new Organization(name, id, metadata, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java new file mode 100644 index 000000000000..2994a6d8c2fd --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java @@ -0,0 +1,179 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = PaginatedResult.Builder.class) +public final class PaginatedResult { + private final PagingCursors paging; + + private final List results; + + private final Map additionalProperties; + + private PaginatedResult(PagingCursors paging, List results, Map additionalProperties) { + this.paging = paging; + this.results = results; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public List getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PaginatedResult && equalTo((PaginatedResult) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(PaginatedResult other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(@NotNull PagingCursors paging); + + Builder from(PaginatedResult other); + } + + public interface _FinalStage { + PaginatedResult build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(List results); + + _FinalStage addResults(Object results); + + _FinalStage addAllResults(List results); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private List results = new ArrayList<>(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(PaginatedResult other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(@NotNull PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addAllResults(List results) { + if (results != null) { + this.results.addAll(results); + } + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addResults(Object results) { + this.results.add(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter(value = "results", nulls = Nulls.SKIP) + public _FinalStage results(List results) { + this.results.clear(); + if (results != null) { + this.results.addAll(results); + } + return this; + } + + @java.lang.Override + public PaginatedResult build() { + return new PaginatedResult(paging, results, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java new file mode 100644 index 000000000000..5453abc20ef3 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java @@ -0,0 +1,172 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = PagingCursors.Builder.class) +public final class PagingCursors { + private final String next; + + private final Optional previous; + + private final Map additionalProperties; + + private PagingCursors(String next, Optional previous, Map additionalProperties) { + this.next = next; + this.previous = previous; + this.additionalProperties = additionalProperties; + } + + /** + * @return Cursor for the next page of results. + */ + @JsonProperty("next") + public String getNext() { + return next; + } + + /** + * @return Cursor for the previous page of results. + */ + @JsonProperty("previous") + public Optional getPrevious() { + return previous; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PagingCursors && equalTo((PagingCursors) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(PagingCursors other) { + return next.equals(other.next) && previous.equals(other.previous); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.next, this.previous); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NextStage builder() { + return new Builder(); + } + + public interface NextStage { + /** + *

Cursor for the next page of results.

+ */ + _FinalStage next(@NotNull String next); + + Builder from(PagingCursors other); + } + + public interface _FinalStage { + PagingCursors build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Cursor for the previous page of results.

+ */ + _FinalStage previous(Optional previous); + + _FinalStage previous(String previous); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements NextStage, _FinalStage { + private String next; + + private Optional previous = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(PagingCursors other) { + next(other.getNext()); + previous(other.getPrevious()); + return this; + } + + /** + *

Cursor for the next page of results.

+ *

Cursor for the next page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("next") + public _FinalStage next(@NotNull String next) { + this.next = Objects.requireNonNull(next, "next must not be null"); + return this; + } + + /** + *

Cursor for the previous page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage previous(String previous) { + this.previous = Optional.ofNullable(previous); + return this; + } + + /** + *

Cursor for the previous page of results.

+ */ + @java.lang.Override + @JsonSetter(value = "previous", nulls = Nulls.SKIP) + public _FinalStage previous(Optional previous) { + this.previous = previous; + return this; + } + + @java.lang.Override + public PagingCursors build() { + return new PagingCursors(next, previous, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java new file mode 100644 index 000000000000..46c429ed089e --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class RuleExecutionContext { + public static final RuleExecutionContext PROD = new RuleExecutionContext(Value.PROD, "prod"); + + public static final RuleExecutionContext DEV = new RuleExecutionContext(Value.DEV, "dev"); + + public static final RuleExecutionContext STAGING = new RuleExecutionContext(Value.STAGING, "staging"); + + private final Value value; + + private final String string; + + RuleExecutionContext(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof RuleExecutionContext && this.string.equals(((RuleExecutionContext) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case PROD: + return visitor.visitProd(); + case DEV: + return visitor.visitDev(); + case STAGING: + return visitor.visitStaging(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static RuleExecutionContext valueOf(String value) { + switch (value) { + case "prod": + return PROD; + case "dev": + return DEV; + case "staging": + return STAGING; + default: + return new RuleExecutionContext(Value.UNKNOWN, value); + } + } + + public enum Value { + PROD, + + STAGING, + + DEV, + + UNKNOWN + } + + public interface Visitor { + T visitProd(); + + T visitStaging(); + + T visitDev(); + + T visitUnknown(String unknownType); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java new file mode 100644 index 000000000000..78c325f9465a --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java @@ -0,0 +1,394 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleResponse.Builder.class) +public final class RuleResponse implements IAuditInfo { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private final String id; + + private final String name; + + private final RuleResponseStatus status; + + private final Optional executionContext; + + private final Map additionalProperties; + + private RuleResponse( + Optional createdBy, + Optional createdDateTime, + Optional modifiedBy, + Optional modifiedDateTime, + String id, + String name, + RuleResponseStatus status, + Optional executionContext, + Map additionalProperties) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + this.id = id; + this.name = name; + this.status = status; + this.executionContext = executionContext; + this.additionalProperties = additionalProperties; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + @java.lang.Override + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + @java.lang.Override + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + @java.lang.Override + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + @java.lang.Override + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("status") + public RuleResponseStatus getStatus() { + return status; + } + + @JsonProperty("executionContext") + public Optional getExecutionContext() { + return executionContext; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleResponse && equalTo((RuleResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleResponse other) { + return createdBy.equals(other.createdBy) + && createdDateTime.equals(other.createdDateTime) + && modifiedBy.equals(other.modifiedBy) + && modifiedDateTime.equals(other.modifiedDateTime) + && id.equals(other.id) + && name.equals(other.name) + && status.equals(other.status) + && executionContext.equals(other.executionContext); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash( + this.createdBy, + this.createdDateTime, + this.modifiedBy, + this.modifiedDateTime, + this.id, + this.name, + this.status, + this.executionContext); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(@NotNull String id); + + Builder from(RuleResponse other); + } + + public interface NameStage { + StatusStage name(@NotNull String name); + } + + public interface StatusStage { + _FinalStage status(@NotNull RuleResponseStatus status); + } + + public interface _FinalStage { + RuleResponse build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

The user who created this resource.

+ */ + _FinalStage createdBy(Optional createdBy); + + _FinalStage createdBy(String createdBy); + + /** + *

When this resource was created.

+ */ + _FinalStage createdDateTime(Optional createdDateTime); + + _FinalStage createdDateTime(OffsetDateTime createdDateTime); + + /** + *

The user who last modified this resource.

+ */ + _FinalStage modifiedBy(Optional modifiedBy); + + _FinalStage modifiedBy(String modifiedBy); + + /** + *

When this resource was last modified.

+ */ + _FinalStage modifiedDateTime(Optional modifiedDateTime); + + _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); + + _FinalStage executionContext(Optional executionContext); + + _FinalStage executionContext(RuleExecutionContext executionContext); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { + private String id; + + private String name; + + private RuleResponseStatus status; + + private Optional executionContext = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional createdBy = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleResponse other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + id(other.getId()); + name(other.getName()); + status(other.getStatus()); + executionContext(other.getExecutionContext()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public StatusStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public _FinalStage status(@NotNull RuleResponseStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage executionContext(RuleExecutionContext executionContext) { + this.executionContext = Optional.ofNullable(executionContext); + return this; + } + + @java.lang.Override + @JsonSetter(value = "executionContext", nulls = Nulls.SKIP) + public _FinalStage executionContext(Optional executionContext) { + this.executionContext = executionContext; + return this; + } + + /** + *

When this resource was last modified.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @java.lang.Override + @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) + public _FinalStage modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + /** + *

The user who last modified this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @java.lang.Override + @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) + public _FinalStage modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + /** + *

When this resource was created.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

When this resource was created.

+ */ + @java.lang.Override + @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) + public _FinalStage createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + /** + *

The user who created this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

The user who created this resource.

+ */ + @java.lang.Override + @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) + public _FinalStage createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + @java.lang.Override + public RuleResponse build() { + return new RuleResponse( + createdBy, + createdDateTime, + modifiedBy, + modifiedDateTime, + id, + name, + status, + executionContext, + additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java new file mode 100644 index 000000000000..b771bb61005f --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class RuleResponseStatus { + public static final RuleResponseStatus INACTIVE = new RuleResponseStatus(Value.INACTIVE, "inactive"); + + public static final RuleResponseStatus ACTIVE = new RuleResponseStatus(Value.ACTIVE, "active"); + + public static final RuleResponseStatus DRAFT = new RuleResponseStatus(Value.DRAFT, "draft"); + + private final Value value; + + private final String string; + + RuleResponseStatus(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof RuleResponseStatus && this.string.equals(((RuleResponseStatus) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case INACTIVE: + return visitor.visitInactive(); + case ACTIVE: + return visitor.visitActive(); + case DRAFT: + return visitor.visitDraft(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static RuleResponseStatus valueOf(String value) { + switch (value) { + case "inactive": + return INACTIVE; + case "active": + return ACTIVE; + case "draft": + return DRAFT; + default: + return new RuleResponseStatus(Value.UNKNOWN, value); + } + } + + public enum Value { + ACTIVE, + + INACTIVE, + + DRAFT, + + UNKNOWN + } + + public interface Visitor { + T visitActive(); + + T visitInactive(); + + T visitDraft(); + + T visitUnknown(String unknownType); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java new file mode 100644 index 000000000000..18d016fde1bd --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java @@ -0,0 +1,170 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleType.Builder.class) +public final class RuleType { + private final String id; + + private final String name; + + private final Optional description; + + private final Map additionalProperties; + + private RuleType(String id, String name, Optional description, Map additionalProperties) { + this.id = id; + this.name = name; + this.description = description; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("description") + public Optional getDescription() { + return description; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleType && equalTo((RuleType) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleType other) { + return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.description); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(@NotNull String id); + + Builder from(RuleType other); + } + + public interface NameStage { + _FinalStage name(@NotNull String name); + } + + public interface _FinalStage { + RuleType build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + _FinalStage description(Optional description); + + _FinalStage description(String description); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, NameStage, _FinalStage { + private String id; + + private String name; + + private Optional description = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleType other) { + id(other.getId()); + name(other.getName()); + description(other.getDescription()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage description(String description) { + this.description = Optional.ofNullable(description); + return this; + } + + @java.lang.Override + @JsonSetter(value = "description", nulls = Nulls.SKIP) + public _FinalStage description(Optional description) { + this.description = description; + return this; + } + + @java.lang.Override + public RuleType build() { + return new RuleType(id, name, description, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java new file mode 100644 index 000000000000..6df397a8a58d --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java @@ -0,0 +1,163 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RuleTypeSearchResponse.Builder.class) +public final class RuleTypeSearchResponse { + private final Optional> results; + + private final PagingCursors paging; + + private final Map additionalProperties; + + private RuleTypeSearchResponse( + Optional> results, PagingCursors paging, Map additionalProperties) { + this.results = results; + this.paging = paging; + this.additionalProperties = additionalProperties; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RuleTypeSearchResponse other) { + return results.equals(other.results) && paging.equals(other.paging); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.results, this.paging); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(@NotNull PagingCursors paging); + + Builder from(RuleTypeSearchResponse other); + } + + public interface _FinalStage { + RuleTypeSearchResponse build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(RuleTypeSearchResponse other) { + results(other.getResults()); + paging(other.getPaging()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(@NotNull PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter(value = "results", nulls = Nulls.SKIP) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public RuleTypeSearchResponse build() { + return new RuleTypeSearchResponse(results, paging, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java new file mode 100644 index 000000000000..cb2b9f73c34e --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = User.Builder.class) +public final class User { + private final String id; + + private final String email; + + private final Map additionalProperties; + + private User(String id, String email, Map additionalProperties) { + this.id = id; + this.email = email; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("email") + public String getEmail() { + return email; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(User other) { + return id.equals(other.id) && email.equals(other.email); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.email); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + EmailStage id(@NotNull String id); + + Builder from(User other); + } + + public interface EmailStage { + _FinalStage email(@NotNull String email); + } + + public interface _FinalStage { + User build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, EmailStage, _FinalStage { + private String id; + + private String email; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(User other) { + id(other.getId()); + email(other.getEmail()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public EmailStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("email") + public _FinalStage email(@NotNull String email) { + this.email = Objects.requireNonNull(email, "email must not be null"); + return this; + } + + @java.lang.Override + public User build() { + return new User(id, email, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java new file mode 100644 index 000000000000..320e2df960ff --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java @@ -0,0 +1,163 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = UserSearchResponse.Builder.class) +public final class UserSearchResponse { + private final Optional> results; + + private final PagingCursors paging; + + private final Map additionalProperties; + + private UserSearchResponse( + Optional> results, PagingCursors paging, Map additionalProperties) { + this.results = results; + this.paging = paging; + this.additionalProperties = additionalProperties; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(UserSearchResponse other) { + return results.equals(other.results) && paging.equals(other.paging); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.results, this.paging); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(@NotNull PagingCursors paging); + + Builder from(UserSearchResponse other); + } + + public interface _FinalStage { + UserSearchResponse build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(UserSearchResponse other) { + results(other.getResults()); + paging(other.getPaging()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(@NotNull PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter(value = "results", nulls = Nulls.SKIP) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public UserSearchResponse build() { + return new UserSearchResponse(results, paging, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example0.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example0.java new file mode 100644 index 000000000000..22ad146af59c --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example0.java @@ -0,0 +1,13 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.SearchRuleTypesRequest; + +public class Example0 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.searchRuleTypes(SearchRuleTypesRequest.builder().build()); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example1.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example1.java new file mode 100644 index 000000000000..20488261da14 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example1.java @@ -0,0 +1,13 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.SearchRuleTypesRequest; + +public class Example1 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.searchRuleTypes(SearchRuleTypesRequest.builder().query("query").build()); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example2.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example2.java new file mode 100644 index 000000000000..e0f6cdddaea0 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example2.java @@ -0,0 +1,17 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.types.RuleExecutionContext; + +public class Example2 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.createRule(RuleCreateRequest.builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build()); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example3.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example3.java new file mode 100644 index 000000000000..7f1d77d08358 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example3.java @@ -0,0 +1,17 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.RuleCreateRequest; +import com.seed.api.types.RuleExecutionContext; + +public class Example3 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.createRule(RuleCreateRequest.builder() + .name("name") + .executionContext(RuleExecutionContext.PROD) + .build()); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example4.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example4.java new file mode 100644 index 000000000000..e680b39ebabe --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example4.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example4 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.listUsers(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example5.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example5.java new file mode 100644 index 000000000000..83e4c14bbe78 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example5.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example5 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.listUsers(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example6.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example6.java new file mode 100644 index 000000000000..e1757715676e --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example6.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example6 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getEntity(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example7.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example7.java new file mode 100644 index 000000000000..e180edb8e8a3 --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example7.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example7 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getEntity(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example8.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example8.java new file mode 100644 index 000000000000..e80deb53f21a --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example8.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example8 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getOrganization(); + } +} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example9.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example9.java new file mode 100644 index 000000000000..f5ab1ab4544d --- /dev/null +++ b/seed/java-sdk/allof/src/main/java/com/snippets/Example9.java @@ -0,0 +1,12 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; + +public class Example9 { + public static void main(String[] args) { + SeedApiClient client = + SeedApiClient.builder().url("https://api.fern.com").build(); + + client.getOrganization(); + } +} diff --git a/seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java b/seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java new file mode 100644 index 000000000000..9bf64b7a1cf5 --- /dev/null +++ b/seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java @@ -0,0 +1,120 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import static org.junit.jupiter.api.Assertions.*; + +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.Stream; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public final class StreamTest { + @Test + public void testJsonStream() { + List> messages = + Arrays.asList(createMap("message", "hello"), createMap("message", "world")); + List jsonStrings = messages.stream().map(StreamTest::mapToJson).collect(Collectors.toList()); + String input = String.join("\n", jsonStrings); + StringReader jsonInput = new StringReader(input); + Stream jsonStream = Stream.fromJson(Map.class, jsonInput); + int expectedMessages = 2; + int actualMessages = 0; + for (Map jsonObject : jsonStream) { + actualMessages++; + assertTrue(jsonObject.containsKey("message")); + } + assertEquals(expectedMessages, actualMessages); + } + + @Test + public void testSseStream() { + List> events = Arrays.asList(createMap("event", "start"), createMap("event", "end")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("event")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testSseStreamWithTerminator() { + List> events = Arrays.asList(createMap("message", "first"), createMap("message", "second")); + List sseStrings = + new ArrayList<>(events.stream().map(StreamTest::mapToSse).collect(Collectors.toList())); + sseStrings.add("data: [DONE]"); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput, "[DONE]"); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("message")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testSseEventDiscriminatedStream() { + List sseStrings = Arrays.asList( + mapToSseWithEvent("start", createMap("status", "pending")), + mapToSseWithEvent("end", createMap("status", "complete"))); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSseWithEventDiscrimination(Map.class, sseInput, "event"); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + // Event-level discrimination includes the event field in the parsed result + assertTrue(eventData.containsKey("event")); + assertTrue(eventData.containsKey("data")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testStreamResourceManagement() throws IOException { + StringReader testInput = new StringReader("{\"test\":\"data\"}"); + Stream testStream = Stream.fromJson(Map.class, testInput); + testStream.close(); + assertFalse(testStream.iterator().hasNext()); + } + + private static String mapToJson(Map map) { + try { + return ObjectMappers.JSON_MAPPER.writeValueAsString(map); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String mapToSse(Map map) { + return "data: " + mapToJson(map); + } + + private static String mapToSseWithEvent(String eventType, Map data) { + return "event: " + eventType + "\n" + "data: " + mapToJson(data); + } + + private static Map createMap(String key, String value) { + Map map = new HashMap<>(); + map.put(key, value); + return map; + } +} diff --git a/seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java new file mode 100644 index 000000000000..1686cfd803c1 --- /dev/null +++ b/seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java b/seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java new file mode 100644 index 000000000000..ead1a49af7a2 --- /dev/null +++ b/seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java @@ -0,0 +1,339 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public final class QueryStringMapperTest { + @Test + public void testObjectWithQuotedString_indexedArrays() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithQuotedString_arraysAsRepeats() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_indexedArrays() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_arraysAsRepeats() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_indexedArrays() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_arraysAsRepeats() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_indexedArrays() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_arraysAsRepeats() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_indexedArrays() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = "objects%5B0%5D%5Bvalue%5D=world&objects%5B0%5D%5Bkey%5D=hello&objects%5B1%5D" + + "%5Bvalue%5D=bar&objects%5B1%5D%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_arraysAsRepeats() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = + "objects%5Bvalue%5D=world&objects%5Bkey%5D=hello&objects%5Bvalue" + "%5D=bar&objects%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_indexedArrays() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = + "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds%5D%5B0%5D=id1&objectwitharray" + + "%5BcontactIds%5D%5B1%5D=id2&objectwitharray%5BcontactIds%5D%5B2%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_arraysAsRepeats() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds" + + "%5D=id1&objectwitharray%5BcontactIds%5D=id2&objectwitharray%5BcontactIds%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + private static String queryString(Map params, boolean arraysAsRepeats) { + HttpUrl.Builder httpUrl = HttpUrl.parse("http://www.fakewebsite.com/").newBuilder(); + params.forEach((paramName, paramValue) -> + QueryStringMapper.addQueryParameter(httpUrl, paramName, paramValue, arraysAsRepeats)); + return httpUrl.build().encodedQuery(); + } +} diff --git a/seed/php-sdk/allof-inline/.fern/metadata.json b/seed/php-sdk/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..37f759a1679d --- /dev/null +++ b/seed/php-sdk/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-php-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/.github/workflows/ci.yml b/seed/php-sdk/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..678eb6c9e141 --- /dev/null +++ b/seed/php-sdk/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test diff --git a/seed/php-sdk/allof-inline/.gitignore b/seed/php-sdk/allof-inline/.gitignore new file mode 100644 index 000000000000..31a1aeb14f35 --- /dev/null +++ b/seed/php-sdk/allof-inline/.gitignore @@ -0,0 +1,5 @@ +.idea +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/README.md b/seed/php-sdk/allof-inline/README.md new file mode 100644 index 000000000000..0b89aa3897c7 --- /dev/null +++ b/seed/php-sdk/allof-inline/README.md @@ -0,0 +1,169 @@ +# Seed PHP Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPHP) +[![php shield](https://img.shields.io/badge/php-packagist-pink)](https://packagist.org/packages/seed/seed) + +The Seed PHP library provides convenient access to the Seed APIs from PHP. + +## Table of Contents + +- [Requirements](#requirements) +- [Installation](#installation) +- [Usage](#usage) +- [Environments](#environments) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Custom Client](#custom-client) + - [Retries](#retries) + - [Timeouts](#timeouts) +- [Contributing](#contributing) + +## Requirements + +This SDK requires PHP ^8.1. + +## Installation + +```sh +composer require seed/seed +``` + +## Usage + +Instantiate and use the client with the following: + +```php +createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); + +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```php +The SDK defaults to the `Default_` environment. To use a different environment, pass it to the client constructor: + +```php +use Seed\SeedClient; +use Seed\Environments; + +$client = new SeedClient( + token: '', + options: [ + 'baseUrl' => Environments::Staging->value + ] +); +``` + +Available environments: +- `Environments::Default_` +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), an exception will be thrown. + +```php +use Seed\Exceptions\SeedApiException; +use Seed\Exceptions\SeedException; + +try { + $response = $client->createRule(...); +} catch (SeedApiException $e) { + echo 'API Exception occurred: ' . $e->getMessage() . "\n"; + echo 'Status Code: ' . $e->getCode() . "\n"; + echo 'Response Body: ' . $e->getBody() . "\n"; + // Optionally, rethrow the exception or handle accordingly. +} +``` + +## Advanced + +### Custom Client + +This SDK is built to work with any HTTP client that implements the [PSR-18](https://www.php-fig.org/psr/psr-18/) `ClientInterface`. +By default, if no client is provided, the SDK will use `php-http/discovery` to find an installed HTTP client. +However, you can pass your own client that adheres to `ClientInterface`: + +```php +use Seed\SeedClient; + +// Pass any PSR-18 compatible HTTP client implementation. +// For example, using Guzzle: +$customClient = new \GuzzleHttp\Client([ + 'timeout' => 5.0, +]); + +$client = new SeedClient(options: [ + 'client' => $customClient +]); + +// Or using Symfony HttpClient: +// $customClient = (new \Symfony\Component\HttpClient\Psr18Client()) +// ->withOptions(['timeout' => 5.0]); +// +// $client = new SeedClient(options: [ +// 'client' => $customClient +// ]); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```php +$response = $client->createRule( + ..., + options: [ + 'maxRetries' => 0 // Override maxRetries at the request level + ] +); +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `timeout` option to configure this behavior. + +```php +$response = $client->createRule( + ..., + options: [ + 'timeout' => 3.0 // Override timeout at the request level + ] +); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/php-sdk/allof-inline/composer.json b/seed/php-sdk/allof-inline/composer.json new file mode 100644 index 000000000000..ad30960a8764 --- /dev/null +++ b/seed/php-sdk/allof-inline/composer.json @@ -0,0 +1,46 @@ +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "php-http/discovery": "^1.0", + "php-http/multipart-stream-builder": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12", + "guzzlehttp/guzzle": "^7.4" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src tests --memory-limit=1G" + } +} \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/phpstan.neon b/seed/php-sdk/allof-inline/phpstan.neon new file mode 100644 index 000000000000..780706b8f8a2 --- /dev/null +++ b/seed/php-sdk/allof-inline/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: max + reportUnmatchedIgnoredErrors: false + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/phpunit.xml b/seed/php-sdk/allof-inline/phpunit.xml new file mode 100644 index 000000000000..54630a51163c --- /dev/null +++ b/seed/php-sdk/allof-inline/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/reference.md b/seed/php-sdk/allof-inline/reference.md new file mode 100644 index 000000000000..b824179f7851 --- /dev/null +++ b/seed/php-sdk/allof-inline/reference.md @@ -0,0 +1,171 @@ +# Reference +
$client->searchRuleTypes($request) -> ?RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->searchRuleTypes( + new SearchRuleTypesRequest([]), +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**$query:** `?string` + +
+
+
+
+ + +
+
+
+ +
$client->createRule($request) -> ?RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**$name:** `string` + +
+
+ +
+
+ +**$executionContext:** `string` + +
+
+
+
+ + +
+
+
+ +
$client->listUsers() -> ?UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->listUsers(); +``` +
+
+
+
+ + +
+
+
+ +
$client->getEntity() -> ?CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->getEntity(); +``` +
+
+
+
+ + +
+
+
+ +
$client->getOrganization() -> ?Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->getOrganization(); +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/php-sdk/allof-inline/snippet.json b/seed/php-sdk/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php new file mode 100644 index 000000000000..5e1283e2b6f6 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php b/seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php new file mode 100644 index 000000000000..8ac806af0325 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php @@ -0,0 +1,56 @@ + + */ + private array $responses = []; + + /** + * @var array + */ + private array $requests = []; + + /** + * @param ResponseInterface ...$responses + */ + public function append(ResponseInterface ...$responses): void + { + foreach ($responses as $response) { + $this->responses[] = $response; + } + } + + /** + * @param RequestInterface $request + * @return ResponseInterface + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $this->requests[] = $request; + + if (empty($this->responses)) { + throw new RuntimeException('No more responses in the queue. Add responses using append().'); + } + + return array_shift($this->responses); + } + + /** + * @return ?RequestInterface + */ + public function getLastRequest(): ?RequestInterface + { + if (empty($this->requests)) { + return null; + } + return $this->requests[count($this->requests) - 1]; + } + + /** + * @return int + */ + public function getRequestCount(): int + { + return count($this->requests); + } + + /** + * Returns the number of remaining responses in the queue. + * + * @return int + */ + public function count(): int + { + return count($this->responses); + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Client/RawClient.php b/seed/php-sdk/allof-inline/src/Core/Client/RawClient.php new file mode 100644 index 000000000000..14716c7d678b --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Client/RawClient.php @@ -0,0 +1,310 @@ + $headers + */ + private array $headers; + + /** + * @var ?(callable(): array) $getAuthHeaders + */ + private $getAuthHeaders; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * getAuthHeaders?: callable(): array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = HttpClientBuilder::build( + $this->options['client'] ?? null, + $this->options['maxRetries'] ?? 2, + ); + $this->requestFactory = HttpClientBuilder::requestFactory(); + $this->streamFactory = HttpClientBuilder::streamFactory(); + $this->headers = $this->options['headers'] ?? []; + $this->getAuthHeaders = $this->options['getAuthHeaders'] ?? null; + } + + /** + * @param BaseApiRequest $request + * @param ?array{ + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ?array $options = null, + ): ResponseInterface { + $opts = $options ?? []; + $httpRequest = $this->buildRequest($request, $opts); + + $timeout = $opts['timeout'] ?? $this->options['timeout'] ?? null; + $maxRetries = $opts['maxRetries'] ?? null; + + return $this->client->send($httpRequest, $timeout, $maxRetries); + } + + /** + * @param BaseApiRequest $request + * @param array{ + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return RequestInterface + */ + private function buildRequest( + BaseApiRequest $request, + array $options + ): RequestInterface { + $url = $this->buildUrl($request, $options); + $headers = $this->encodeHeaders($request, $options); + + $httpRequest = $this->requestFactory->createRequest( + $request->method->name, + $url, + ); + + // Encode body and, for multipart, capture the Content-Type with boundary. + if ($request instanceof MultipartApiRequest && $request->body !== null) { + $builder = new MultipartStreamBuilder($this->streamFactory); + $request->body->addToBuilder($builder); + $httpRequest = $httpRequest->withBody($builder->build()); + $headers['Content-Type'] = "multipart/form-data; boundary={$builder->getBoundary()}"; + } else { + $body = $this->encodeRequestBody($request, $options); + if ($body !== null) { + $httpRequest = $httpRequest->withBody($body); + } + } + + foreach ($headers as $name => $value) { + $httpRequest = $httpRequest->withHeader($name, $value); + } + + return $httpRequest; + } + + /** + * @param BaseApiRequest $request + * @param array{ + * headers?: array, + * } $options + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request, + array $options, + ): array { + $authHeaders = $this->getAuthHeaders !== null ? ($this->getAuthHeaders)() : []; + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + [ + "Content-Type" => "application/json", + "Accept" => "*/*", + ], + $this->headers, + $authHeaders, + $request->headers, + $options['headers'] ?? [], + ), + MultipartApiRequest::class => array_merge( + $this->headers, + $authHeaders, + $request->headers, + $options['headers'] ?? [], + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + /** + * @param BaseApiRequest $request + * @param array{ + * bodyProperties?: array, + * } $options + * @return ?StreamInterface + */ + private function encodeRequestBody( + BaseApiRequest $request, + array $options, + ): ?StreamInterface { + if ($request instanceof JsonApiRequest) { + return $request->body === null ? null : $this->streamFactory->createStream( + JsonEncoder::encode( + $this->buildJsonBody( + $request->body, + $options, + ), + ) + ); + } + + if ($request instanceof MultipartApiRequest) { + return null; + } + + throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)); + } + + /** + * @param mixed $body + * @param array{ + * bodyProperties?: array, + * } $options + * @return mixed + */ + private function buildJsonBody( + mixed $body, + array $options, + ): mixed { + $overrideProperties = $options['bodyProperties'] ?? []; + if (is_array($body) && (empty($body) || self::isSequential($body))) { + return array_merge($body, $overrideProperties); + } + + if ($body instanceof JsonSerializable) { + $result = $body->jsonSerialize(); + } else { + $result = $body; + } + if (is_array($result)) { + $result = array_merge($result, $overrideProperties); + if (empty($result)) { + // force to be serialized as {} instead of [] + return (object)($result); + } + } + + return $result; + } + + /** + * @param BaseApiRequest $request + * @param array{ + * queryParameters?: array, + * } $options + * @return string + */ + private function buildUrl( + BaseApiRequest $request, + array $options, + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + $query = array_merge( + $request->query, + $options['queryParameters'] ?? [], + ); + if (!empty($query)) { + $url .= '?' . $this->encodeQuery($query); + } + return $url; + } + + /** + * @param array $query + * @return string + */ + private function encodeQuery(array $query): string + { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue(mixed $value): string + { + if (is_string($value)) { + return urlencode($value); + } + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(JsonEncoder::encode($value)); + } + + /** + * Check if an array is sequential, not associative. + * @param mixed[] $arr + * @return bool + */ + private static function isSequential(array $arr): bool + { + if (empty($arr)) { + return false; + } + $length = count($arr); + $keys = array_keys($arr); + for ($i = 0; $i < $length; $i++) { + if ($keys[$i] !== $i) { + return false; + } + } + return true; + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php new file mode 100644 index 000000000000..b16170cf2805 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php @@ -0,0 +1,241 @@ +client = $client; + $this->maxRetries = $maxRetries; + $this->baseDelay = $baseDelay; + $this->sleepFunction = $sleepFunction ?? 'usleep'; + } + + /** + * @param RequestInterface $request + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + return $this->send($request); + } + + /** + * Sends a request with optional per-request timeout and retry overrides. + * + * When a Guzzle or Symfony PSR-18 client is detected, the timeout is + * forwarded via the client's native API. For other PSR-18 clients the + * timeout value is silently ignored. + * + * @param RequestInterface $request + * @param ?float $timeout Timeout in seconds, or null to use the client default. + * @param ?int $maxRetries Maximum retry attempts, or null to use the client default. + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + public function send( + RequestInterface $request, + ?float $timeout = null, + ?int $maxRetries = null, + ): ResponseInterface { + $maxRetries = $maxRetries ?? $this->maxRetries; + $retryAttempt = 0; + $lastResponse = null; + + while (true) { + try { + $lastResponse = $this->doSend($request, $timeout); + if (!$this->shouldRetry($retryAttempt, $maxRetries, $lastResponse)) { + return $lastResponse; + } + } catch (ClientExceptionInterface $e) { + if ($retryAttempt >= $maxRetries) { + throw $e; + } + } + + $retryAttempt++; + $delay = $this->getRetryDelay($retryAttempt, $lastResponse); + ($this->sleepFunction)($delay * 1000); // Convert milliseconds to microseconds + + // Rewind the request body so retries don't send an empty body. + $request->getBody()->rewind(); + } + } + + /** + * Dispatches the request to the underlying client, forwarding the timeout + * option to Guzzle or Symfony when available. + * + * @param RequestInterface $request + * @param ?float $timeout + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + private function doSend(RequestInterface $request, ?float $timeout): ResponseInterface + { + static $warned = false; + + if ($timeout === null) { + return $this->client->sendRequest($request); + } + + if (class_exists('GuzzleHttp\ClientInterface') + && $this->client instanceof \GuzzleHttp\ClientInterface + ) { + return $this->client->send($request, ['timeout' => $timeout]); + } + if (class_exists('Symfony\Component\HttpClient\Psr18Client') + && $this->client instanceof \Symfony\Component\HttpClient\Psr18Client + ) { + /** @var ClientInterface $clientWithTimeout */ + $clientWithTimeout = $this->client->withOptions(['timeout' => $timeout]); + return $clientWithTimeout->sendRequest($request); + } + + if ($warned) { + return $this->client->sendRequest($request); + } + $warned = true; + trigger_error( + 'Timeout option is not supported for the current PSR-18 client (' + . get_class($this->client) + . '). Use Guzzle or Symfony HttpClient for timeout support.', + E_USER_WARNING, + ); + return $this->client->sendRequest($request); + } + + /** + * @param int $retryAttempt + * @param int $maxRetries + * @param ?ResponseInterface $response + * @return bool + */ + private function shouldRetry( + int $retryAttempt, + int $maxRetries, + ?ResponseInterface $response = null, + ): bool { + if ($retryAttempt >= $maxRetries) { + return false; + } + + if ($response !== null) { + return $response->getStatusCode() >= 500 || + in_array($response->getStatusCode(), self::RETRY_STATUS_CODES); + } + + return false; + } + + /** + * Calculate the retry delay based on response headers or exponential backoff. + * + * @param int $retryAttempt + * @param ?ResponseInterface $response + * @return int milliseconds + */ + private function getRetryDelay(int $retryAttempt, ?ResponseInterface $response): int + { + if ($response !== null) { + // Check Retry-After header + $retryAfter = $response->getHeaderLine('Retry-After'); + if ($retryAfter !== '') { + // Try parsing as integer (seconds) + if (is_numeric($retryAfter)) { + $retryAfterSeconds = (int)$retryAfter; + if ($retryAfterSeconds > 0) { + return min($retryAfterSeconds * 1000, self::MAX_RETRY_DELAY); + } + } + + // Try parsing as HTTP date + $retryAfterDate = strtotime($retryAfter); + if ($retryAfterDate !== false) { + $delay = ($retryAfterDate - time()) * 1000; + if ($delay > 0) { + return min(max($delay, 0), self::MAX_RETRY_DELAY); + } + } + } + + // Check X-RateLimit-Reset header + $rateLimitReset = $response->getHeaderLine('X-RateLimit-Reset'); + if ($rateLimitReset !== '' && is_numeric($rateLimitReset)) { + $resetTime = (int)$rateLimitReset; + $delay = ($resetTime * 1000) - (int)(microtime(true) * 1000); + if ($delay > 0) { + return $this->addPositiveJitter(min($delay, self::MAX_RETRY_DELAY)); + } + } + } + + // Fall back to exponential backoff with symmetric jitter + return $this->addSymmetricJitter( + min($this->exponentialDelay($retryAttempt), self::MAX_RETRY_DELAY) + ); + } + + /** + * Add positive jitter (0% to +20%) to the delay. + * + * @param int $delay + * @return int + */ + private function addPositiveJitter(int $delay): int + { + $jitterMultiplier = 1 + (mt_rand() / mt_getrandmax()) * self::JITTER_FACTOR; + return (int)($delay * $jitterMultiplier); + } + + /** + * Add symmetric jitter (-10% to +10%) to the delay. + * + * @param int $delay + * @return int + */ + private function addSymmetricJitter(int $delay): int + { + $jitterMultiplier = 1 + ((mt_rand() / mt_getrandmax()) - 0.5) * self::JITTER_FACTOR; + return (int)($delay * $jitterMultiplier); + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + private function exponentialDelay(int $retryAttempt): int + { + return 2 ** ($retryAttempt - 1) * $this->baseDelay; + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php new file mode 100644 index 000000000000..8fdf493606e6 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php @@ -0,0 +1,28 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php new file mode 100644 index 000000000000..2da34087c644 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: $json"); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php new file mode 100644 index 000000000000..1a250c614e45 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,218 @@ + $data The array to be deserialized. + * @param array $type The type definition from the annotation. + * @return array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (\Throwable) { + // Catching Throwable instead of Exception to handle TypeError + // that occurs when assigning null to non-nullable typed properties + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: $type" + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + /** @var array $data */ + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement JsonSerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, JsonSerializableType::class)) { + throw new JsonException("$type is not a subclass of JsonSerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php new file mode 100644 index 000000000000..0dbf3fcc9948 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ + Extra properties from JSON that don't map to class properties */ + private array $__additionalProperties = []; + + /** @var array Properties that have been explicitly set via setter methods */ + private array $__explicitlySetProperties = []; + + /** + * Serializes the object to a JSON string. + * + * @return string JSON-encoded string representation of the object. + * @throws Exception If encoding fails. + */ + public function toJson(): string + { + $serializedObject = $this->jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey === null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === Date::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + // Include the value if it's not null, OR if it was explicitly set (even to null) + if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { + $result[$jsonKey] = $value; + } + } + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + /** @var array $decodedJson */ + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + $properties = []; + $additionalProperties = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + $properties[$jsonKey] = $property; + } + + foreach ($data as $jsonKey => $value) { + if (!isset($properties[$jsonKey])) { + // This JSON key doesn't map to any class property - add it to additionalProperties + $additionalProperties[$jsonKey] = $value; + continue; + } + + $property = $properties[$jsonKey]; + + // Handle Date annotation + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === Date::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle Array annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + /** @var array $arrayValue */ + $arrayValue = $value; + $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); + } + + $args[$property->getName()] = $value; + } + + // Fill in any missing properties with defaults + foreach ($properties as $property) { + if (!isset($args[$property->getName()])) { + $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; + } + } + + // @phpstan-ignore-next-line + $result = new static($args); + $result->__additionalProperties = $additionalProperties; + return $result; + } + + /** + * Get properties from JSON that weren't mapped to class fields + * @return array + */ + public function getAdditionalProperties(): array + { + return $this->__additionalProperties; + } + + /** + * Mark a property as explicitly set. + * This ensures the property will be included in JSON serialization even if null. + * + * @param string $propertyName The name of the property to mark as explicitly set. + */ + protected function _setField(string $propertyName): void + { + $this->__explicitlySetProperties[$propertyName] = true; + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php new file mode 100644 index 000000000000..f7d80ed5e8f3 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php @@ -0,0 +1,205 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + $formatted = $date->format(Constant::DateTimeFormat); + if (str_ends_with($formatted, '+00:00')) { + return substr($formatted, 0, -6) . 'Z'; + } + return $formatted; + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param array $data The array to be serialized. + * @param array $type The type definition from the annotation. + * @return array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: $unionType" + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/Utils.php b/seed/php-sdk/allof-inline/src/Core/Json/Utils.php new file mode 100644 index 000000000000..4099b8253005 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Json/Utils.php @@ -0,0 +1,62 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return int|string The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): int|string + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + // PHP arrays don't support float keys; truncate to int + 'float' => (int)$key, + 'string' => (string)$key, + default => is_int($key) ? $key : (string)$key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php new file mode 100644 index 000000000000..7760366456c8 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php @@ -0,0 +1,28 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param ?MultipartFormData $body The multipart form data for the request (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly ?MultipartFormData $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php new file mode 100644 index 000000000000..911a28b6ad64 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php @@ -0,0 +1,58 @@ + + */ + private array $parts = []; + + /** + * Adds a new part to the multipart form data. + * + * @param string $name + * @param string|int|bool|float|StreamInterface $value + * @param ?string $contentType + */ + public function add( + string $name, + string|int|bool|float|StreamInterface $value, + ?string $contentType = null, + ): void { + $headers = $contentType !== null ? ['Content-Type' => $contentType] : null; + $this->addPart( + new MultipartFormDataPart( + name: $name, + value: $value, + headers: $headers, + ) + ); + } + + /** + * Adds a new part to the multipart form data. + * + * @param MultipartFormDataPart $part + */ + public function addPart(MultipartFormDataPart $part): void + { + $this->parts[] = $part; + } + + /** + * Adds all parts to a MultipartStreamBuilder. + * + * @param MultipartStreamBuilder $builder + */ + public function addToBuilder(MultipartStreamBuilder $builder): void + { + foreach ($this->parts as $part) { + $part->addToBuilder($builder); + } + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php new file mode 100644 index 000000000000..4db35e58ae37 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php @@ -0,0 +1,62 @@ + + */ + private ?array $headers; + + /** + * @param string $name + * @param string|bool|float|int|StreamInterface $value + * @param ?string $filename + * @param ?array $headers + */ + public function __construct( + string $name, + string|bool|float|int|StreamInterface $value, + ?string $filename = null, + ?array $headers = null + ) { + $this->name = $name; + $this->contents = $value instanceof StreamInterface ? $value : (string)$value; + $this->filename = $filename; + $this->headers = $headers; + } + + /** + * Adds this part to a MultipartStreamBuilder. + * + * @param MultipartStreamBuilder $builder + */ + public function addToBuilder(MultipartStreamBuilder $builder): void + { + $options = array_filter([ + 'filename' => $this->filename, + 'headers' => $this->headers, + ], fn ($value) => $value !== null); + + $builder->addResource($this->name, $this->contents, $options); + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php b/seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php new file mode 100644 index 000000000000..a26d29008ec3 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/allof-inline/src/Core/Types/Constant.php b/seed/php-sdk/allof-inline/src/Core/Types/Constant.php new file mode 100644 index 000000000000..5ac4518cc6d6 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/allof-inline/src/Environments.php b/seed/php-sdk/allof-inline/src/Environments.php new file mode 100644 index 000000000000..43d64cdb5e37 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Environments.php @@ -0,0 +1,8 @@ +body = $body; + parent::__construct($message, $statusCode, $previous); + } + + /** + * Returns the body of the response that triggered the exception. + * + * @return mixed + */ + public function getBody(): mixed + { + return $this->body; + } + + /** + * @return string + */ + public function __toString(): string + { + if (empty($this->body)) { + return $this->message . '; Status Code: ' . $this->getCode() . "\n"; + } + return $this->message . '; Status Code: ' . $this->getCode() . '; Body: ' . print_r($this->body, true) . "\n"; + } +} diff --git a/seed/php-sdk/allof-inline/src/Exceptions/SeedException.php b/seed/php-sdk/allof-inline/src/Exceptions/SeedException.php new file mode 100644 index 000000000000..457035276737 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Exceptions/SeedException.php @@ -0,0 +1,12 @@ + $executionContext + */ + #[JsonProperty('executionContext')] + public string $executionContext; + + /** + * @param array{ + * name: string, + * executionContext: value-of, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->executionContext = $values['executionContext']; + } +} diff --git a/seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php b/seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php new file mode 100644 index 000000000000..bd49b88a3872 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php @@ -0,0 +1,24 @@ +query = $values['query'] ?? null; + } +} diff --git a/seed/php-sdk/allof-inline/src/SeedClient.php b/seed/php-sdk/allof-inline/src/SeedClient.php new file mode 100644 index 000000000000..63b560f9b6c7 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/SeedClient.php @@ -0,0 +1,302 @@ +, + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator + */ + private array $options; + + /** + * @var RawClient $client + */ + private RawClient $client; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * } $options + */ + public function __construct( + ?array $options = null, + ) { + $defaultHeaders = [ + 'X-Fern-Language' => 'PHP', + 'X-Fern-SDK-Name' => 'Seed', + 'X-Fern-SDK-Version' => '0.0.1', + 'User-Agent' => 'seed/seed/0.0.1', + ]; + + $this->options = $options ?? []; + + $this->options['headers'] = array_merge( + $defaultHeaders, + $this->options['headers'] ?? [], + ); + + $this->client = new RawClient( + options: $this->options, + ); + } + + /** + * @param SearchRuleTypesRequest $request + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?RuleTypeSearchResponse + * @throws SeedException + * @throws SeedApiException + */ + public function searchRuleTypes(SearchRuleTypesRequest $request = new SearchRuleTypesRequest(), ?array $options = null): ?RuleTypeSearchResponse + { + $options = array_merge($this->options, $options ?? []); + $query = []; + if ($request->query != null) { + $query['query'] = $request->query; + } + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "rule-types", + method: HttpMethod::GET, + query: $query, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return RuleTypeSearchResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param RuleCreateRequest $request + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?RuleResponse + * @throws SeedException + * @throws SeedApiException + */ + public function createRule(RuleCreateRequest $request, ?array $options = null): ?RuleResponse + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "rules", + method: HttpMethod::POST, + body: $request, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return RuleResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?UserSearchResponse + * @throws SeedException + * @throws SeedApiException + */ + public function listUsers(?array $options = null): ?UserSearchResponse + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "users", + method: HttpMethod::GET, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return UserSearchResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?CombinedEntity + * @throws SeedException + * @throws SeedApiException + */ + public function getEntity(?array $options = null): ?CombinedEntity + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "entities", + method: HttpMethod::GET, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return CombinedEntity::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?Organization + * @throws SeedException + * @throws SeedApiException + */ + public function getOrganization(?array $options = null): ?Organization + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "organizations", + method: HttpMethod::GET, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return Organization::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/AuditInfo.php b/seed/php-sdk/allof-inline/src/Types/AuditInfo.php new file mode 100644 index 000000000000..49fd43fd80f3 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/AuditInfo.php @@ -0,0 +1,63 @@ +createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/BaseOrg.php b/seed/php-sdk/allof-inline/src/Types/BaseOrg.php new file mode 100644 index 000000000000..5d403a06b445 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/BaseOrg.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php b/seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php new file mode 100644 index 000000000000..e974fa97bb1a --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->tier = $values['tier'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/CombinedEntity.php b/seed/php-sdk/allof-inline/src/Types/CombinedEntity.php new file mode 100644 index 000000000000..53a5e19ec1e6 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/CombinedEntity.php @@ -0,0 +1,58 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @param array{ + * id: string, + * status: value-of, + * name?: ?string, + * summary?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->id = $values['id']; + $this->name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + $this->status = $values['status']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php b/seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php new file mode 100644 index 000000000000..63aba6a34b11 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php @@ -0,0 +1,9 @@ +name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/DetailedOrg.php b/seed/php-sdk/allof-inline/src/Types/DetailedOrg.php new file mode 100644 index 000000000000..c53ac92b926a --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/DetailedOrg.php @@ -0,0 +1,34 @@ +metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php b/seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php new file mode 100644 index 000000000000..ea98caf5f68a --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->domain = $values['domain'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/Identifiable.php b/seed/php-sdk/allof-inline/src/Types/Identifiable.php new file mode 100644 index 000000000000..83c1714a7eb8 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/Identifiable.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->name = $values['name'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/Organization.php b/seed/php-sdk/allof-inline/src/Types/Organization.php new file mode 100644 index 000000000000..823e2cf645ac --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/Organization.php @@ -0,0 +1,50 @@ +id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + $this->name = $values['name']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php b/seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php new file mode 100644 index 000000000000..feed3e97ef52 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->domain = $values['domain'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/PaginatedResult.php b/seed/php-sdk/allof-inline/src/Types/PaginatedResult.php new file mode 100644 index 000000000000..c24634881db3 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/PaginatedResult.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType(['mixed'])] + public array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/PagingCursors.php b/seed/php-sdk/allof-inline/src/Types/PagingCursors.php new file mode 100644 index 000000000000..b4a3809a1dbb --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/PagingCursors.php @@ -0,0 +1,42 @@ +next = $values['next']; + $this->previous = $values['previous'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php b/seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php new file mode 100644 index 000000000000..bc98b8b52369 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php @@ -0,0 +1,10 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @var ?value-of $executionContext + */ + #[JsonProperty('executionContext')] + public ?string $executionContext; + + /** + * @param array{ + * id: string, + * name: string, + * status: value-of, + * createdBy?: ?string, + * createdDateTime?: ?DateTime, + * modifiedBy?: ?string, + * modifiedDateTime?: ?DateTime, + * executionContext?: ?value-of, + * } $values + */ + public function __construct( + array $values, + ) { + $this->createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + $this->id = $values['id']; + $this->name = $values['name']; + $this->status = $values['status']; + $this->executionContext = $values['executionContext'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php b/seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php new file mode 100644 index 000000000000..cef363294474 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php @@ -0,0 +1,10 @@ +id = $values['id']; + $this->name = $values['name']; + $this->description = $values['description'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php b/seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php new file mode 100644 index 000000000000..af3e5734a366 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([RuleType::class])] + public ?array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/User.php b/seed/php-sdk/allof-inline/src/Types/User.php new file mode 100644 index 000000000000..aab3174ca08c --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/User.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->email = $values['email']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php b/seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php new file mode 100644 index 000000000000..7c572643b3c1 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([User::class])] + public ?array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof-inline/src/Utils/File.php b/seed/php-sdk/allof-inline/src/Utils/File.php new file mode 100644 index 000000000000..ee2af27b8909 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/Utils/File.php @@ -0,0 +1,129 @@ +filename = $filename; + $this->contentType = $contentType; + $this->stream = $stream; + } + + /** + * Creates a File instance from a filepath. + * + * @param string $filepath + * @param ?string $filename + * @param ?string $contentType + * @return File + * @throws Exception + */ + public static function createFromFilepath( + string $filepath, + ?string $filename = null, + ?string $contentType = null, + ): File { + $resource = @fopen($filepath, 'r'); + if (!$resource) { + throw new Exception("Unable to open file $filepath"); + } + $stream = Psr17FactoryDiscovery::findStreamFactory()->createStreamFromResource($resource); + if (!$stream->isReadable()) { + throw new Exception("File $filepath is not readable"); + } + return new self( + stream: $stream, + filename: $filename ?? basename($filepath), + contentType: $contentType, + ); + } + + /** + * Creates a File instance from a string. + * + * @param string $content + * @param ?string $filename + * @param ?string $contentType + * @return File + */ + public static function createFromString( + string $content, + ?string $filename, + ?string $contentType = null, + ): File { + return new self( + stream: Psr17FactoryDiscovery::findStreamFactory()->createStream($content), + filename: $filename, + contentType: $contentType, + ); + } + + /** + * Maps this File into a multipart form data part. + * + * @param string $name The name of the multipart form data part. + * @param ?string $contentType Overrides the Content-Type associated with the file, if any. + * @return MultipartFormDataPart + */ + public function toMultipartFormDataPart(string $name, ?string $contentType = null): MultipartFormDataPart + { + $contentType ??= $this->contentType; + $headers = $contentType !== null + ? ['Content-Type' => $contentType] + : null; + + return new MultipartFormDataPart( + name: $name, + value: $this->stream, + filename: $this->filename, + headers: $headers, + ); + } + + /** + * Closes the file stream. + */ + public function close(): void + { + $this->stream->close(); + } + + /** + * Destructor to ensure stream is closed. + */ + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable) { + // Swallow errors during garbage collection to avoid fatal errors. + } + } +} diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php new file mode 100644 index 000000000000..1e384a760b83 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php @@ -0,0 +1,15 @@ + 'https://api.fern.com', + ], +); +$client->searchRuleTypes( + new SearchRuleTypesRequest([]), +); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php new file mode 100644 index 000000000000..826582f7bc27 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php @@ -0,0 +1,17 @@ + 'https://api.fern.com', + ], +); +$client->searchRuleTypes( + new SearchRuleTypesRequest([ + 'query' => 'query', + ]), +); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php new file mode 100644 index 000000000000..5539210a6505 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php @@ -0,0 +1,19 @@ + 'https://api.fern.com', + ], +); +$client->createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php new file mode 100644 index 000000000000..5539210a6505 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php @@ -0,0 +1,19 @@ + 'https://api.fern.com', + ], +); +$client->createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php new file mode 100644 index 000000000000..477f8f61c34c --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->listUsers(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php new file mode 100644 index 000000000000..477f8f61c34c --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->listUsers(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php new file mode 100644 index 000000000000..4b4021abb4a6 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getEntity(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php new file mode 100644 index 000000000000..4b4021abb4a6 --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getEntity(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php new file mode 100644 index 000000000000..bcf929bea40c --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getOrganization(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php new file mode 100644 index 000000000000..bcf929bea40c --- /dev/null +++ b/seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getOrganization(); diff --git a/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php b/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php new file mode 100644 index 000000000000..df36dc918894 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php @@ -0,0 +1,1074 @@ +name = $values['name']; + } + + /** + * @return string + */ + public function getName(): ?string + { + return $this->name; + } +} + +class RawClientTest extends TestCase +{ + private string $baseUrl = 'https://api.example.com'; + private MockHttpClient $mockClient; + private RawClient $rawClient; + + protected function setUp(): void + { + $this->mockClient = new MockHttpClient(); + $this->rawClient = new RawClient(['client' => $this->mockClient, 'maxRetries' => 0]); + } + + /** + * @throws ClientExceptionInterface + */ + public function testHeaders(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + /** + * @throws ClientExceptionInterface + */ + public function testQueryParameters(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + /** + * @throws ClientExceptionInterface + */ + public function testJsonBody(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(JsonEncoder::encode($body), (string)$lastRequest->getBody()); + } + + public function testAdditionalHeaders(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = new JsonRequest([ + 'name' => 'john.doe' + ]); + $headers = [ + 'X-API-Version' => '1.0.0', + ]; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + $headers, + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'headers' => [ + 'X-Tenancy' => 'test' + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('1.0.0', $lastRequest->getHeaderLine('X-API-Version')); + $this->assertEquals('test', $lastRequest->getHeaderLine('X-Tenancy')); + } + + public function testOverrideAdditionalHeaders(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = new JsonRequest([ + 'name' => 'john.doe' + ]); + $headers = [ + 'X-API-Version' => '1.0.0', + ]; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + $headers, + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'headers' => [ + 'X-API-Version' => '2.0.0' + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('2.0.0', $lastRequest->getHeaderLine('X-API-Version')); + } + + public function testAdditionalBodyProperties(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = new JsonRequest([ + 'name' => 'john.doe' + ]); + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'bodyProperties' => [ + 'age' => 42 + ] + ] + ); + + $expectedJson = [ + 'name' => 'john.doe', + 'age' => 42 + ]; + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); + } + + public function testOverrideAdditionalBodyProperties(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = [ + 'name' => 'john.doe' + ]; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'bodyProperties' => [ + 'name' => 'jane.doe' + ] + ] + ); + + $expectedJson = [ + 'name' => 'jane.doe', + ]; + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); + } + + public function testAdditionalQueryParameters(): void + { + $this->mockClient->append(self::createResponse(200)); + + $query = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + $query, + [] + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'queryParameters' => [ + 'extra' => 42 + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('key=value&extra=42', $lastRequest->getUri()->getQuery()); + } + + public function testOverrideQueryParameters(): void + { + $this->mockClient->append(self::createResponse(200)); + + $query = ['key' => 'invalid']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + $query, + [] + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'queryParameters' => [ + 'key' => 'value' + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('key=value', $lastRequest->getUri()->getQuery()); + } + + public function testDefaultRetries(): void + { + $this->mockClient->append(self::createResponse(500)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET + ); + + $response = $this->rawClient->sendRequest($request); + $this->assertEquals(500, $response->getStatusCode()); + $this->assertEquals(0, $this->mockClient->count()); + } + + /** + * @throws ClientExceptionInterface + */ + public function testExplicitRetriesSuccess(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(200)); + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(0, $mockClient->count()); + } + + public function testExplicitRetriesFailure(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(500)); + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(500, $response->getStatusCode()); + $this->assertEquals(0, $mockClient->count()); + } + + /** + * @throws ClientExceptionInterface + */ + public function testShouldRetryOnStatusCodes(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(408), + self::createResponse(429), + self::createResponse(500), + self::createResponse(501), + self::createResponse(502), + self::createResponse(503), + self::createResponse(504), + self::createResponse(505), + self::createResponse(599), + self::createResponse(200), + ); + $countOfErrorRequests = $mockClient->count() - 1; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: $countOfErrorRequests, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(0, $mockClient->count()); + } + + public function testShouldFailOn400Response(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(400), self::createResponse(200)); + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals(1, $mockClient->count()); + } + + public function testRetryAfterSecondsHeaderControlsDelay(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, ['Retry-After' => '10']), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); // Convert microseconds to milliseconds + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(10000, $capturedDelays[0]); + $this->assertLessThanOrEqual(12000, $capturedDelays[0]); + } + + public function testRetryAfterHttpDateHeaderIsHandled(): void + { + $retryAfterDate = gmdate('D, d M Y H:i:s \G\M\T', time() + 5); + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, ['Retry-After' => $retryAfterDate]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThan(0, $capturedDelays[0]); + $this->assertLessThanOrEqual(60000, $capturedDelays[0]); + } + + public function testRateLimitResetHeaderControlsDelay(): void + { + $resetTime = (int) floor(microtime(true)) + 5; + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThan(0, $capturedDelays[0]); + $this->assertLessThanOrEqual(60000, $capturedDelays[0]); + } + + public function testRateLimitResetHeaderRespectsMaxDelayAndPositiveJitter(): void + { + $resetTime = (int) floor(microtime(true)) + 1000; + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 1, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); + $this->assertLessThanOrEqual(72000, $capturedDelays[0]); + } + + public function testExponentialBackoffWithSymmetricJitterWhenNoHeaders(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 1, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(900, $capturedDelays[0]); + $this->assertLessThanOrEqual(1100, $capturedDelays[0]); + } + + public function testRetryAfterHeaderTakesPrecedenceOverRateLimitReset(): void + { + $resetTime = (int) floor(microtime(true)) + 30; + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, [ + 'Retry-After' => '5', + 'X-RateLimit-Reset' => (string) $resetTime, + ]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(5000, $capturedDelays[0]); + $this->assertLessThanOrEqual(6000, $capturedDelays[0]); + } + + public function testMaxDelayCapIsApplied(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, ['Retry-After' => '120']), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); + $this->assertLessThanOrEqual(72000, $capturedDelays[0]); + } + + public function testMultipartContentTypeIncludesBoundary(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->add('field', 'value'); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $contentType = $lastRequest->getHeaderLine('Content-Type'); + $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); + + $boundary = substr($contentType, strlen('multipart/form-data; boundary=')); + $body = (string) $lastRequest->getBody(); + $this->assertStringContainsString("--{$boundary}\r\n", $body); + $this->assertStringContainsString("Content-Disposition: form-data; name=\"field\"\r\n", $body); + $this->assertStringContainsString("value", $body); + $this->assertStringContainsString("--{$boundary}--\r\n", $body); + } + + public function testMultipartWithFilename(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->addPart(new MultipartFormDataPart( + name: 'document', + value: 'file-contents', + filename: 'report.pdf', + headers: ['Content-Type' => 'application/pdf'], + )); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $body = (string) $lastRequest->getBody(); + $this->assertStringContainsString( + 'Content-Disposition: form-data; name="document"; filename="report.pdf"', + $body, + ); + $this->assertStringContainsString('Content-Type: application/pdf', $body); + $this->assertStringContainsString('file-contents', $body); + } + + public function testMultipartWithMultipleParts(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->add('name', 'John'); + $formData->add('age', 30); + $formData->addPart(new MultipartFormDataPart( + name: 'avatar', + value: 'image-data', + filename: 'avatar.png', + )); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/profile', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $body = (string) $lastRequest->getBody(); + $this->assertStringContainsString('name="name"', $body); + $this->assertStringContainsString('John', $body); + $this->assertStringContainsString('name="age"', $body); + $this->assertStringContainsString('30', $body); + $this->assertStringContainsString('name="avatar"; filename="avatar.png"', $body); + $this->assertStringContainsString('image-data', $body); + } + + public function testMultipartDoesNotIncludeJsonContentType(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->add('field', 'value'); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $contentType = $lastRequest->getHeaderLine('Content-Type'); + $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); + $this->assertStringNotContainsString('application/json', $contentType); + } + + public function testMultipartNullBodySendsNoBody(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('', (string) $lastRequest->getBody()); + $this->assertStringNotContainsString('multipart/form-data', $lastRequest->getHeaderLine('Content-Type')); + } + + public function testJsonNullBodySendsNoBody(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('', (string) $lastRequest->getBody()); + } + + public function testEmptyJsonBodySerializesAsObject(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + ['key' => 'value'], + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'bodyProperties' => [ + 'key' => 'value', + ], + ], + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + // When bodyProperties override all keys, the merged result should still + // serialize as a JSON object {}, not an array []. + $decoded = json_decode((string) $lastRequest->getBody(), true); + $this->assertIsArray($decoded); + $this->assertEquals('value', $decoded['key']); + } + + public function testAuthHeadersAreIncluded(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], + ]); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + $rawClient->sendRequest($request); + + $lastRequest = $mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); + } + + public function testAuthHeadersAreIncludedInMultipart(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], + ]); + + $formData = new MultipartFormData(); + $formData->add('field', 'value'); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $rawClient->sendRequest($request); + + $lastRequest = $mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); + $this->assertStringStartsWith('multipart/form-data; boundary=', $lastRequest->getHeaderLine('Content-Type')); + } + + /** + * Creates a PSR-7 response using discovery, without depending on any specific implementation. + * + * @param int $statusCode + * @param array $headers + * @param string $body + * @return ResponseInterface + */ + private static function createResponse( + int $statusCode = 200, + array $headers = [], + string $body = '', + ): ResponseInterface { + $response = \Http\Discovery\Psr17FactoryDiscovery::findResponseFactory() + ->createResponse($statusCode); + foreach ($headers as $name => $value) { + $response = $response->withHeader($name, $value); + } + if ($body !== '') { + $response = $response->withBody( + \Http\Discovery\Psr17FactoryDiscovery::findStreamFactory() + ->createStream($body), + ); + } + return $response; + } + + + public function testTimeoutOptionIsAccepted(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + // MockHttpClient is not Guzzle/Symfony, so a warning is triggered once. + set_error_handler(static function (int $errno, string $errstr): bool { + return $errno === E_USER_WARNING + && str_contains($errstr, 'Timeout option is not supported'); + }); + + try { + $response = $this->rawClient->sendRequest( + $request, + options: [ + 'timeout' => 3.0 + ] + ); + + $this->assertEquals(200, $response->getStatusCode()); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + } finally { + restore_error_handler(); + } + } + + public function testClientLevelTimeoutIsAccepted(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'timeout' => 5.0, + ]); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + set_error_handler(static function (int $errno, string $errstr): bool { + return $errno === E_USER_WARNING + && str_contains($errstr, 'Timeout option is not supported'); + }); + + try { + $response = $rawClient->sendRequest($request); + $this->assertEquals(200, $response->getStatusCode()); + } finally { + restore_error_handler(); + } + } + + public function testPerRequestTimeoutOverridesClientTimeout(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'timeout' => 5.0, + ]); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + set_error_handler(static function (int $errno, string $errstr): bool { + return $errno === E_USER_WARNING + && str_contains($errstr, 'Timeout option is not supported'); + }); + + try { + $response = $rawClient->sendRequest( + $request, + options: [ + 'timeout' => 1.0 + ] + ); + + $this->assertEquals(200, $response->getStatusCode()); + } finally { + restore_error_handler(); + } + } + + public function testDiscoveryFindsHttpClient(): void + { + // HttpClientBuilder::build() with no client arg uses Psr18ClientDiscovery. + $client = HttpClientBuilder::build(); + $this->assertInstanceOf(\Psr\Http\Client\ClientInterface::class, $client); + } + + public function testDiscoveryFindsFactories(): void + { + $requestFactory = HttpClientBuilder::requestFactory(); + $this->assertInstanceOf(\Psr\Http\Message\RequestFactoryInterface::class, $requestFactory); + + $streamFactory = HttpClientBuilder::streamFactory(); + $this->assertInstanceOf(\Psr\Http\Message\StreamFactoryInterface::class, $streamFactory); + + // Verify they produce usable objects + $request = $requestFactory->createRequest('GET', 'https://example.com'); + $this->assertEquals('GET', $request->getMethod()); + + $stream = $streamFactory->createStream('hello'); + $this->assertEquals('hello', (string) $stream); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php new file mode 100644 index 000000000000..2c32002340e7 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php @@ -0,0 +1,76 @@ +name; + } + + /** + * @return string|null + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * @param array{ + * name: string, + * email?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->email = $values['email'] ?? null; + } +} + +class AdditionalPropertiesTest extends TestCase +{ + public function testExtraProperties(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'name' => 'john.doe', + 'email' => 'john.doe@example.com', + 'age' => 42 + ], + ); + + $person = Person::fromJson($expectedJson); + $this->assertEquals('john.doe', $person->getName()); + $this->assertEquals('john.doe@example.com', $person->getEmail()); + $this->assertEquals( + [ + 'age' => 42 + ], + $person->getAdditionalProperties(), + ); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php new file mode 100644 index 000000000000..e7794d652432 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php @@ -0,0 +1,54 @@ +dates = $values['dates']; + } +} + +class DateArrayTest extends TestCase +{ + public function testDateTimeInArrays(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ], + ); + + $object = DateArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php new file mode 100644 index 000000000000..b5f217e01f76 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php @@ -0,0 +1,71 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArrayTest extends TestCase +{ + public function testEmptyArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ], + ); + + $object = EmptyArray::fromJson($expectedJson); + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php new file mode 100644 index 000000000000..72dc6f2cfa00 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php @@ -0,0 +1,77 @@ +value; + } +} + +class ShapeType extends JsonSerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = JsonEncoder::encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ]); + + $actualJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $actualJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php new file mode 100644 index 000000000000..4c288378b48b --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php @@ -0,0 +1,197 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class Type extends JsonSerializableType +{ + /** + * @var Nested nestedType + */ + #[JsonProperty('nested_type')] + public Nested $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[Date(Date::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[Date(Date::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(Nested::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: Nested, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class ExhaustiveTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in Type. + */ + public function testExhaustive(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // Omit 'nullable_property' to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56Z', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> + ], + ); + + $object = Type::fromJson($expectedJson); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php new file mode 100644 index 000000000000..9d845ea113b8 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php @@ -0,0 +1,42 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTest extends TestCase +{ + public function testInvalidJsonThrowsException(): void + { + $this->expectException(\TypeError::class); + $json = JsonEncoder::encode( + [ + 'integer_property' => 'not_an_integer' + ], + ); + Invalid::fromJson($json); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php new file mode 100644 index 000000000000..8fbbeb939f02 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php @@ -0,0 +1,89 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArray extends JsonSerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTest extends TestCase +{ + public function testNestedUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ], + ); + + $object = NestedUnionArray::fromJson($expectedJson); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php new file mode 100644 index 000000000000..ce20a2442825 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php @@ -0,0 +1,53 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullProperty( + [ + "nonNullProperty" => "Test String", + "nullProperty" => null + ] + ); + + $serialized = $object->jsonSerialize(); + $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); + $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php new file mode 100644 index 000000000000..d1749c434a4c --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php @@ -0,0 +1,49 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTest extends TestCase +{ + public function testNullableArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nullable_string_array' => ['one', null, 'three'] + ], + ); + + $object = NullableArray::fromJson($expectedJson); + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php new file mode 100644 index 000000000000..ad4db0251bb5 --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php @@ -0,0 +1,116 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats + ], + ); + + $object = Scalar::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php new file mode 100644 index 000000000000..e18f06d4191b --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php @@ -0,0 +1,60 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ], + ); + + $object = TypeWithTrait::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php new file mode 100644 index 000000000000..de20cf9fde1b --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php @@ -0,0 +1,57 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class UnionArrayTest extends TestCase +{ + public function testUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00Z', + 2 => null, + 3 => 'Some String' + ] + ], + ); + + $object = UnionArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php new file mode 100644 index 000000000000..f733062cfabc --- /dev/null +++ b/seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php @@ -0,0 +1,111 @@ + 'integer'], UnionProperty::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionProperty + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => [1 => 100, 2 => 200] + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => new UnionProperty( + [ + 'complexUnion' => 'Nested String' + ] + ) + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $expectedJson = JsonEncoder::encode( + [], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 42 + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 'Some String' + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/allof/.fern/metadata.json b/seed/php-sdk/allof/.fern/metadata.json new file mode 100644 index 000000000000..37f759a1679d --- /dev/null +++ b/seed/php-sdk/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-php-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/php-sdk/allof/.github/workflows/ci.yml b/seed/php-sdk/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..678eb6c9e141 --- /dev/null +++ b/seed/php-sdk/allof/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test diff --git a/seed/php-sdk/allof/.gitignore b/seed/php-sdk/allof/.gitignore new file mode 100644 index 000000000000..31a1aeb14f35 --- /dev/null +++ b/seed/php-sdk/allof/.gitignore @@ -0,0 +1,5 @@ +.idea +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-sdk/allof/README.md b/seed/php-sdk/allof/README.md new file mode 100644 index 000000000000..0b89aa3897c7 --- /dev/null +++ b/seed/php-sdk/allof/README.md @@ -0,0 +1,169 @@ +# Seed PHP Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPHP) +[![php shield](https://img.shields.io/badge/php-packagist-pink)](https://packagist.org/packages/seed/seed) + +The Seed PHP library provides convenient access to the Seed APIs from PHP. + +## Table of Contents + +- [Requirements](#requirements) +- [Installation](#installation) +- [Usage](#usage) +- [Environments](#environments) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Custom Client](#custom-client) + - [Retries](#retries) + - [Timeouts](#timeouts) +- [Contributing](#contributing) + +## Requirements + +This SDK requires PHP ^8.1. + +## Installation + +```sh +composer require seed/seed +``` + +## Usage + +Instantiate and use the client with the following: + +```php +createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); + +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```php +The SDK defaults to the `Default_` environment. To use a different environment, pass it to the client constructor: + +```php +use Seed\SeedClient; +use Seed\Environments; + +$client = new SeedClient( + token: '', + options: [ + 'baseUrl' => Environments::Staging->value + ] +); +``` + +Available environments: +- `Environments::Default_` +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), an exception will be thrown. + +```php +use Seed\Exceptions\SeedApiException; +use Seed\Exceptions\SeedException; + +try { + $response = $client->createRule(...); +} catch (SeedApiException $e) { + echo 'API Exception occurred: ' . $e->getMessage() . "\n"; + echo 'Status Code: ' . $e->getCode() . "\n"; + echo 'Response Body: ' . $e->getBody() . "\n"; + // Optionally, rethrow the exception or handle accordingly. +} +``` + +## Advanced + +### Custom Client + +This SDK is built to work with any HTTP client that implements the [PSR-18](https://www.php-fig.org/psr/psr-18/) `ClientInterface`. +By default, if no client is provided, the SDK will use `php-http/discovery` to find an installed HTTP client. +However, you can pass your own client that adheres to `ClientInterface`: + +```php +use Seed\SeedClient; + +// Pass any PSR-18 compatible HTTP client implementation. +// For example, using Guzzle: +$customClient = new \GuzzleHttp\Client([ + 'timeout' => 5.0, +]); + +$client = new SeedClient(options: [ + 'client' => $customClient +]); + +// Or using Symfony HttpClient: +// $customClient = (new \Symfony\Component\HttpClient\Psr18Client()) +// ->withOptions(['timeout' => 5.0]); +// +// $client = new SeedClient(options: [ +// 'client' => $customClient +// ]); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```php +$response = $client->createRule( + ..., + options: [ + 'maxRetries' => 0 // Override maxRetries at the request level + ] +); +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `timeout` option to configure this behavior. + +```php +$response = $client->createRule( + ..., + options: [ + 'timeout' => 3.0 // Override timeout at the request level + ] +); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/php-sdk/allof/composer.json b/seed/php-sdk/allof/composer.json new file mode 100644 index 000000000000..ad30960a8764 --- /dev/null +++ b/seed/php-sdk/allof/composer.json @@ -0,0 +1,46 @@ +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "php-http/discovery": "^1.0", + "php-http/multipart-stream-builder": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12", + "guzzlehttp/guzzle": "^7.4" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src tests --memory-limit=1G" + } +} \ No newline at end of file diff --git a/seed/php-sdk/allof/phpstan.neon b/seed/php-sdk/allof/phpstan.neon new file mode 100644 index 000000000000..780706b8f8a2 --- /dev/null +++ b/seed/php-sdk/allof/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: max + reportUnmatchedIgnoredErrors: false + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-sdk/allof/phpunit.xml b/seed/php-sdk/allof/phpunit.xml new file mode 100644 index 000000000000..54630a51163c --- /dev/null +++ b/seed/php-sdk/allof/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-sdk/allof/reference.md b/seed/php-sdk/allof/reference.md new file mode 100644 index 000000000000..b824179f7851 --- /dev/null +++ b/seed/php-sdk/allof/reference.md @@ -0,0 +1,171 @@ +# Reference +
$client->searchRuleTypes($request) -> ?RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->searchRuleTypes( + new SearchRuleTypesRequest([]), +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**$query:** `?string` + +
+
+
+
+ + +
+
+
+ +
$client->createRule($request) -> ?RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**$name:** `string` + +
+
+ +
+
+ +**$executionContext:** `string` + +
+
+
+
+ + +
+
+
+ +
$client->listUsers() -> ?UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->listUsers(); +``` +
+
+
+
+ + +
+
+
+ +
$client->getEntity() -> ?CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->getEntity(); +``` +
+
+
+
+ + +
+
+
+ +
$client->getOrganization() -> ?Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->getOrganization(); +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/php-sdk/allof/snippet.json b/seed/php-sdk/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php new file mode 100644 index 000000000000..5e1283e2b6f6 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php b/seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php new file mode 100644 index 000000000000..8ac806af0325 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php @@ -0,0 +1,56 @@ + + */ + private array $responses = []; + + /** + * @var array + */ + private array $requests = []; + + /** + * @param ResponseInterface ...$responses + */ + public function append(ResponseInterface ...$responses): void + { + foreach ($responses as $response) { + $this->responses[] = $response; + } + } + + /** + * @param RequestInterface $request + * @return ResponseInterface + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $this->requests[] = $request; + + if (empty($this->responses)) { + throw new RuntimeException('No more responses in the queue. Add responses using append().'); + } + + return array_shift($this->responses); + } + + /** + * @return ?RequestInterface + */ + public function getLastRequest(): ?RequestInterface + { + if (empty($this->requests)) { + return null; + } + return $this->requests[count($this->requests) - 1]; + } + + /** + * @return int + */ + public function getRequestCount(): int + { + return count($this->requests); + } + + /** + * Returns the number of remaining responses in the queue. + * + * @return int + */ + public function count(): int + { + return count($this->responses); + } +} diff --git a/seed/php-sdk/allof/src/Core/Client/RawClient.php b/seed/php-sdk/allof/src/Core/Client/RawClient.php new file mode 100644 index 000000000000..14716c7d678b --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Client/RawClient.php @@ -0,0 +1,310 @@ + $headers + */ + private array $headers; + + /** + * @var ?(callable(): array) $getAuthHeaders + */ + private $getAuthHeaders; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * getAuthHeaders?: callable(): array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = HttpClientBuilder::build( + $this->options['client'] ?? null, + $this->options['maxRetries'] ?? 2, + ); + $this->requestFactory = HttpClientBuilder::requestFactory(); + $this->streamFactory = HttpClientBuilder::streamFactory(); + $this->headers = $this->options['headers'] ?? []; + $this->getAuthHeaders = $this->options['getAuthHeaders'] ?? null; + } + + /** + * @param BaseApiRequest $request + * @param ?array{ + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ?array $options = null, + ): ResponseInterface { + $opts = $options ?? []; + $httpRequest = $this->buildRequest($request, $opts); + + $timeout = $opts['timeout'] ?? $this->options['timeout'] ?? null; + $maxRetries = $opts['maxRetries'] ?? null; + + return $this->client->send($httpRequest, $timeout, $maxRetries); + } + + /** + * @param BaseApiRequest $request + * @param array{ + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return RequestInterface + */ + private function buildRequest( + BaseApiRequest $request, + array $options + ): RequestInterface { + $url = $this->buildUrl($request, $options); + $headers = $this->encodeHeaders($request, $options); + + $httpRequest = $this->requestFactory->createRequest( + $request->method->name, + $url, + ); + + // Encode body and, for multipart, capture the Content-Type with boundary. + if ($request instanceof MultipartApiRequest && $request->body !== null) { + $builder = new MultipartStreamBuilder($this->streamFactory); + $request->body->addToBuilder($builder); + $httpRequest = $httpRequest->withBody($builder->build()); + $headers['Content-Type'] = "multipart/form-data; boundary={$builder->getBoundary()}"; + } else { + $body = $this->encodeRequestBody($request, $options); + if ($body !== null) { + $httpRequest = $httpRequest->withBody($body); + } + } + + foreach ($headers as $name => $value) { + $httpRequest = $httpRequest->withHeader($name, $value); + } + + return $httpRequest; + } + + /** + * @param BaseApiRequest $request + * @param array{ + * headers?: array, + * } $options + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request, + array $options, + ): array { + $authHeaders = $this->getAuthHeaders !== null ? ($this->getAuthHeaders)() : []; + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + [ + "Content-Type" => "application/json", + "Accept" => "*/*", + ], + $this->headers, + $authHeaders, + $request->headers, + $options['headers'] ?? [], + ), + MultipartApiRequest::class => array_merge( + $this->headers, + $authHeaders, + $request->headers, + $options['headers'] ?? [], + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + /** + * @param BaseApiRequest $request + * @param array{ + * bodyProperties?: array, + * } $options + * @return ?StreamInterface + */ + private function encodeRequestBody( + BaseApiRequest $request, + array $options, + ): ?StreamInterface { + if ($request instanceof JsonApiRequest) { + return $request->body === null ? null : $this->streamFactory->createStream( + JsonEncoder::encode( + $this->buildJsonBody( + $request->body, + $options, + ), + ) + ); + } + + if ($request instanceof MultipartApiRequest) { + return null; + } + + throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)); + } + + /** + * @param mixed $body + * @param array{ + * bodyProperties?: array, + * } $options + * @return mixed + */ + private function buildJsonBody( + mixed $body, + array $options, + ): mixed { + $overrideProperties = $options['bodyProperties'] ?? []; + if (is_array($body) && (empty($body) || self::isSequential($body))) { + return array_merge($body, $overrideProperties); + } + + if ($body instanceof JsonSerializable) { + $result = $body->jsonSerialize(); + } else { + $result = $body; + } + if (is_array($result)) { + $result = array_merge($result, $overrideProperties); + if (empty($result)) { + // force to be serialized as {} instead of [] + return (object)($result); + } + } + + return $result; + } + + /** + * @param BaseApiRequest $request + * @param array{ + * queryParameters?: array, + * } $options + * @return string + */ + private function buildUrl( + BaseApiRequest $request, + array $options, + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + $query = array_merge( + $request->query, + $options['queryParameters'] ?? [], + ); + if (!empty($query)) { + $url .= '?' . $this->encodeQuery($query); + } + return $url; + } + + /** + * @param array $query + * @return string + */ + private function encodeQuery(array $query): string + { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue(mixed $value): string + { + if (is_string($value)) { + return urlencode($value); + } + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(JsonEncoder::encode($value)); + } + + /** + * Check if an array is sequential, not associative. + * @param mixed[] $arr + * @return bool + */ + private static function isSequential(array $arr): bool + { + if (empty($arr)) { + return false; + } + $length = count($arr); + $keys = array_keys($arr); + for ($i = 0; $i < $length; $i++) { + if ($keys[$i] !== $i) { + return false; + } + } + return true; + } +} diff --git a/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php new file mode 100644 index 000000000000..b16170cf2805 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php @@ -0,0 +1,241 @@ +client = $client; + $this->maxRetries = $maxRetries; + $this->baseDelay = $baseDelay; + $this->sleepFunction = $sleepFunction ?? 'usleep'; + } + + /** + * @param RequestInterface $request + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + return $this->send($request); + } + + /** + * Sends a request with optional per-request timeout and retry overrides. + * + * When a Guzzle or Symfony PSR-18 client is detected, the timeout is + * forwarded via the client's native API. For other PSR-18 clients the + * timeout value is silently ignored. + * + * @param RequestInterface $request + * @param ?float $timeout Timeout in seconds, or null to use the client default. + * @param ?int $maxRetries Maximum retry attempts, or null to use the client default. + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + public function send( + RequestInterface $request, + ?float $timeout = null, + ?int $maxRetries = null, + ): ResponseInterface { + $maxRetries = $maxRetries ?? $this->maxRetries; + $retryAttempt = 0; + $lastResponse = null; + + while (true) { + try { + $lastResponse = $this->doSend($request, $timeout); + if (!$this->shouldRetry($retryAttempt, $maxRetries, $lastResponse)) { + return $lastResponse; + } + } catch (ClientExceptionInterface $e) { + if ($retryAttempt >= $maxRetries) { + throw $e; + } + } + + $retryAttempt++; + $delay = $this->getRetryDelay($retryAttempt, $lastResponse); + ($this->sleepFunction)($delay * 1000); // Convert milliseconds to microseconds + + // Rewind the request body so retries don't send an empty body. + $request->getBody()->rewind(); + } + } + + /** + * Dispatches the request to the underlying client, forwarding the timeout + * option to Guzzle or Symfony when available. + * + * @param RequestInterface $request + * @param ?float $timeout + * @return ResponseInterface + * @throws ClientExceptionInterface + */ + private function doSend(RequestInterface $request, ?float $timeout): ResponseInterface + { + static $warned = false; + + if ($timeout === null) { + return $this->client->sendRequest($request); + } + + if (class_exists('GuzzleHttp\ClientInterface') + && $this->client instanceof \GuzzleHttp\ClientInterface + ) { + return $this->client->send($request, ['timeout' => $timeout]); + } + if (class_exists('Symfony\Component\HttpClient\Psr18Client') + && $this->client instanceof \Symfony\Component\HttpClient\Psr18Client + ) { + /** @var ClientInterface $clientWithTimeout */ + $clientWithTimeout = $this->client->withOptions(['timeout' => $timeout]); + return $clientWithTimeout->sendRequest($request); + } + + if ($warned) { + return $this->client->sendRequest($request); + } + $warned = true; + trigger_error( + 'Timeout option is not supported for the current PSR-18 client (' + . get_class($this->client) + . '). Use Guzzle or Symfony HttpClient for timeout support.', + E_USER_WARNING, + ); + return $this->client->sendRequest($request); + } + + /** + * @param int $retryAttempt + * @param int $maxRetries + * @param ?ResponseInterface $response + * @return bool + */ + private function shouldRetry( + int $retryAttempt, + int $maxRetries, + ?ResponseInterface $response = null, + ): bool { + if ($retryAttempt >= $maxRetries) { + return false; + } + + if ($response !== null) { + return $response->getStatusCode() >= 500 || + in_array($response->getStatusCode(), self::RETRY_STATUS_CODES); + } + + return false; + } + + /** + * Calculate the retry delay based on response headers or exponential backoff. + * + * @param int $retryAttempt + * @param ?ResponseInterface $response + * @return int milliseconds + */ + private function getRetryDelay(int $retryAttempt, ?ResponseInterface $response): int + { + if ($response !== null) { + // Check Retry-After header + $retryAfter = $response->getHeaderLine('Retry-After'); + if ($retryAfter !== '') { + // Try parsing as integer (seconds) + if (is_numeric($retryAfter)) { + $retryAfterSeconds = (int)$retryAfter; + if ($retryAfterSeconds > 0) { + return min($retryAfterSeconds * 1000, self::MAX_RETRY_DELAY); + } + } + + // Try parsing as HTTP date + $retryAfterDate = strtotime($retryAfter); + if ($retryAfterDate !== false) { + $delay = ($retryAfterDate - time()) * 1000; + if ($delay > 0) { + return min(max($delay, 0), self::MAX_RETRY_DELAY); + } + } + } + + // Check X-RateLimit-Reset header + $rateLimitReset = $response->getHeaderLine('X-RateLimit-Reset'); + if ($rateLimitReset !== '' && is_numeric($rateLimitReset)) { + $resetTime = (int)$rateLimitReset; + $delay = ($resetTime * 1000) - (int)(microtime(true) * 1000); + if ($delay > 0) { + return $this->addPositiveJitter(min($delay, self::MAX_RETRY_DELAY)); + } + } + } + + // Fall back to exponential backoff with symmetric jitter + return $this->addSymmetricJitter( + min($this->exponentialDelay($retryAttempt), self::MAX_RETRY_DELAY) + ); + } + + /** + * Add positive jitter (0% to +20%) to the delay. + * + * @param int $delay + * @return int + */ + private function addPositiveJitter(int $delay): int + { + $jitterMultiplier = 1 + (mt_rand() / mt_getrandmax()) * self::JITTER_FACTOR; + return (int)($delay * $jitterMultiplier); + } + + /** + * Add symmetric jitter (-10% to +10%) to the delay. + * + * @param int $delay + * @return int + */ + private function addSymmetricJitter(int $delay): int + { + $jitterMultiplier = 1 + ((mt_rand() / mt_getrandmax()) - 0.5) * self::JITTER_FACTOR; + return (int)($delay * $jitterMultiplier); + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + private function exponentialDelay(int $retryAttempt): int + { + return 2 ** ($retryAttempt - 1) * $this->baseDelay; + } +} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php b/seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php new file mode 100644 index 000000000000..8fdf493606e6 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php @@ -0,0 +1,28 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonDecoder.php b/seed/php-sdk/allof/src/Core/Json/JsonDecoder.php new file mode 100644 index 000000000000..2da34087c644 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: $json"); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php new file mode 100644 index 000000000000..1a250c614e45 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,218 @@ + $data The array to be deserialized. + * @param array $type The type definition from the annotation. + * @return array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (\Throwable) { + // Catching Throwable instead of Exception to handle TypeError + // that occurs when assigning null to non-nullable typed properties + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: $type" + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + /** @var array $data */ + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement JsonSerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, JsonSerializableType::class)) { + throw new JsonException("$type is not a subclass of JsonSerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonEncoder.php b/seed/php-sdk/allof/src/Core/Json/JsonEncoder.php new file mode 100644 index 000000000000..0dbf3fcc9948 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ + Extra properties from JSON that don't map to class properties */ + private array $__additionalProperties = []; + + /** @var array Properties that have been explicitly set via setter methods */ + private array $__explicitlySetProperties = []; + + /** + * Serializes the object to a JSON string. + * + * @return string JSON-encoded string representation of the object. + * @throws Exception If encoding fails. + */ + public function toJson(): string + { + $serializedObject = $this->jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey === null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === Date::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + // Include the value if it's not null, OR if it was explicitly set (even to null) + if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { + $result[$jsonKey] = $value; + } + } + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + /** @var array $decodedJson */ + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + $properties = []; + $additionalProperties = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + $properties[$jsonKey] = $property; + } + + foreach ($data as $jsonKey => $value) { + if (!isset($properties[$jsonKey])) { + // This JSON key doesn't map to any class property - add it to additionalProperties + $additionalProperties[$jsonKey] = $value; + continue; + } + + $property = $properties[$jsonKey]; + + // Handle Date annotation + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === Date::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle Array annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + /** @var array $arrayValue */ + $arrayValue = $value; + $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); + } + + $args[$property->getName()] = $value; + } + + // Fill in any missing properties with defaults + foreach ($properties as $property) { + if (!isset($args[$property->getName()])) { + $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; + } + } + + // @phpstan-ignore-next-line + $result = new static($args); + $result->__additionalProperties = $additionalProperties; + return $result; + } + + /** + * Get properties from JSON that weren't mapped to class fields + * @return array + */ + public function getAdditionalProperties(): array + { + return $this->__additionalProperties; + } + + /** + * Mark a property as explicitly set. + * This ensures the property will be included in JSON serialization even if null. + * + * @param string $propertyName The name of the property to mark as explicitly set. + */ + protected function _setField(string $propertyName): void + { + $this->__explicitlySetProperties[$propertyName] = true; + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonSerializer.php b/seed/php-sdk/allof/src/Core/Json/JsonSerializer.php new file mode 100644 index 000000000000..f7d80ed5e8f3 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Json/JsonSerializer.php @@ -0,0 +1,205 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + $formatted = $date->format(Constant::DateTimeFormat); + if (str_ends_with($formatted, '+00:00')) { + return substr($formatted, 0, -6) . 'Z'; + } + return $formatted; + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param array $data The array to be serialized. + * @param array $type The type definition from the annotation. + * @return array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: $unionType" + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/allof/src/Core/Json/Utils.php b/seed/php-sdk/allof/src/Core/Json/Utils.php new file mode 100644 index 000000000000..4099b8253005 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Json/Utils.php @@ -0,0 +1,62 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return int|string The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): int|string + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + // PHP arrays don't support float keys; truncate to int + 'float' => (int)$key, + 'string' => (string)$key, + default => is_int($key) ? $key : (string)$key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php b/seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php new file mode 100644 index 000000000000..7760366456c8 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php @@ -0,0 +1,28 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param ?MultipartFormData $body The multipart form data for the request (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly ?MultipartFormData $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php b/seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php new file mode 100644 index 000000000000..911a28b6ad64 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php @@ -0,0 +1,58 @@ + + */ + private array $parts = []; + + /** + * Adds a new part to the multipart form data. + * + * @param string $name + * @param string|int|bool|float|StreamInterface $value + * @param ?string $contentType + */ + public function add( + string $name, + string|int|bool|float|StreamInterface $value, + ?string $contentType = null, + ): void { + $headers = $contentType !== null ? ['Content-Type' => $contentType] : null; + $this->addPart( + new MultipartFormDataPart( + name: $name, + value: $value, + headers: $headers, + ) + ); + } + + /** + * Adds a new part to the multipart form data. + * + * @param MultipartFormDataPart $part + */ + public function addPart(MultipartFormDataPart $part): void + { + $this->parts[] = $part; + } + + /** + * Adds all parts to a MultipartStreamBuilder. + * + * @param MultipartStreamBuilder $builder + */ + public function addToBuilder(MultipartStreamBuilder $builder): void + { + foreach ($this->parts as $part) { + $part->addToBuilder($builder); + } + } +} diff --git a/seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php b/seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php new file mode 100644 index 000000000000..4db35e58ae37 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php @@ -0,0 +1,62 @@ + + */ + private ?array $headers; + + /** + * @param string $name + * @param string|bool|float|int|StreamInterface $value + * @param ?string $filename + * @param ?array $headers + */ + public function __construct( + string $name, + string|bool|float|int|StreamInterface $value, + ?string $filename = null, + ?array $headers = null + ) { + $this->name = $name; + $this->contents = $value instanceof StreamInterface ? $value : (string)$value; + $this->filename = $filename; + $this->headers = $headers; + } + + /** + * Adds this part to a MultipartStreamBuilder. + * + * @param MultipartStreamBuilder $builder + */ + public function addToBuilder(MultipartStreamBuilder $builder): void + { + $options = array_filter([ + 'filename' => $this->filename, + 'headers' => $this->headers, + ], fn ($value) => $value !== null); + + $builder->addResource($this->name, $this->contents, $options); + } +} diff --git a/seed/php-sdk/allof/src/Core/Types/ArrayType.php b/seed/php-sdk/allof/src/Core/Types/ArrayType.php new file mode 100644 index 000000000000..a26d29008ec3 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/allof/src/Core/Types/Constant.php b/seed/php-sdk/allof/src/Core/Types/Constant.php new file mode 100644 index 000000000000..5ac4518cc6d6 --- /dev/null +++ b/seed/php-sdk/allof/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/allof/src/Environments.php b/seed/php-sdk/allof/src/Environments.php new file mode 100644 index 000000000000..43d64cdb5e37 --- /dev/null +++ b/seed/php-sdk/allof/src/Environments.php @@ -0,0 +1,8 @@ +body = $body; + parent::__construct($message, $statusCode, $previous); + } + + /** + * Returns the body of the response that triggered the exception. + * + * @return mixed + */ + public function getBody(): mixed + { + return $this->body; + } + + /** + * @return string + */ + public function __toString(): string + { + if (empty($this->body)) { + return $this->message . '; Status Code: ' . $this->getCode() . "\n"; + } + return $this->message . '; Status Code: ' . $this->getCode() . '; Body: ' . print_r($this->body, true) . "\n"; + } +} diff --git a/seed/php-sdk/allof/src/Exceptions/SeedException.php b/seed/php-sdk/allof/src/Exceptions/SeedException.php new file mode 100644 index 000000000000..457035276737 --- /dev/null +++ b/seed/php-sdk/allof/src/Exceptions/SeedException.php @@ -0,0 +1,12 @@ + $executionContext + */ + #[JsonProperty('executionContext')] + public string $executionContext; + + /** + * @param array{ + * name: string, + * executionContext: value-of, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->executionContext = $values['executionContext']; + } +} diff --git a/seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php b/seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php new file mode 100644 index 000000000000..bd49b88a3872 --- /dev/null +++ b/seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php @@ -0,0 +1,24 @@ +query = $values['query'] ?? null; + } +} diff --git a/seed/php-sdk/allof/src/SeedClient.php b/seed/php-sdk/allof/src/SeedClient.php new file mode 100644 index 000000000000..63b560f9b6c7 --- /dev/null +++ b/seed/php-sdk/allof/src/SeedClient.php @@ -0,0 +1,302 @@ +, + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator + */ + private array $options; + + /** + * @var RawClient $client + */ + private RawClient $client; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * } $options + */ + public function __construct( + ?array $options = null, + ) { + $defaultHeaders = [ + 'X-Fern-Language' => 'PHP', + 'X-Fern-SDK-Name' => 'Seed', + 'X-Fern-SDK-Version' => '0.0.1', + 'User-Agent' => 'seed/seed/0.0.1', + ]; + + $this->options = $options ?? []; + + $this->options['headers'] = array_merge( + $defaultHeaders, + $this->options['headers'] ?? [], + ); + + $this->client = new RawClient( + options: $this->options, + ); + } + + /** + * @param SearchRuleTypesRequest $request + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?RuleTypeSearchResponse + * @throws SeedException + * @throws SeedApiException + */ + public function searchRuleTypes(SearchRuleTypesRequest $request = new SearchRuleTypesRequest(), ?array $options = null): ?RuleTypeSearchResponse + { + $options = array_merge($this->options, $options ?? []); + $query = []; + if ($request->query != null) { + $query['query'] = $request->query; + } + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "rule-types", + method: HttpMethod::GET, + query: $query, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return RuleTypeSearchResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param RuleCreateRequest $request + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?RuleResponse + * @throws SeedException + * @throws SeedApiException + */ + public function createRule(RuleCreateRequest $request, ?array $options = null): ?RuleResponse + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "rules", + method: HttpMethod::POST, + body: $request, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return RuleResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?UserSearchResponse + * @throws SeedException + * @throws SeedApiException + */ + public function listUsers(?array $options = null): ?UserSearchResponse + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "users", + method: HttpMethod::GET, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return UserSearchResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?CombinedEntity + * @throws SeedException + * @throws SeedApiException + */ + public function getEntity(?array $options = null): ?CombinedEntity + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "entities", + method: HttpMethod::GET, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return CombinedEntity::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?Organization + * @throws SeedException + * @throws SeedApiException + */ + public function getOrganization(?array $options = null): ?Organization + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, + path: "organizations", + method: HttpMethod::GET, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return Organization::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } +} diff --git a/seed/php-sdk/allof/src/Traits/AuditInfo.php b/seed/php-sdk/allof/src/Traits/AuditInfo.php new file mode 100644 index 000000000000..2c2ce1e5f033 --- /dev/null +++ b/seed/php-sdk/allof/src/Traits/AuditInfo.php @@ -0,0 +1,42 @@ +createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/BaseOrg.php b/seed/php-sdk/allof/src/Types/BaseOrg.php new file mode 100644 index 000000000000..5d403a06b445 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/BaseOrg.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/BaseOrgMetadata.php b/seed/php-sdk/allof/src/Types/BaseOrgMetadata.php new file mode 100644 index 000000000000..e974fa97bb1a --- /dev/null +++ b/seed/php-sdk/allof/src/Types/BaseOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->tier = $values['tier'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/CombinedEntity.php b/seed/php-sdk/allof/src/Types/CombinedEntity.php new file mode 100644 index 000000000000..ae8be69937ad --- /dev/null +++ b/seed/php-sdk/allof/src/Types/CombinedEntity.php @@ -0,0 +1,58 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @var string $id Unique identifier. + */ + #[JsonProperty('id')] + public string $id; + + /** + * @var ?string $name Display name from Identifiable. + */ + #[JsonProperty('name')] + public ?string $name; + + /** + * @var ?string $summary A short summary. + */ + #[JsonProperty('summary')] + public ?string $summary; + + /** + * @param array{ + * status: value-of, + * id: string, + * name?: ?string, + * summary?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->status = $values['status']; + $this->id = $values['id']; + $this->name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/CombinedEntityStatus.php b/seed/php-sdk/allof/src/Types/CombinedEntityStatus.php new file mode 100644 index 000000000000..63aba6a34b11 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/CombinedEntityStatus.php @@ -0,0 +1,9 @@ +name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/DetailedOrg.php b/seed/php-sdk/allof/src/Types/DetailedOrg.php new file mode 100644 index 000000000000..c53ac92b926a --- /dev/null +++ b/seed/php-sdk/allof/src/Types/DetailedOrg.php @@ -0,0 +1,34 @@ +metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php b/seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php new file mode 100644 index 000000000000..ea98caf5f68a --- /dev/null +++ b/seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->domain = $values['domain'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/Identifiable.php b/seed/php-sdk/allof/src/Types/Identifiable.php new file mode 100644 index 000000000000..83c1714a7eb8 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/Identifiable.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->name = $values['name'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/Organization.php b/seed/php-sdk/allof/src/Types/Organization.php new file mode 100644 index 000000000000..a03834027cfb --- /dev/null +++ b/seed/php-sdk/allof/src/Types/Organization.php @@ -0,0 +1,50 @@ +name = $values['name']; + $this->id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/PaginatedResult.php b/seed/php-sdk/allof/src/Types/PaginatedResult.php new file mode 100644 index 000000000000..c24634881db3 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/PaginatedResult.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType(['mixed'])] + public array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/PagingCursors.php b/seed/php-sdk/allof/src/Types/PagingCursors.php new file mode 100644 index 000000000000..b4a3809a1dbb --- /dev/null +++ b/seed/php-sdk/allof/src/Types/PagingCursors.php @@ -0,0 +1,42 @@ +next = $values['next']; + $this->previous = $values['previous'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/RuleExecutionContext.php b/seed/php-sdk/allof/src/Types/RuleExecutionContext.php new file mode 100644 index 000000000000..bc98b8b52369 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/RuleExecutionContext.php @@ -0,0 +1,10 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @var ?value-of $executionContext + */ + #[JsonProperty('executionContext')] + public ?string $executionContext; + + /** + * @param array{ + * id: string, + * name: string, + * status: value-of, + * createdBy?: ?string, + * createdDateTime?: ?DateTime, + * modifiedBy?: ?string, + * modifiedDateTime?: ?DateTime, + * executionContext?: ?value-of, + * } $values + */ + public function __construct( + array $values, + ) { + $this->createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + $this->id = $values['id']; + $this->name = $values['name']; + $this->status = $values['status']; + $this->executionContext = $values['executionContext'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/RuleResponseStatus.php b/seed/php-sdk/allof/src/Types/RuleResponseStatus.php new file mode 100644 index 000000000000..cef363294474 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/RuleResponseStatus.php @@ -0,0 +1,10 @@ +id = $values['id']; + $this->name = $values['name']; + $this->description = $values['description'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php b/seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php new file mode 100644 index 000000000000..6272d4420ec7 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([RuleType::class])] + public ?array $results; + + /** + * @var PagingCursors $paging + */ + #[JsonProperty('paging')] + public PagingCursors $paging; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->results = $values['results'] ?? null; + $this->paging = $values['paging']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/User.php b/seed/php-sdk/allof/src/Types/User.php new file mode 100644 index 000000000000..aab3174ca08c --- /dev/null +++ b/seed/php-sdk/allof/src/Types/User.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->email = $values['email']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Types/UserSearchResponse.php b/seed/php-sdk/allof/src/Types/UserSearchResponse.php new file mode 100644 index 000000000000..4944636dfa78 --- /dev/null +++ b/seed/php-sdk/allof/src/Types/UserSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([User::class])] + public ?array $results; + + /** + * @var PagingCursors $paging + */ + #[JsonProperty('paging')] + public PagingCursors $paging; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->results = $values['results'] ?? null; + $this->paging = $values['paging']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-sdk/allof/src/Utils/File.php b/seed/php-sdk/allof/src/Utils/File.php new file mode 100644 index 000000000000..ee2af27b8909 --- /dev/null +++ b/seed/php-sdk/allof/src/Utils/File.php @@ -0,0 +1,129 @@ +filename = $filename; + $this->contentType = $contentType; + $this->stream = $stream; + } + + /** + * Creates a File instance from a filepath. + * + * @param string $filepath + * @param ?string $filename + * @param ?string $contentType + * @return File + * @throws Exception + */ + public static function createFromFilepath( + string $filepath, + ?string $filename = null, + ?string $contentType = null, + ): File { + $resource = @fopen($filepath, 'r'); + if (!$resource) { + throw new Exception("Unable to open file $filepath"); + } + $stream = Psr17FactoryDiscovery::findStreamFactory()->createStreamFromResource($resource); + if (!$stream->isReadable()) { + throw new Exception("File $filepath is not readable"); + } + return new self( + stream: $stream, + filename: $filename ?? basename($filepath), + contentType: $contentType, + ); + } + + /** + * Creates a File instance from a string. + * + * @param string $content + * @param ?string $filename + * @param ?string $contentType + * @return File + */ + public static function createFromString( + string $content, + ?string $filename, + ?string $contentType = null, + ): File { + return new self( + stream: Psr17FactoryDiscovery::findStreamFactory()->createStream($content), + filename: $filename, + contentType: $contentType, + ); + } + + /** + * Maps this File into a multipart form data part. + * + * @param string $name The name of the multipart form data part. + * @param ?string $contentType Overrides the Content-Type associated with the file, if any. + * @return MultipartFormDataPart + */ + public function toMultipartFormDataPart(string $name, ?string $contentType = null): MultipartFormDataPart + { + $contentType ??= $this->contentType; + $headers = $contentType !== null + ? ['Content-Type' => $contentType] + : null; + + return new MultipartFormDataPart( + name: $name, + value: $this->stream, + filename: $this->filename, + headers: $headers, + ); + } + + /** + * Closes the file stream. + */ + public function close(): void + { + $this->stream->close(); + } + + /** + * Destructor to ensure stream is closed. + */ + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable) { + // Swallow errors during garbage collection to avoid fatal errors. + } + } +} diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php new file mode 100644 index 000000000000..1e384a760b83 --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php @@ -0,0 +1,15 @@ + 'https://api.fern.com', + ], +); +$client->searchRuleTypes( + new SearchRuleTypesRequest([]), +); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php new file mode 100644 index 000000000000..826582f7bc27 --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php @@ -0,0 +1,17 @@ + 'https://api.fern.com', + ], +); +$client->searchRuleTypes( + new SearchRuleTypesRequest([ + 'query' => 'query', + ]), +); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php new file mode 100644 index 000000000000..5539210a6505 --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php @@ -0,0 +1,19 @@ + 'https://api.fern.com', + ], +); +$client->createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php new file mode 100644 index 000000000000..5539210a6505 --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php @@ -0,0 +1,19 @@ + 'https://api.fern.com', + ], +); +$client->createRule( + new RuleCreateRequest([ + 'name' => 'name', + 'executionContext' => RuleExecutionContext::Prod->value, + ]), +); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php new file mode 100644 index 000000000000..477f8f61c34c --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->listUsers(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php new file mode 100644 index 000000000000..477f8f61c34c --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->listUsers(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php new file mode 100644 index 000000000000..4b4021abb4a6 --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getEntity(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php new file mode 100644 index 000000000000..4b4021abb4a6 --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getEntity(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php new file mode 100644 index 000000000000..bcf929bea40c --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getOrganization(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php new file mode 100644 index 000000000000..bcf929bea40c --- /dev/null +++ b/seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php @@ -0,0 +1,12 @@ + 'https://api.fern.com', + ], +); +$client->getOrganization(); diff --git a/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php b/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php new file mode 100644 index 000000000000..df36dc918894 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php @@ -0,0 +1,1074 @@ +name = $values['name']; + } + + /** + * @return string + */ + public function getName(): ?string + { + return $this->name; + } +} + +class RawClientTest extends TestCase +{ + private string $baseUrl = 'https://api.example.com'; + private MockHttpClient $mockClient; + private RawClient $rawClient; + + protected function setUp(): void + { + $this->mockClient = new MockHttpClient(); + $this->rawClient = new RawClient(['client' => $this->mockClient, 'maxRetries' => 0]); + } + + /** + * @throws ClientExceptionInterface + */ + public function testHeaders(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + /** + * @throws ClientExceptionInterface + */ + public function testQueryParameters(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + /** + * @throws ClientExceptionInterface + */ + public function testJsonBody(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(JsonEncoder::encode($body), (string)$lastRequest->getBody()); + } + + public function testAdditionalHeaders(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = new JsonRequest([ + 'name' => 'john.doe' + ]); + $headers = [ + 'X-API-Version' => '1.0.0', + ]; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + $headers, + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'headers' => [ + 'X-Tenancy' => 'test' + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('1.0.0', $lastRequest->getHeaderLine('X-API-Version')); + $this->assertEquals('test', $lastRequest->getHeaderLine('X-Tenancy')); + } + + public function testOverrideAdditionalHeaders(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = new JsonRequest([ + 'name' => 'john.doe' + ]); + $headers = [ + 'X-API-Version' => '1.0.0', + ]; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + $headers, + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'headers' => [ + 'X-API-Version' => '2.0.0' + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('2.0.0', $lastRequest->getHeaderLine('X-API-Version')); + } + + public function testAdditionalBodyProperties(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = new JsonRequest([ + 'name' => 'john.doe' + ]); + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'bodyProperties' => [ + 'age' => 42 + ] + ] + ); + + $expectedJson = [ + 'name' => 'john.doe', + 'age' => 42 + ]; + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); + } + + public function testOverrideAdditionalBodyProperties(): void + { + $this->mockClient->append(self::createResponse(200)); + + $body = [ + 'name' => 'john.doe' + ]; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'bodyProperties' => [ + 'name' => 'jane.doe' + ] + ] + ); + + $expectedJson = [ + 'name' => 'jane.doe', + ]; + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); + } + + public function testAdditionalQueryParameters(): void + { + $this->mockClient->append(self::createResponse(200)); + + $query = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + $query, + [] + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'queryParameters' => [ + 'extra' => 42 + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('key=value&extra=42', $lastRequest->getUri()->getQuery()); + } + + public function testOverrideQueryParameters(): void + { + $this->mockClient->append(self::createResponse(200)); + + $query = ['key' => 'invalid']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + $query, + [] + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'queryParameters' => [ + 'key' => 'value' + ] + ] + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('key=value', $lastRequest->getUri()->getQuery()); + } + + public function testDefaultRetries(): void + { + $this->mockClient->append(self::createResponse(500)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET + ); + + $response = $this->rawClient->sendRequest($request); + $this->assertEquals(500, $response->getStatusCode()); + $this->assertEquals(0, $this->mockClient->count()); + } + + /** + * @throws ClientExceptionInterface + */ + public function testExplicitRetriesSuccess(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(200)); + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(0, $mockClient->count()); + } + + public function testExplicitRetriesFailure(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(500)); + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(500, $response->getStatusCode()); + $this->assertEquals(0, $mockClient->count()); + } + + /** + * @throws ClientExceptionInterface + */ + public function testShouldRetryOnStatusCodes(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(408), + self::createResponse(429), + self::createResponse(500), + self::createResponse(501), + self::createResponse(502), + self::createResponse(503), + self::createResponse(504), + self::createResponse(505), + self::createResponse(599), + self::createResponse(200), + ); + $countOfErrorRequests = $mockClient->count() - 1; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: $countOfErrorRequests, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(0, $mockClient->count()); + } + + public function testShouldFailOn400Response(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(400), self::createResponse(200)); + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->sendRequest($request); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals(1, $mockClient->count()); + } + + public function testRetryAfterSecondsHeaderControlsDelay(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, ['Retry-After' => '10']), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); // Convert microseconds to milliseconds + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(10000, $capturedDelays[0]); + $this->assertLessThanOrEqual(12000, $capturedDelays[0]); + } + + public function testRetryAfterHttpDateHeaderIsHandled(): void + { + $retryAfterDate = gmdate('D, d M Y H:i:s \G\M\T', time() + 5); + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, ['Retry-After' => $retryAfterDate]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThan(0, $capturedDelays[0]); + $this->assertLessThanOrEqual(60000, $capturedDelays[0]); + } + + public function testRateLimitResetHeaderControlsDelay(): void + { + $resetTime = (int) floor(microtime(true)) + 5; + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThan(0, $capturedDelays[0]); + $this->assertLessThanOrEqual(60000, $capturedDelays[0]); + } + + public function testRateLimitResetHeaderRespectsMaxDelayAndPositiveJitter(): void + { + $resetTime = (int) floor(microtime(true)) + 1000; + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 1, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); + $this->assertLessThanOrEqual(72000, $capturedDelays[0]); + } + + public function testExponentialBackoffWithSymmetricJitterWhenNoHeaders(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 1, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(900, $capturedDelays[0]); + $this->assertLessThanOrEqual(1100, $capturedDelays[0]); + } + + public function testRetryAfterHeaderTakesPrecedenceOverRateLimitReset(): void + { + $resetTime = (int) floor(microtime(true)) + 30; + + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, [ + 'Retry-After' => '5', + 'X-RateLimit-Reset' => (string) $resetTime, + ]), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(5000, $capturedDelays[0]); + $this->assertLessThanOrEqual(6000, $capturedDelays[0]); + } + + public function testMaxDelayCapIsApplied(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append( + self::createResponse(503, ['Retry-After' => '120']), + self::createResponse(200), + ); + + $capturedDelays = []; + $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { + $capturedDelays[] = (int) ($microseconds / 1000); + }; + + $retryClient = new RetryDecoratingClient( + $mockClient, + maxRetries: 2, + baseDelay: 1000, + sleepFunction: $sleepFunction, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $retryClient->sendRequest($request); + + $this->assertCount(1, $capturedDelays); + $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); + $this->assertLessThanOrEqual(72000, $capturedDelays[0]); + } + + public function testMultipartContentTypeIncludesBoundary(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->add('field', 'value'); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $contentType = $lastRequest->getHeaderLine('Content-Type'); + $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); + + $boundary = substr($contentType, strlen('multipart/form-data; boundary=')); + $body = (string) $lastRequest->getBody(); + $this->assertStringContainsString("--{$boundary}\r\n", $body); + $this->assertStringContainsString("Content-Disposition: form-data; name=\"field\"\r\n", $body); + $this->assertStringContainsString("value", $body); + $this->assertStringContainsString("--{$boundary}--\r\n", $body); + } + + public function testMultipartWithFilename(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->addPart(new MultipartFormDataPart( + name: 'document', + value: 'file-contents', + filename: 'report.pdf', + headers: ['Content-Type' => 'application/pdf'], + )); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $body = (string) $lastRequest->getBody(); + $this->assertStringContainsString( + 'Content-Disposition: form-data; name="document"; filename="report.pdf"', + $body, + ); + $this->assertStringContainsString('Content-Type: application/pdf', $body); + $this->assertStringContainsString('file-contents', $body); + } + + public function testMultipartWithMultipleParts(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->add('name', 'John'); + $formData->add('age', 30); + $formData->addPart(new MultipartFormDataPart( + name: 'avatar', + value: 'image-data', + filename: 'avatar.png', + )); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/profile', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $body = (string) $lastRequest->getBody(); + $this->assertStringContainsString('name="name"', $body); + $this->assertStringContainsString('John', $body); + $this->assertStringContainsString('name="age"', $body); + $this->assertStringContainsString('30', $body); + $this->assertStringContainsString('name="avatar"; filename="avatar.png"', $body); + $this->assertStringContainsString('image-data', $body); + } + + public function testMultipartDoesNotIncludeJsonContentType(): void + { + $this->mockClient->append(self::createResponse(200)); + + $formData = new MultipartFormData(); + $formData->add('field', 'value'); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $contentType = $lastRequest->getHeaderLine('Content-Type'); + $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); + $this->assertStringNotContainsString('application/json', $contentType); + } + + public function testMultipartNullBodySendsNoBody(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('', (string) $lastRequest->getBody()); + $this->assertStringNotContainsString('multipart/form-data', $lastRequest->getHeaderLine('Content-Type')); + } + + public function testJsonNullBodySendsNoBody(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + ); + + $this->rawClient->sendRequest($request); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('', (string) $lastRequest->getBody()); + } + + public function testEmptyJsonBodySerializesAsObject(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + ['key' => 'value'], + ); + + $this->rawClient->sendRequest( + $request, + options: [ + 'bodyProperties' => [ + 'key' => 'value', + ], + ], + ); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + // When bodyProperties override all keys, the merged result should still + // serialize as a JSON object {}, not an array []. + $decoded = json_decode((string) $lastRequest->getBody(), true); + $this->assertIsArray($decoded); + $this->assertEquals('value', $decoded['key']); + } + + public function testAuthHeadersAreIncluded(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], + ]); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + $rawClient->sendRequest($request); + + $lastRequest = $mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); + } + + public function testAuthHeadersAreIncludedInMultipart(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], + ]); + + $formData = new MultipartFormData(); + $formData->add('field', 'value'); + + $request = new MultipartApiRequest( + $this->baseUrl, + '/upload', + HttpMethod::POST, + [], + [], + $formData, + ); + + $rawClient->sendRequest($request); + + $lastRequest = $mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + + $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); + $this->assertStringStartsWith('multipart/form-data; boundary=', $lastRequest->getHeaderLine('Content-Type')); + } + + /** + * Creates a PSR-7 response using discovery, without depending on any specific implementation. + * + * @param int $statusCode + * @param array $headers + * @param string $body + * @return ResponseInterface + */ + private static function createResponse( + int $statusCode = 200, + array $headers = [], + string $body = '', + ): ResponseInterface { + $response = \Http\Discovery\Psr17FactoryDiscovery::findResponseFactory() + ->createResponse($statusCode); + foreach ($headers as $name => $value) { + $response = $response->withHeader($name, $value); + } + if ($body !== '') { + $response = $response->withBody( + \Http\Discovery\Psr17FactoryDiscovery::findStreamFactory() + ->createStream($body), + ); + } + return $response; + } + + + public function testTimeoutOptionIsAccepted(): void + { + $this->mockClient->append(self::createResponse(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + // MockHttpClient is not Guzzle/Symfony, so a warning is triggered once. + set_error_handler(static function (int $errno, string $errstr): bool { + return $errno === E_USER_WARNING + && str_contains($errstr, 'Timeout option is not supported'); + }); + + try { + $response = $this->rawClient->sendRequest( + $request, + options: [ + 'timeout' => 3.0 + ] + ); + + $this->assertEquals(200, $response->getStatusCode()); + + $lastRequest = $this->mockClient->getLastRequest(); + $this->assertInstanceOf(RequestInterface::class, $lastRequest); + } finally { + restore_error_handler(); + } + } + + public function testClientLevelTimeoutIsAccepted(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'timeout' => 5.0, + ]); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + set_error_handler(static function (int $errno, string $errstr): bool { + return $errno === E_USER_WARNING + && str_contains($errstr, 'Timeout option is not supported'); + }); + + try { + $response = $rawClient->sendRequest($request); + $this->assertEquals(200, $response->getStatusCode()); + } finally { + restore_error_handler(); + } + } + + public function testPerRequestTimeoutOverridesClientTimeout(): void + { + $mockClient = new MockHttpClient(); + $mockClient->append(self::createResponse(200)); + + $rawClient = new RawClient([ + 'client' => $mockClient, + 'maxRetries' => 0, + 'timeout' => 5.0, + ]); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ); + + set_error_handler(static function (int $errno, string $errstr): bool { + return $errno === E_USER_WARNING + && str_contains($errstr, 'Timeout option is not supported'); + }); + + try { + $response = $rawClient->sendRequest( + $request, + options: [ + 'timeout' => 1.0 + ] + ); + + $this->assertEquals(200, $response->getStatusCode()); + } finally { + restore_error_handler(); + } + } + + public function testDiscoveryFindsHttpClient(): void + { + // HttpClientBuilder::build() with no client arg uses Psr18ClientDiscovery. + $client = HttpClientBuilder::build(); + $this->assertInstanceOf(\Psr\Http\Client\ClientInterface::class, $client); + } + + public function testDiscoveryFindsFactories(): void + { + $requestFactory = HttpClientBuilder::requestFactory(); + $this->assertInstanceOf(\Psr\Http\Message\RequestFactoryInterface::class, $requestFactory); + + $streamFactory = HttpClientBuilder::streamFactory(); + $this->assertInstanceOf(\Psr\Http\Message\StreamFactoryInterface::class, $streamFactory); + + // Verify they produce usable objects + $request = $requestFactory->createRequest('GET', 'https://example.com'); + $this->assertEquals('GET', $request->getMethod()); + + $stream = $streamFactory->createStream('hello'); + $this->assertEquals('hello', (string) $stream); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php new file mode 100644 index 000000000000..2c32002340e7 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php @@ -0,0 +1,76 @@ +name; + } + + /** + * @return string|null + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * @param array{ + * name: string, + * email?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->email = $values['email'] ?? null; + } +} + +class AdditionalPropertiesTest extends TestCase +{ + public function testExtraProperties(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'name' => 'john.doe', + 'email' => 'john.doe@example.com', + 'age' => 42 + ], + ); + + $person = Person::fromJson($expectedJson); + $this->assertEquals('john.doe', $person->getName()); + $this->assertEquals('john.doe@example.com', $person->getEmail()); + $this->assertEquals( + [ + 'age' => 42 + ], + $person->getAdditionalProperties(), + ); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php new file mode 100644 index 000000000000..e7794d652432 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php @@ -0,0 +1,54 @@ +dates = $values['dates']; + } +} + +class DateArrayTest extends TestCase +{ + public function testDateTimeInArrays(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ], + ); + + $object = DateArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php new file mode 100644 index 000000000000..b5f217e01f76 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php @@ -0,0 +1,71 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArrayTest extends TestCase +{ + public function testEmptyArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ], + ); + + $object = EmptyArray::fromJson($expectedJson); + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/EnumTest.php b/seed/php-sdk/allof/tests/Core/Json/EnumTest.php new file mode 100644 index 000000000000..72dc6f2cfa00 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/EnumTest.php @@ -0,0 +1,77 @@ +value; + } +} + +class ShapeType extends JsonSerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = JsonEncoder::encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ]); + + $actualJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $actualJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php b/seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php new file mode 100644 index 000000000000..4c288378b48b --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php @@ -0,0 +1,197 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class Type extends JsonSerializableType +{ + /** + * @var Nested nestedType + */ + #[JsonProperty('nested_type')] + public Nested $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[Date(Date::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[Date(Date::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(Nested::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: Nested, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class ExhaustiveTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in Type. + */ + public function testExhaustive(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // Omit 'nullable_property' to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56Z', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> + ], + ); + + $object = Type::fromJson($expectedJson); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/InvalidTest.php b/seed/php-sdk/allof/tests/Core/Json/InvalidTest.php new file mode 100644 index 000000000000..9d845ea113b8 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/InvalidTest.php @@ -0,0 +1,42 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTest extends TestCase +{ + public function testInvalidJsonThrowsException(): void + { + $this->expectException(\TypeError::class); + $json = JsonEncoder::encode( + [ + 'integer_property' => 'not_an_integer' + ], + ); + Invalid::fromJson($json); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php new file mode 100644 index 000000000000..8fbbeb939f02 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php @@ -0,0 +1,89 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArray extends JsonSerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTest extends TestCase +{ + public function testNestedUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ], + ); + + $object = NestedUnionArray::fromJson($expectedJson); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php b/seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php new file mode 100644 index 000000000000..ce20a2442825 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php @@ -0,0 +1,53 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullProperty( + [ + "nonNullProperty" => "Test String", + "nullProperty" => null + ] + ); + + $serialized = $object->jsonSerialize(); + $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); + $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php new file mode 100644 index 000000000000..d1749c434a4c --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php @@ -0,0 +1,49 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTest extends TestCase +{ + public function testNullableArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nullable_string_array' => ['one', null, 'three'] + ], + ); + + $object = NullableArray::fromJson($expectedJson); + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/ScalarTest.php b/seed/php-sdk/allof/tests/Core/Json/ScalarTest.php new file mode 100644 index 000000000000..ad4db0251bb5 --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/ScalarTest.php @@ -0,0 +1,116 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats + ], + ); + + $object = Scalar::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/TraitTest.php b/seed/php-sdk/allof/tests/Core/Json/TraitTest.php new file mode 100644 index 000000000000..e18f06d4191b --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/TraitTest.php @@ -0,0 +1,60 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ], + ); + + $object = TypeWithTrait::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php new file mode 100644 index 000000000000..de20cf9fde1b --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php @@ -0,0 +1,57 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class UnionArrayTest extends TestCase +{ + public function testUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00Z', + 2 => null, + 3 => 'Some String' + ] + ], + ); + + $object = UnionArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php b/seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php new file mode 100644 index 000000000000..f733062cfabc --- /dev/null +++ b/seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php @@ -0,0 +1,111 @@ + 'integer'], UnionProperty::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionProperty + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => [1 => 100, 2 => 200] + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => new UnionProperty( + [ + 'complexUnion' => 'Nested String' + ] + ) + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $expectedJson = JsonEncoder::encode( + [], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 42 + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 'Some String' + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json b/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json new file mode 100644 index 000000000000..e489d22a79b5 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json @@ -0,0 +1,10 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-python-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "enable_wire_tests": true + }, + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml b/seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml new file mode 100644 index 000000000000..fd1df043d08d --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: ci +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest -rP -n auto . + + - name: Install aiohttp extra + run: poetry install --extras aiohttp + + - name: Test (aiohttp) + run: poetry run pytest -rP -n auto -m aiohttp . + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" + env: + PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/python-sdk/allof-inline/no-custom-config/.gitignore b/seed/python-sdk/allof-inline/no-custom-config/.gitignore new file mode 100644 index 000000000000..d2e4ca808d21 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/.gitignore @@ -0,0 +1,5 @@ +.mypy_cache/ +.ruff_cache/ +__pycache__/ +dist/ +poetry.toml diff --git a/seed/python-sdk/allof-inline/no-custom-config/README.md b/seed/python-sdk/allof-inline/no-custom-config/README.md new file mode 100644 index 000000000000..327e202173c9 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/README.md @@ -0,0 +1,176 @@ +# Seed Python Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPython) +[![pypi](https://img.shields.io/pypi/v/fern_allof-inline)](https://pypi.python.org/pypi/fern_allof-inline) + +The Seed Python library provides convenient access to the Seed APIs from Python. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Async Client](#async-client) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Access Raw Response Data](#access-raw-response-data) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom Client](#custom-client) +- [Contributing](#contributing) + +## Installation + +```sh +pip install fern_allof-inline +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```python +from seed import SeedApi + +client = SeedApi() + +client.create_rule( + name="name", + execution_context="prod", +) +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) +``` + +## Async Client + +The SDK also exports an `async` client so that you can make non-blocking calls to our API. Note that if you are constructing an Async httpx client class to pass into this client, use `httpx.AsyncClient()` instead of `httpx.Client()` (e.g. for the `httpx_client` parameter of this client). + +```python +import asyncio + +from seed import AsyncSeedApi + +client = AsyncSeedApi() + + +async def main() -> None: + await client.create_rule( + name="name", + execution_context="prod", + ) + + +asyncio.run(main()) +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```python +from seed.core.api_error import ApiError + +try: + client.create_rule(...) +except ApiError as e: + print(e.status_code) + print(e.body) +``` + +## Advanced + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.with_raw_response` property. +The `.with_raw_response` property returns a "raw" client that can be used to access the `.headers` and `.data` attributes. + +```python +from seed import SeedApi + +client = SeedApi(...) +response = client.with_raw_response.create_rule(...) +print(response.headers) # access the response headers +print(response.status_code) # access the response status code +print(response.data) # access the underlying object +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` request option to configure this behavior. + +```python +client.create_rule(..., request_options={ + "max_retries": 1 +}) +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```python +from seed import SeedApi + +client = SeedApi(..., timeout=20.0) + +# Override timeout for a specific method +client.create_rule(..., request_options={ + "timeout_in_seconds": 1 +}) +``` + +### Custom Client + +You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies +and transports. + +```python +import httpx +from seed import SeedApi + +client = SeedApi( + ..., + httpx_client=httpx.Client( + proxy="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/allof-inline/no-custom-config/poetry.lock b/seed/python-sdk/allof-inline/no-custom-config/poetry.lock new file mode 100644 index 000000000000..75cfea1d0649 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/poetry.lock @@ -0,0 +1,1613 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = true +python-versions = ">=3.9" +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.13.5" +description = "Async http client/server framework (asyncio)" +optional = true +python-versions = ">=3.9" +files = [ + {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, + {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, + {file = "aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7"}, + {file = "aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9"}, + {file = "aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3"}, + {file = "aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06"}, + {file = "aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14"}, + {file = "aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3"}, + {file = "aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c"}, + {file = "aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc"}, + {file = "aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b"}, + {file = "aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3"}, + {file = "aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9"}, + {file = "aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8"}, + {file = "aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:347542f0ea3f95b2a955ee6656461fa1c776e401ac50ebce055a6c38454a0adf"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:178c7b5e62b454c2bc790786e6058c3cc968613b4419251b478c153a4aec32b1"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af545c2cffdb0967a96b6249e6f5f7b0d92cdfd267f9d5238d5b9ca63e8edb10"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:206b7b3ef96e4ce211754f0cd003feb28b7d81f0ad26b8d077a5d5161436067f"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee5e86776273de1795947d17bddd6bb19e0365fd2af4289c0d2c5454b6b1d36b"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95d14ca7abefde230f7639ec136ade282655431fd5db03c343b19dda72dd1643"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:912d4b6af530ddb1338a66229dac3a25ff11d4448be3ec3d6340583995f56031"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e999f0c88a458c836d5fb521814e92ed2172c649200336a6df514987c1488258"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39380e12bd1f2fdab4285b6e055ad48efbaed5c836433b142ed4f5b9be71036a"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9efcc0f11d850cefcafdd9275b9576ad3bfb539bed96807663b32ad99c4d4b88"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:147b4f501d0292077f29d5268c16bb7c864a1f054d7001c4c1812c0421ea1ed0"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d147004fede1b12f6013a6dbb2a26a986a671a03c6ea740ddc76500e5f1c399f"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9277145d36a01653863899c665243871434694bcc3431922c3b35c978061bdb8"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4e704c52438f66fdd89588346183d898bb42167cf88f8b7ff1c0f9fc957c348f"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8a4d3427e8de1312ddf309cc482186466c79895b3a139fed3259fc01dfa9a5b"}, + {file = "aiohttp-3.13.5-cp39-cp39-win32.whl", hash = "sha256:6f497a6876aa4b1a102b04996ce4c1170c7040d83faa9387dd921c16e30d5c83"}, + {file = "aiohttp-3.13.5-cp39-cp39-win_amd64.whl", hash = "sha256:cb979826071c0986a5f08333a36104153478ce6018c58cba7f9caddaf63d5d67"}, + {file = "aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = true +python-versions = ">=3.9" +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + +[[package]] +name = "anyio" +version = "4.13.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.10" +files = [ + {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, + {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +trio = ["trio (>=0.32.0)"] + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = true +python-versions = ">=3.8" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "attrs" +version = "26.1.0" +description = "Classes Without Boilerplate" +optional = true +python-versions = ">=3.9" +files = [ + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +files = [ + {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, + {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +files = [ + {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, + {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, + {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.1.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "frozenlist" +version = "1.8.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = true +python-versions = ">=3.9" +files = [ + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, + {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, + {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, + {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, + {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, + {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, + {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, + {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, + {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, +] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "httpx-aiohttp" +version = "0.1.8" +description = "Aiohttp transport for HTTPX" +optional = true +python-versions = ">=3.8" +files = [ + {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, + {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, +] + +[package.dependencies] +aiohttp = ">=3.10.0,<4" +httpx = ">=0.27.0" + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "multidict" +version = "6.7.1" +description = "multidict implementation" +optional = true +python-versions = ">=3.9" +files = [ + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, + {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, + {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, + {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, + {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, + {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, + {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, + {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, + {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, + {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, + {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, + {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, + {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, + {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, + {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, + {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, + {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, + {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, + {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, + {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, + {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, + {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, + {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, + {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, + {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, + {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, + {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "propcache" +version = "0.4.1" +description = "Accelerated property cache" +optional = true +python-versions = ">=3.9" +files = [ + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, + {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, + {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, + {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, + {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, + {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, + {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, + {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, + {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, + {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, + {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, + {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, + {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, + {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, + {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, + {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, + {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, + {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, + {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, + {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, + {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, + {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, + {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, + {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, + {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, + {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, + {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, +] + +[[package]] +name = "pydantic" +version = "1.10.26" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546"}, + {file = "pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572"}, + {file = "pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464"}, + {file = "pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53"}, + {file = "pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945"}, + {file = "pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637"}, + {file = "pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77"}, + {file = "pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f"}, + {file = "pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5"}, + {file = "pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df"}, + {file = "pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f"}, + {file = "pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f"}, + {file = "pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb"}, + {file = "pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b"}, + {file = "pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c"}, + {file = "pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c"}, + {file = "pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba"}, + {file = "pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62"}, + {file = "pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847"}, + {file = "pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314"}, + {file = "pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba"}, + {file = "pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37"}, + {file = "pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a"}, + {file = "pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962"}, + {file = "pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c"}, + {file = "pydantic-1.10.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc5c91a3b3106caf07ac6735ec6efad8ba37b860b9eb569923386debe65039ad"}, + {file = "pydantic-1.10.26-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dde599e0388e04778480d57f49355c9cc7916de818bf674de5d5429f2feebfb6"}, + {file = "pydantic-1.10.26-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be08b5cfe88e58198722861c7aab737c978423c3a27300911767931e5311d0d"}, + {file = "pydantic-1.10.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0141f4bafe5eda539d98c9755128a9ea933654c6ca4306b5059fc87a01a38573"}, + {file = "pydantic-1.10.26-cp38-cp38-win_amd64.whl", hash = "sha256:eb664305ffca8a9766a8629303bb596607d77eae35bb5f32ff9245984881b638"}, + {file = "pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d"}, + {file = "pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15"}, + {file = "pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90"}, + {file = "pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba"}, + {file = "pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96"}, + {file = "pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917"}, + {file = "pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pydantic-core" +version = "2.42.0" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.42.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:0ae7d50a47ada2a04f7296be9a7a2bf447118a25855f41fc52c8fc4bfb70c105"}, + {file = "pydantic_core-2.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c9d04d4bd8de1dcd5c8845faf6c11e36cda34c2efffa29d70ad83cc6f6a6c9a8"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e459e89453bb1bc69853272260afb5328ae404f854ddec485f5427fbace8d7e"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:def66968fbe20274093fd4fc85d82b2ec42dbe20d9e51d27bbf3b5c7428c7a10"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:272fab515dc7da0f456c49747b87b4e8721a33ab352a54760cc8fd1a4fd5348a"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa82dec59f36106738ae981878e0001074e2b3a949f21a5b3bea20485b9c6db4"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a70fe4db00ab03a9f976d28471c8e696ebd3b8455ccfa5e36e5d1a2ff301a7"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b4c0f656b4fa218413a485c550ac3e4ddf2f343a9c46b6137394bd77c4128445"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a4396ffc8b42499d14662f958b3f00656b62a67bde7f156580fd618827bebf5a"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:36067825f365a5c3065f17d08421a72b036ff4588c450afe54d5750b80cc220d"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eec64367de940786c0b686d47bd952692018dd7cd895027aa82023186e469b7d"}, + {file = "pydantic_core-2.42.0-cp310-cp310-win32.whl", hash = "sha256:ff9f0737f487277721682d8518434557cfcef141ba55b89381c92700594a8b65"}, + {file = "pydantic_core-2.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:77f0a8ab035d3bc319b759d8215f51846e9ea582dacbabb2777e5e3e135a048e"}, + {file = "pydantic_core-2.42.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1159b9ee73511ae7c5631b108d80373577bc14f22d18d85bb2aa1fa1051dabc"}, + {file = "pydantic_core-2.42.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff8e49b22225445d3e078aaa9bead90c37c852aee8f8a169ba15fdaaa13d1ecb"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe777d9a1a932c6b3ef32b201985324d06d9c74028adef1e1c7ea226fca2ba34"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e92592c1040ed17968d603e05b72acec321662ef9bf88fef443ceae4d1a130c2"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:557a6eb6dc4db8a3f071929710feb29c6b5d7559218ab547a4e60577fb404f2f"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4035f81e7d1a5e065543061376ca52ccb0accaf970911ba0a9ec9d22062806ca"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63a4e073f8def1c7fd100a355b3a96e1bbaf0446b6a8530ae58f1afaa0478a46"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd8469c8d9f6c81befd10c72a0268079e929ba494cd27fa63e868964b0e04fb6"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bdebfd610a02bdb82f8e36dc7d4683e03e420624a2eda63e1205730970021308"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9577eb5221abd4e5adf8a232a65f74c509b82b57b7b96b3667dac22f03ff9e94"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6d36841b61100128c2374341a7c2c0ab347ef4b63aa4b6837b4431465d4d4fd"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win32.whl", hash = "sha256:1d9d45333a28b0b8fb8ecedf67d280dc3318899988093e4d3a81618396270697"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:4631b4d1a3fe460aadd3822af032bb6c2e7ad77071fbf71c4e95ef9083c7c1a8"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d46bfc6175a4b4b80b9f98f76133fbf68d5a02d7469b3090ca922d40f23d32d"}, + {file = "pydantic_core-2.42.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a11b9115364681779bcc39c6b9cdc20d48a9812a4bf3ed986fec4f694ed3a1e7"}, + {file = "pydantic_core-2.42.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c43088e8a44ccb2a2329d83892110587ebe661090b546dd03624a933fc4cfd0d"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13a7f9dde97c8400de559b2b2dcd9439f7b2b8951dad9b19711ef8c6e3f68ac0"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6380214c627f702993ea6b65b6aa8afc0f1481a179cdd169a2fc80a195e21158"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:606f80d8c61d4680ff82a34e9c49b7ab069b544b93393cc3c5906ac9e8eec7c9"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ab80ae93cb739de6c9ccc06a12cd731b079e1b25b03e2dcdccbc914389cc7e0"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:638f04b55bea04ec5bbda57a4743a51051f24b884abcb155b0ed2c3cb59ba448"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec72ba5c7555f69757b64b398509c7079fb22da705a6c67ac613e3f14a05f729"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0364f6cd61be57bcd629c34788c197db211e91ce1c3009bf4bf97f6bb0eb21f"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:856f0fd81173b308cd6ceb714332cd9ea3c66ce43176c7defaed6b2ed51d745c"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1be705396e480ea96fd3cccd7512affda86823b8a2a8c196d9028ec37cb1ca77"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win32.whl", hash = "sha256:acacf0795d68e42d01ae8cc77ae19a5b3c80593e0fd60e4e2d336ec13d3de906"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win_amd64.whl", hash = "sha256:475a1a5ecf3a748a0d066b56138d258018c8145873ee899745c9f0e0af1cc4d4"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win_arm64.whl", hash = "sha256:e2369cef245dd5aeafe6964cf43d571fb478f317251749c152c0ae564127053a"}, + {file = "pydantic_core-2.42.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:02fd2b4a62efa12e004fce2bfd2648cf8c39efc5dfc5ed5f196eb4ccefc7db4e"}, + {file = "pydantic_core-2.42.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c042694870c20053b8814a57c416cd2c6273fe462a440460005c791c24c39baf"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f905f3a082e7498dfaa70c204b236e92d448ba966ad112a96fcaaba2c4984fba"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4762081e8acc5458bf907373817cf93c927d451a1b294c1d0535b0570890d939"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4a433bbf6304bd114b96b0ce3ed9add2ee686df448892253bca5f622c030f31"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd695305724cfce8b19a18e87809c518f56905e5c03a19e3ad061974970f717d"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f352ffa0ec2983b849a93714571063bfc57413b5df2f1027d7a04b6e8bdd25"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e61f2a194291338d76307a29e4881a8007542150b750900c1217117fc9bb698e"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:032f990dc1759f11f6b287e5c6eb1b0bcfbc18141779414a77269b420360b3bf"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:9c28b42768da6b9238554ae23b39291c3bbe6f53c4810aea6414d83efd59b96a"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b22af1ac75fa873d81a65cce22ada1d840583b73a129b06133097c81f6f9e53b"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win32.whl", hash = "sha256:1de0350645c8643003176659ee70b637cd80e8514a063fff36f088fcda2dba06"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win_amd64.whl", hash = "sha256:d34b481a8a3eba3678a96e166c6e547c0c8b026844c13d9deb70c9f1fd2b0979"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win_arm64.whl", hash = "sha256:5e0a65358eef041d95eef93fcf8834c2c8b83cc5a92d32f84bb3a7955dfe21c9"}, + {file = "pydantic_core-2.42.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:de4c9ad4615983b3fb2ee57f5c570cf964bda13353c6c41a54dac394927f0e54"}, + {file = "pydantic_core-2.42.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:129d5e6357814e4567e18b2ded4c210919aafd9ef0887235561f8d853fd34123"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c45582a5dac4649e512840ad212a5c2f9d168622f8db8863e8a29b54a29dfd"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a97fc19afb730b45de55d2e80093f1a36effc29538dec817204c929add8f2b4a"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45d83d38d94f22ffe9a0f0393b23e25bfefe4804ae63c8013906b76ab8de8ed"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3060192d8b63611a2abb26eccadddff5602a66491b8fafd9ae34fb67302ae84"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f17739150af9dc58b5c8fc3c4a1826ff84461f11b9f8ad5618445fcdd1ccec6"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d14e4c229467a7c27aa7c71e21584b3d77352ccb64e968fdbed4633373f73f7"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:aaef75e1b54366c7ccfbf4fc949ceaaa0f4c87e106df850354be6c7d45143db0"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:d2e362dceeeb4d56fd63e649c2de3ad4c3aa448b13ab8a9976e23a669f9c1854"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:a8edee724b527818bf0a6c8e677549794c0d0caffd14492851bd7a4ceab0f258"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win32.whl", hash = "sha256:a10c105c221f68221cb81be71f063111172f5ddf8b06f6494560e826c148f872"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win_amd64.whl", hash = "sha256:232d86e00870aceee7251aa5f4ab17e3e4864a4656c015f8e03d1223bf8e17ba"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win_arm64.whl", hash = "sha256:9a6fce4e778c2fe2b3f1df63bfaa522c147668517ba040c49ad7f67a66867cff"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f4d1670fbc5488cfb18dd9fc71a2c7c8e12caeeb6e5bb641aa351ac5e01963cf"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:baeae16666139d0110f1006a06809228f5293ab84e77f4b9dda2bdee95d6c4e8"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a77c7a8cedf5557a4e5547dabf55a8ec99949162bd7925b312f6ec37c24101c"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:133fccf13546ff2a0610cc5b978dd4ee2c7f55a7a86b6b722fd6e857694bacc5"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad5dbebfbab92cf0f6d0b13d55bf0a239880a1534377edf6387e2e7a4469f131"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6c0181016cb29ba4824940246606a8e13b1135de8306e00b5bd9d1efbc4cf85"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:020cfd7041cb71eac4dc93a29a6d5ec34f10b1fdc37f4f189c25bcc6748a2f97"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73c6de3ee24f2b614d344491eda5628c4cdf3e7b79c0ac69bb40884ced2d319"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b2b448da50e1e8d5aac786dcf441afa761d26f1be4532b52cdf50864b47bd784"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:0df0488b1f548ef874b45bbc60a70631eee0177b79b5527344d7a253e77a5ed2"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:b8aa32697701dc36c956f4a78172549adbe25eacba952bbfbde786fb66316151"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win32.whl", hash = "sha256:173de56229897ff81b650ca9ed6f4c62401c49565234d3e9ae251119f6fd45c6"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2db227cf6797c286361f8d1e52b513f358a3ff9ebdede335e55a5edf4c59f06b"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a983862733ecaf0b5c7275145f86397bde4ee1ad84cf650e1d7af7febe5f7073"}, + {file = "pydantic_core-2.42.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fc0834a2d658189c89d7a009ae19462da1d70fc4786d2b8e5c8c6971f4d3bcc1"}, + {file = "pydantic_core-2.42.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff69cf1eb517600d40c903dbc3507360e0a6c1ffa2dcf3cfa49a1c6fe203a46a"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3eab236da1c53a8cdf741765e31190906eb2838837bfedcaa6c0206b8f5975e"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15df82e324fa5b2b1403d5eb1bb186d14214c3ce0aebc9a3594435b82154d402"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ee7047297892d4fec68658898b7495be8c1a8a2932774e2d6810c3de1173783"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aec13272d859be1dd3344b75aab4d1d6690bfef78bd241628f6903c2bf101f8d"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e7adfd7794da8ae101d2d5e6a7be7cb39bb90d45b6aa42ecb502a256e94f8e0"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e3cfcacb42193479ead3aaba26a79e7df4c1c2415aefc43f1a60b57f50f8aa4"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf89cee72f88db54763f800d32948bd6b1b9bf03e0ecb0a9cb93eac513caec5f"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c6ae4c08e6c4b08e35eb2b114803d09c5012602983d8bbd3564013d555dfe5fd"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dfedd24ce01a3ea32f29c257e5a7fc79ed635cff0bd1a1aed12a22d3440cb39f"}, + {file = "pydantic_core-2.42.0-cp39-cp39-win32.whl", hash = "sha256:26ab24eecdec230bdf7ec519b9cd0c65348ec6e97304e87f9d3409749ea3377b"}, + {file = "pydantic_core-2.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:f93228d630913af3bc2d55a50a96e0d33446b219aea9591bfdc0a06677f689ff"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:53ab90bed3a191750a6726fe2570606a9794608696063823d2deea734c100bf6"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:b8d9911a3cdb8062f4102499b666303c9a976202b420200a26606eafa0bfecf8"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe6b7b22dd1d326a1ab23b9e611a69c41d606cb723839755bb00456ebff3f672"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5e36849ca8e2e39828a70f1a86aa2b86f645a1d710223b6653f2fa8a130b703"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4d7e36c2a1f3c0020742190714388884a11282a0179f3d1c55796ee26b32dba5"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:41a702c2ac3dbbafa7d13bea142b3e04c8676d1fca199bac52b5ee24e6cdb737"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad5cb8ed96ffac804a0298f5d03f002769514700d79cbe77b66a27a6e605a65a"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51e33cf940cddcad333f85e15a25a2a949ac0a7f26fe8f43dc2d6816ce974ec4"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495e70705f553c3b8f939965fa7cf77825c81417ff3c7ac046be9509b94c292c"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8757702cc696d48f9fdcb65cb835ca18bda5d83169fe6d13efd706e4195aea81"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32cc3087f38e4a9ee679f6184670a1b6591b8c3840c483f3342e176e215194d1"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e824d8f372aa717eeb435ee220c8247e514283a4fc0ecdc4ce44c09ee485a5b8"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e5900b257abb20371135f28b686d6990202dcdd9b7d8ff2e2290568aa0058280"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:f6705c73ab2abaebef81cad882a75afd6b8a0550e853768933610dce2945705e"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5ed95136324ceef6f33bd96ee3a299d36169175401204590037983aeb5bc73de"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9d729a3934e0ef3bc171025f0414d422aa6397d6bbd8176d5402739140e50616"}, + {file = "pydantic_core-2.42.0.tar.gz", hash = "sha256:34068adadf673c872f01265fa17ec00073e99d7f53f6d499bdfae652f330b3d2"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, +] + +[package.dependencies] +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "requests" +version = "2.33.1" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.10" +files = [ + {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, + {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, +] + +[package.dependencies] +certifi = ">=2023.5.7" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.26,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] + +[[package]] +name = "ruff" +version = "0.11.5" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, + {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, + {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, + {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, + {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, + {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, + {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tomli" +version = "2.4.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20260408" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.10" +files = [ + {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, + {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, +] + +[[package]] +name = "types-requests" +version = "2.33.0.20260408" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.10" +files = [ + {file = "types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f"}, + {file = "types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +files = [ + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, +] + +[package.extras] +brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["backports-zstd (>=1.0.0)"] + +[[package]] +name = "yarl" +version = "1.23.0" +description = "Yet another URL library" +optional = true +python-versions = ">=3.10" +files = [ + {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, + {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, + {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, + {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, + {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, + {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, + {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, + {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, + {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, + {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, + {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, + {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, + {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, + {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, + {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, + {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, + {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, + {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, + {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, + {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, + {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, + {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, + {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, + {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, + {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, + {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + +[extras] +aiohttp = ["aiohttp", "httpx-aiohttp"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "a8bb006c9955dae7967f3694092cf6799a89e0aac94e82e903d8b308c4d0cfc4" diff --git a/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml b/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml new file mode 100644 index 000000000000..53eee1990c99 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml @@ -0,0 +1,102 @@ +[project] +name = "fern_allof-inline" +dynamic = ["version"] + +[tool.poetry] +name = "fern_allof-inline" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [ + "fern", + "test" +] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed", from = "src"} +] + +[tool.poetry.urls] +Documentation = 'https://buildwithfern.com/learn' +Homepage = 'https://buildwithfern.com/' +Repository = 'https://github.com/allof-inline/fern' + +[tool.poetry.dependencies] +python = "^3.10" +aiohttp = { version = ">=3.10.0,<4", optional = true} +httpx = ">=0.21.2" +httpx-aiohttp = { version = "0.1.8", optional = true} +pydantic = "^1.10" +pydantic-core = ">=2.18.2,<2.44.0" +typing_extensions = ">= 4.0.0" + +[tool.poetry.group.dev.dependencies] +mypy = "==1.13.0" +pytest = "^8.2.0" +pytest-asyncio = "^1.0.0" +pytest-xdist = "^3.6.1" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" +requests = "^2.31.0" +types-requests = "^2.31.0" +ruff = "==0.11.5" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" +markers = [ + "aiohttp: tests that require httpx_aiohttp to be installed", +] + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort +] +ignore = [ + "E402", # Module level import not at top of file + "E501", # Line too long + "E711", # Comparison to `None` should be `cond is not None` + "E712", # Avoid equality comparisons to `True`; use `if ...:` checks + "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks + "E722", # Do not use bare `except` + "E731", # Do not assign a `lambda` expression, use a `def` + "F821", # Undefined name + "F841" # Local variable ... is assigned to but never used +] + +[tool.ruff.lint.isort] +section-order = ["future", "standard-library", "third-party", "first-party"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.extras] +aiohttp=["aiohttp", "httpx-aiohttp"] diff --git a/seed/python-sdk/allof-inline/no-custom-config/reference.md b/seed/python-sdk/allof-inline/no-custom-config/reference.md new file mode 100644 index 000000000000..e92bd3a6925d --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/reference.md @@ -0,0 +1,268 @@ +# Reference +
client.search_rule_types(...) -> RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.search_rule_types() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `typing.Optional[str]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.create_rule(...) -> RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.create_rule( + name="name", + execution_context="prod", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `str` + +
+
+ +
+
+ +**execution_context:** `RuleExecutionContext` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.list_users() -> UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.list_users() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.get_entity() -> CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.get_entity() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.get_organization() -> Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.get_organization() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/python-sdk/allof-inline/no-custom-config/requirements.txt b/seed/python-sdk/allof-inline/no-custom-config/requirements.txt new file mode 100644 index 000000000000..0141a1a5014b --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/requirements.txt @@ -0,0 +1,4 @@ +httpx>=0.21.2 +pydantic>= 1.9.2 +pydantic-core>=2.18.2,<2.44.0 +typing_extensions>= 4.0.0 diff --git a/seed/python-sdk/allof-inline/no-custom-config/snippet.json b/seed/python-sdk/allof-inline/no-custom-config/snippet.json new file mode 100644 index 000000000000..f000c2ddf786 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/snippet.json @@ -0,0 +1,70 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": "default", + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.search_rule_types()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.search_rule_types()\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.list_users()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.list_users()\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_entity()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_entity()\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_organization()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_organization()\n\n\nasyncio.run(main())\n", + "type": "python" + } + } + ] +} \ No newline at end of file diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py new file mode 100644 index 000000000000..fce5a035510d --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py @@ -0,0 +1,113 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + AuditInfo, + BaseOrg, + BaseOrgMetadata, + CombinedEntity, + CombinedEntityStatus, + Describable, + DetailedOrg, + DetailedOrgMetadata, + Identifiable, + Organization, + OrganizationMetadata, + PaginatedResult, + PagingCursors, + RuleExecutionContext, + RuleResponse, + RuleResponseStatus, + RuleType, + RuleTypeSearchResponse, + User, + UserSearchResponse, + ) + from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient + from .client import AsyncSeedApi, SeedApi + from .environment import SeedApiEnvironment + from .version import __version__ +_dynamic_imports: typing.Dict[str, str] = { + "AsyncSeedApi": ".client", + "AuditInfo": ".types", + "BaseOrg": ".types", + "BaseOrgMetadata": ".types", + "CombinedEntity": ".types", + "CombinedEntityStatus": ".types", + "DefaultAioHttpClient": "._default_clients", + "DefaultAsyncHttpxClient": "._default_clients", + "Describable": ".types", + "DetailedOrg": ".types", + "DetailedOrgMetadata": ".types", + "Identifiable": ".types", + "Organization": ".types", + "OrganizationMetadata": ".types", + "PaginatedResult": ".types", + "PagingCursors": ".types", + "RuleExecutionContext": ".types", + "RuleResponse": ".types", + "RuleResponseStatus": ".types", + "RuleType": ".types", + "RuleTypeSearchResponse": ".types", + "SeedApi": ".client", + "SeedApiEnvironment": ".environment", + "User": ".types", + "UserSearchResponse": ".types", + "__version__": ".version", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AsyncSeedApi", + "AuditInfo", + "BaseOrg", + "BaseOrgMetadata", + "CombinedEntity", + "CombinedEntityStatus", + "DefaultAioHttpClient", + "DefaultAsyncHttpxClient", + "Describable", + "DetailedOrg", + "DetailedOrgMetadata", + "Identifiable", + "Organization", + "OrganizationMetadata", + "PaginatedResult", + "PagingCursors", + "RuleExecutionContext", + "RuleResponse", + "RuleResponseStatus", + "RuleType", + "RuleTypeSearchResponse", + "SeedApi", + "SeedApiEnvironment", + "User", + "UserSearchResponse", + "__version__", +] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py new file mode 100644 index 000000000000..61f3fedfc0ce --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +SDK_DEFAULT_TIMEOUT = 60 + +try: + import httpx_aiohttp # type: ignore[import-not-found] +except ImportError: + + class DefaultAioHttpClient(httpx.AsyncClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + raise RuntimeError( + "To use the aiohttp client, install the aiohttp extra: pip install fern_allof-inline[aiohttp]" + ) + +else: + + class DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +class DefaultAsyncHttpxClient(httpx.AsyncClient): + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py new file mode 100644 index 000000000000..65adf5ca16bc --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py @@ -0,0 +1,500 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .core.logging import LogConfig, Logger +from .core.request_options import RequestOptions +from .environment import SeedApiEnvironment +from .raw_client import AsyncRawSeedApi, RawSeedApi +from .types.combined_entity import CombinedEntity +from .types.organization import Organization +from .types.rule_execution_context import RuleExecutionContext +from .types.rule_response import RuleResponse +from .types.rule_type_search_response import RuleTypeSearchResponse +from .types.user_search_response import UserSearchResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : SeedApiEnvironment + The environment to use for requests from the client. from .environment import SeedApiEnvironment + + + + Defaults to SeedApiEnvironment.DEFAULT + + + + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = ( + timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read + ) + self._client_wrapper = SyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + headers=headers, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + logging=logging, + ) + self._raw_client = RawSeedApi(client_wrapper=self._client_wrapper) + + @property + def with_raw_response(self) -> RawSeedApi: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSeedApi + """ + return self._raw_client + + def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> RuleTypeSearchResponse: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleTypeSearchResponse + Paginated list of rule types + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.search_rule_types() + """ + _response = self._raw_client.search_rule_types(query=query, request_options=request_options) + return _response.data + + def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> RuleResponse: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleResponse + Created rule + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.create_rule( + name="name", + execution_context="prod", + ) + """ + _response = self._raw_client.create_rule( + name=name, execution_context=execution_context, request_options=request_options + ) + return _response.data + + def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UserSearchResponse + Paginated list of users + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.list_users() + """ + _response = self._raw_client.list_users(request_options=request_options) + return _response.data + + def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CombinedEntity + An entity with properties from multiple parents + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.get_entity() + """ + _response = self._raw_client.get_entity(request_options=request_options) + return _response.data + + def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Organization + An organization whose metadata is merged from two parents + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.get_organization() + """ + _response = self._raw_client.get_organization(request_options=request_options) + return _response.data + + +def _make_default_async_client( + timeout: typing.Optional[float], + follow_redirects: typing.Optional[bool], +) -> httpx.AsyncClient: + try: + import httpx_aiohttp # type: ignore[import-not-found] + except ImportError: + pass + else: + if follow_redirects is not None: + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout) + + if follow_redirects is not None: + return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx.AsyncClient(timeout=timeout) + + +class AsyncSeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : SeedApiEnvironment + The environment to use for requests from the client. from .environment import SeedApiEnvironment + + + + Defaults to SeedApiEnvironment.DEFAULT + + + + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from seed import AsyncSeedApi + + client = AsyncSeedApi() + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = ( + timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read + ) + self._client_wrapper = AsyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + headers=headers, + httpx_client=httpx_client + if httpx_client is not None + else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), + timeout=_defaulted_timeout, + logging=logging, + ) + self._raw_client = AsyncRawSeedApi(client_wrapper=self._client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSeedApi: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSeedApi + """ + return self._raw_client + + async def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> RuleTypeSearchResponse: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleTypeSearchResponse + Paginated list of rule types + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.search_rule_types() + + + asyncio.run(main()) + """ + _response = await self._raw_client.search_rule_types(query=query, request_options=request_options) + return _response.data + + async def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> RuleResponse: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleResponse + Created rule + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.create_rule( + name="name", + execution_context="prod", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_rule( + name=name, execution_context=execution_context, request_options=request_options + ) + return _response.data + + async def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UserSearchResponse + Paginated list of users + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.list_users() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_users(request_options=request_options) + return _response.data + + async def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CombinedEntity + An entity with properties from multiple parents + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.get_entity() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_entity(request_options=request_options) + return _response.data + + async def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Organization + An organization whose metadata is merged from two parents + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.get_organization() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_organization(request_options=request_options) + return _response.data + + +def _get_base_url(*, base_url: typing.Optional[str] = None, environment: SeedApiEnvironment) -> str: + if base_url is not None: + return base_url + elif environment is not None: + return environment.value + else: + raise Exception("Please pass in either base_url or environment to construct the client") diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py new file mode 100644 index 000000000000..5bc159a110f2 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py @@ -0,0 +1,127 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .api_error import ApiError + from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper + from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime + from .file import File, convert_file_dict_to_httpx_tuples, with_content_type + from .http_client import AsyncHttpClient, HttpClient + from .http_response import AsyncHttpResponse, HttpResponse + from .jsonable_encoder import encode_path_param, jsonable_encoder + from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger + from .parse_error import ParsingError + from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, + ) + from .query_encoder import encode_query + from .remove_none_from_dict import remove_none_from_dict + from .request_options import RequestOptions + from .serialization import FieldMetadata, convert_and_respect_annotation_metadata +_dynamic_imports: typing.Dict[str, str] = { + "ApiError": ".api_error", + "AsyncClientWrapper": ".client_wrapper", + "AsyncHttpClient": ".http_client", + "AsyncHttpResponse": ".http_response", + "BaseClientWrapper": ".client_wrapper", + "ConsoleLogger": ".logging", + "FieldMetadata": ".serialization", + "File": ".file", + "HttpClient": ".http_client", + "HttpResponse": ".http_response", + "ILogger": ".logging", + "IS_PYDANTIC_V2": ".pydantic_utilities", + "LogConfig": ".logging", + "LogLevel": ".logging", + "Logger": ".logging", + "ParsingError": ".parse_error", + "RequestOptions": ".request_options", + "Rfc2822DateTime": ".datetime_utils", + "SyncClientWrapper": ".client_wrapper", + "UniversalBaseModel": ".pydantic_utilities", + "UniversalRootModel": ".pydantic_utilities", + "convert_and_respect_annotation_metadata": ".serialization", + "convert_file_dict_to_httpx_tuples": ".file", + "create_logger": ".logging", + "encode_path_param": ".jsonable_encoder", + "encode_query": ".query_encoder", + "jsonable_encoder": ".jsonable_encoder", + "parse_obj_as": ".pydantic_utilities", + "parse_rfc2822_datetime": ".datetime_utils", + "remove_none_from_dict": ".remove_none_from_dict", + "serialize_datetime": ".datetime_utils", + "universal_field_validator": ".pydantic_utilities", + "universal_root_validator": ".pydantic_utilities", + "update_forward_refs": ".pydantic_utilities", + "with_content_type": ".file", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "AsyncHttpResponse", + "BaseClientWrapper", + "ConsoleLogger", + "FieldMetadata", + "File", + "HttpClient", + "HttpResponse", + "ILogger", + "IS_PYDANTIC_V2", + "LogConfig", + "LogLevel", + "Logger", + "ParsingError", + "RequestOptions", + "Rfc2822DateTime", + "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "create_logger", + "encode_path_param", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "parse_rfc2822_datetime", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", + "with_content_type", +] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py new file mode 100644 index 000000000000..6f850a60cba3 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ApiError(Exception): + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py new file mode 100644 index 000000000000..381e10b9c2ea --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py @@ -0,0 +1,95 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx +from .http_client import AsyncHttpClient, HttpClient +from .logging import LogConfig, Logger + + +class BaseClientWrapper: + def __init__( + self, + *, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self._headers = headers + self._base_url = base_url + self._timeout = timeout + self._logging = logging + + def get_headers(self) -> typing.Dict[str, str]: + import platform + + headers: typing.Dict[str, str] = { + "User-Agent": "fern_allof-inline/0.0.1", + "X-Fern-Language": "Python", + "X-Fern-Runtime": f"python/{platform.python_version()}", + "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", + "X-Fern-SDK-Name": "fern_allof-inline", + "X-Fern-SDK-Version": "0.0.1", + **(self.get_custom_headers() or {}), + } + return headers + + def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]: + return self._headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + +class SyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + httpx_client: httpx.Client, + ): + super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + logging_config=self._logging, + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, + httpx_client: httpx.AsyncClient, + ): + super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) + self._async_token = async_token + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + async_base_headers=self.async_get_headers, + logging_config=self._logging, + ) + + async def async_get_headers(self) -> typing.Dict[str, str]: + headers = self.get_headers() + if self._async_token is not None: + token = await self._async_token() + headers["Authorization"] = f"Bearer {token}" + return headers diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py new file mode 100644 index 000000000000..a12b2ad03c53 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +from email.utils import parsedate_to_datetime +from typing import Any + +import pydantic + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + + +def parse_rfc2822_datetime(v: Any) -> dt.datetime: + """ + Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") + into a datetime object. If the value is already a datetime, return it as-is. + Falls back to ISO 8601 parsing if RFC 2822 parsing fails. + """ + if isinstance(v, dt.datetime): + return v + if isinstance(v, str): + try: + return parsedate_to_datetime(v) + except Exception: + pass + # Fallback to ISO 8601 parsing + return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) + raise ValueError(f"Expected str or datetime, got {type(v)}") + + +class Rfc2822DateTime(dt.datetime): + """A datetime subclass that parses RFC 2822 date strings. + + On Pydantic V1, uses __get_validators__ for pre-validation. + On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. + """ + + @classmethod + def __get_validators__(cls): # type: ignore[no-untyped-def] + yield parse_rfc2822_datetime + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] + from pydantic_core import core_schema + + return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py new file mode 100644 index 000000000000..44b0d27c0895 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = Union[IO[bytes], bytes, str] +File = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[ + Optional[str], + FileContent, + Optional[str], + Mapping[str, str], + ], +] + + +def convert_file_dict_to_httpx_tuples( + d: Dict[str, Union[File, List[File]]], +) -> List[Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples + + +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ + if isinstance(file, tuple): + if len(file) == 2: + filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore + return (filename, content, default_content_type) + elif len(file) == 3: + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) + elif len(file) == 4: + filename, content, file_content_type, headers = cast( # type: ignore + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file + ) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) + else: + raise ValueError(f"Unexpected tuple length: {len(file)}") + return (None, file, default_content_type) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py new file mode 100644 index 000000000000..5440913fd4bc --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict + + +class ForceMultipartDict(Dict[str, Any]): + """ + A dictionary subclass that always evaluates to True in boolean contexts. + + This is used to force multipart/form-data encoding in HTTP requests even when + the dictionary is empty, which would normally evaluate to False. + """ + + def __bool__(self) -> bool: + return True + + +FORCE_MULTIPART = ForceMultipartDict() diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py new file mode 100644 index 000000000000..f0a39ca8243a --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py @@ -0,0 +1,840 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import re +import time +import typing +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx +from .file import File, convert_file_dict_to_httpx_tuples +from .force_multipart import FORCE_MULTIPART +from .jsonable_encoder import jsonable_encoder +from .logging import LogConfig, Logger, create_logger +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict +from .request_options import RequestOptions +from httpx._types import RequestFiles + +INITIAL_RETRY_DELAY_SECONDS = 1.0 +MAX_RETRY_DELAY_SECONDS = 60.0 +JITTER_FACTOR = 0.2 # 20% random jitter + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _add_positive_jitter(delay: float) -> float: + """Add positive jitter (0-20%) to prevent thundering herd.""" + jitter_multiplier = 1 + random() * JITTER_FACTOR + return delay * jitter_multiplier + + +def _add_symmetric_jitter(delay: float) -> float: + """Add symmetric jitter (±10%) for exponential backoff.""" + jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR + return delay * jitter_multiplier + + +def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + Parse the X-RateLimit-Reset header (Unix timestamp in seconds). + Returns seconds to wait, or None if header is missing/invalid. + """ + reset_time_str = response_headers.get("x-ratelimit-reset") + if reset_time_str is None: + return None + + try: + reset_time = int(reset_time_str) + delay = reset_time - time.time() + if delay > 0: + return delay + except (ValueError, TypeError): + pass + + return None + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # 1. Check Retry-After header first + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after > 0: + return min(retry_after, MAX_RETRY_DELAY_SECONDS) + + # 2. Check X-RateLimit-Reset header (with positive jitter) + ratelimit_reset = _parse_x_ratelimit_reset(response.headers) + if ratelimit_reset is not None: + return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS)) + + # 3. Fall back to exponential backoff (with symmetric jitter) + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _retry_timeout_from_retries(retries: int) -> float: + """Determine retry timeout using exponential backoff when no response is available.""" + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _should_retry(response: httpx.Response) -> bool: + retryable_400s = [429, 408, 409] + return response.status_code >= 500 or response.status_code in retryable_400s + + +_SENSITIVE_HEADERS = frozenset( + { + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "cookie", + "set-cookie", + "proxy-authorization", + "proxy-authenticate", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token", + } +) + + +def _redact_headers(headers: typing.Dict[str, str]) -> typing.Dict[str, str]: + return {k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()} + + +def _build_url(base_url: str, path: typing.Optional[str]) -> str: + """ + Build a full URL by joining a base URL with a path. + + This function correctly handles base URLs that contain path prefixes (e.g., tenant-based URLs) + by using string concatenation instead of urllib.parse.urljoin(), which would incorrectly + strip path components when the path starts with '/'. + + Example: + >>> _build_url("https://cloud.example.com/org/tenant/api", "/users") + 'https://cloud.example.com/org/tenant/api/users' + + Args: + base_url: The base URL, which may contain path prefixes. + path: The path to append. Can be None or empty string. + + Returns: + The full URL with base_url and path properly joined. + """ + if not path: + return base_url + return f"{base_url.rstrip('/')}/{path.lstrip('/')}" + + +def _maybe_filter_none_from_multipart_data( + data: typing.Optional[typing.Any], + request_files: typing.Optional[RequestFiles], + force_multipart: typing.Optional[bool], +) -> typing.Optional[typing.Any]: + """ + Filter None values from data body for multipart/form requests. + This prevents httpx from converting None to empty strings in multipart encoding. + Only applies when files are present or force_multipart is True. + """ + if data is not None and isinstance(data, typing.Mapping) and (request_files or force_multipart): + return remove_none_from_dict(data) + return data + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], + omit: typing.Optional[typing.Any], +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + has_additional_body_parameters = bool( + request_options is not None and request_options.get("additional_body_parameters") + ) + + # Only collapse empty dict to None when the body was not explicitly provided + # and there are no additional body parameters. This preserves explicit empty + # bodies (e.g., when an endpoint has a request body type but all fields are optional). + if json_body == {} and json is None and not has_additional_body_parameters: + json_body = None + if data_body == {} and data is None and not has_additional_body_parameters: + data_body = None + + return json_body, data_body + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + time.sleep(_retry_timeout_from_retries(retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + async_base_headers: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]]] = None, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.async_base_headers = async_base_headers + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + async def _get_headers(self) -> typing.Dict[str, str]: + if self.async_base_headers is not None: + return await self.async_base_headers() + return self.base_headers() + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = await self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + await asyncio.sleep(_retry_timeout_from_retries(retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + async with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py new file mode 100644 index 000000000000..00bb1096d2d0 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Dict, Generic, TypeVar + +import httpx + +# Generic to represent the underlying type of the data wrapped by the HTTP response. +T = TypeVar("T") + + +class BaseHttpResponse: + """Minimalist HTTP response wrapper that exposes response headers and status code.""" + + _response: httpx.Response + + def __init__(self, response: httpx.Response): + self._response = response + + @property + def headers(self) -> Dict[str, str]: + return dict(self._response.headers) + + @property + def status_code(self) -> int: + return self._response.status_code + + +class HttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + def close(self) -> None: + self._response.close() + + +class AsyncHttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + async def close(self) -> None: + await self._response.aclose() diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py new file mode 100644 index 000000000000..730e5a3382eb --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from ._api import EventSource, aconnect_sse, connect_sse + from ._exceptions import SSEError + from ._models import ServerSentEvent +_dynamic_imports: typing.Dict[str, str] = { + "EventSource": "._api", + "SSEError": "._exceptions", + "ServerSentEvent": "._models", + "aconnect_sse": "._api", + "connect_sse": "._api", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py new file mode 100644 index 000000000000..f900b3b686de --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +import re +from contextlib import asynccontextmanager, contextmanager +from typing import Any, AsyncGenerator, AsyncIterator, Iterator, cast + +import httpx +from ._decoders import SSEDecoder +from ._exceptions import SSEError +from ._models import ServerSentEvent + + +class EventSource: + def __init__(self, response: httpx.Response) -> None: + self._response = response + + def _check_content_type(self) -> None: + content_type = self._response.headers.get("content-type", "").partition(";")[0] + if "text/event-stream" not in content_type: + raise SSEError( + f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}" + ) + + def _get_charset(self) -> str: + """Extract charset from Content-Type header, fallback to UTF-8.""" + content_type = self._response.headers.get("content-type", "") + + # Parse charset parameter using regex + charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE) + if charset_match: + charset = charset_match.group(1).strip("\"'") + # Validate that it's a known encoding + try: + # Test if the charset is valid by trying to encode/decode + "test".encode(charset).decode(charset) + return charset + except (LookupError, UnicodeError): + # If charset is invalid, fall back to UTF-8 + pass + + # Default to UTF-8 if no charset specified or invalid charset + return "utf-8" + + @property + def response(self) -> httpx.Response: + return self._response + + def iter_sse(self) -> Iterator[ServerSentEvent]: + self._check_content_type() + decoder = SSEDecoder() + charset = self._get_charset() + + buffer = "" + for chunk in self._response.iter_bytes(): + # Decode chunk using detected charset + text_chunk = chunk.decode(charset, errors="replace") + buffer += text_chunk + + # Process complete lines + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) + line = line.rstrip("\r") + sse = decoder.decode(line) + # when we reach a "\n\n" => line = '' + # => decoder will attempt to return an SSE Event + if sse is not None: + yield sse + + # Process any remaining data in buffer + if buffer.strip(): + line = buffer.rstrip("\r") + sse = decoder.decode(line) + if sse is not None: + yield sse + + async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: + self._check_content_type() + decoder = SSEDecoder() + lines = cast(AsyncGenerator[str, None], self._response.aiter_lines()) + try: + async for line in lines: + line = line.rstrip("\n") + sse = decoder.decode(line) + if sse is not None: + yield sse + finally: + await lines.aclose() + + +@contextmanager +def connect_sse(client: httpx.Client, method: str, url: str, **kwargs: Any) -> Iterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) + + +@asynccontextmanager +async def aconnect_sse( + client: httpx.AsyncClient, + method: str, + url: str, + **kwargs: Any, +) -> AsyncIterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + async with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py new file mode 100644 index 000000000000..339b08901381 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import List, Optional + +from ._models import ServerSentEvent + + +class SSEDecoder: + def __init__(self) -> None: + self._event = "" + self._data: List[str] = [] + self._last_event_id = "" + self._retry: Optional[int] = None + + def decode(self, line: str) -> Optional[ServerSentEvent]: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if not self._event and not self._data and not self._last_event_id and self._retry is None: + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = "" + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py new file mode 100644 index 000000000000..81605a8a65ed --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import httpx + + +class SSEError(httpx.TransportError): + pass diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py new file mode 100644 index 000000000000..1af57f8fd0d2 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass(frozen=True) +class ServerSentEvent: + event: str = "message" + data: str = "" + id: str = "" + retry: Optional[int] = None + + def json(self) -> Any: + """Parse the data field as JSON.""" + return json.loads(self.data) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py new file mode 100644 index 000000000000..5b0902ebcde3 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py @@ -0,0 +1,120 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union + +import pydantic +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + encode_by_type, + to_jsonable_with_fallback, +) + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT". + # OMIT values should be excluded from serialized payloads. + if obj is Ellipsis: + return None + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + if value is Ellipsis: + continue + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + if item is Ellipsis: + continue + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) + + +def encode_path_param(obj: Any) -> str: + """Encode a value for use in a URL path segment. + + Ensures proper string conversion for all types, including + booleans which need lowercase 'true'/'false' rather than + Python's 'True'/'False'. + """ + if isinstance(obj, bool): + return "true" if obj else "false" + return str(jsonable_encoder(obj)) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py new file mode 100644 index 000000000000..e5e572458bc8 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py @@ -0,0 +1,107 @@ +# This file was auto-generated by Fern from our API Definition. + +import logging +import typing + +LogLevel = typing.Literal["debug", "info", "warn", "error"] + +_LOG_LEVEL_MAP: typing.Dict[LogLevel, int] = { + "debug": 1, + "info": 2, + "warn": 3, + "error": 4, +} + + +class ILogger(typing.Protocol): + def debug(self, message: str, **kwargs: typing.Any) -> None: ... + def info(self, message: str, **kwargs: typing.Any) -> None: ... + def warn(self, message: str, **kwargs: typing.Any) -> None: ... + def error(self, message: str, **kwargs: typing.Any) -> None: ... + + +class ConsoleLogger: + _logger: logging.Logger + + def __init__(self) -> None: + self._logger = logging.getLogger("fern") + if not self._logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s")) + self._logger.addHandler(handler) + self._logger.setLevel(logging.DEBUG) + + def debug(self, message: str, **kwargs: typing.Any) -> None: + self._logger.debug(message, extra=kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + self._logger.info(message, extra=kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + self._logger.warning(message, extra=kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + self._logger.error(message, extra=kwargs) + + +class LogConfig(typing.TypedDict, total=False): + level: LogLevel + logger: ILogger + silent: bool + + +class Logger: + _level: int + _logger: ILogger + _silent: bool + + def __init__(self, *, level: LogLevel, logger: ILogger, silent: bool) -> None: + self._level = _LOG_LEVEL_MAP[level] + self._logger = logger + self._silent = silent + + def _should_log(self, level: LogLevel) -> bool: + return not self._silent and self._level <= _LOG_LEVEL_MAP[level] + + def is_debug(self) -> bool: + return self._should_log("debug") + + def is_info(self) -> bool: + return self._should_log("info") + + def is_warn(self) -> bool: + return self._should_log("warn") + + def is_error(self) -> bool: + return self._should_log("error") + + def debug(self, message: str, **kwargs: typing.Any) -> None: + if self.is_debug(): + self._logger.debug(message, **kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + if self.is_info(): + self._logger.info(message, **kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + if self.is_warn(): + self._logger.warn(message, **kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + if self.is_error(): + self._logger.error(message, **kwargs) + + +_default_logger: Logger = Logger(level="info", logger=ConsoleLogger(), silent=True) + + +def create_logger(config: typing.Optional[typing.Union[LogConfig, Logger]] = None) -> Logger: + if config is None: + return _default_logger + if isinstance(config, Logger): + return config + return Logger( + level=config.get("level", "info"), + logger=config.get("logger", ConsoleLogger()), + silent=config.get("silent", True), + ) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py new file mode 100644 index 000000000000..4527c6a8adec --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ParsingError(Exception): + """ + Raised when the SDK fails to parse/validate a response from the server. + This typically indicates that the server returned a response whose shape + does not match the expected schema. + """ + + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + cause: Optional[Exception] + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + cause: Optional[Exception] = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + self.cause = cause + super().__init__() + if cause is not None: + self.__cause__ = cause + + def __str__(self) -> str: + cause_str = f", cause: {self.cause}" if self.cause is not None else "" + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py new file mode 100644 index 000000000000..fea3a08d3268 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py @@ -0,0 +1,634 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import inspect +import json +import logging +from collections import defaultdict +from dataclasses import asdict +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +import pydantic +import typing_extensions +from pydantic.fields import FieldInfo as _FieldInfo + +_logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from .http_sse._models import ServerSentEvent + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] + _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] + + def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value + return _datetime_adapter.validate_python(value) + + def parse_date(value: Any) -> dt.date: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value.date() + if isinstance(value, dt.date): + return value + return _date_adapter.validate_python(value) + + # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. + from typing import get_args as get_args # type: ignore[assignment] + from typing import get_origin as get_origin # type: ignore[assignment] + + def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return typing_extensions.get_origin(tp) is typing_extensions.Literal + + def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] + + # Inline encoders_by_type to avoid importing from pydantic.v1.json + import re as _re + from collections import deque as _deque + from decimal import Decimal as _Decimal + from enum import Enum as _Enum + from ipaddress import ( + IPv4Address as _IPv4Address, + ) + from ipaddress import ( + IPv4Interface as _IPv4Interface, + ) + from ipaddress import ( + IPv4Network as _IPv4Network, + ) + from ipaddress import ( + IPv6Address as _IPv6Address, + ) + from ipaddress import ( + IPv6Interface as _IPv6Interface, + ) + from ipaddress import ( + IPv6Network as _IPv6Network, + ) + from pathlib import Path as _Path + from types import GeneratorType as _GeneratorType + from uuid import UUID as _UUID + + from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] + + def _decimal_encoder(dec_value: Any) -> Any: + if dec_value.as_tuple().exponent >= 0: + return int(dec_value) + return float(dec_value) + + encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] + bytes: lambda o: o.decode(), + dt.date: lambda o: o.isoformat(), + dt.datetime: lambda o: o.isoformat(), + dt.time: lambda o: o.isoformat(), + dt.timedelta: lambda td: td.total_seconds(), + _Decimal: _decimal_encoder, + _Enum: lambda o: o.value, + frozenset: list, + _deque: list, + _GeneratorType: list, + _IPv4Address: str, + _IPv4Interface: str, + _IPv4Network: str, + _IPv6Address: str, + _IPv6Interface: str, + _IPv6Network: str, + _Path: str, + _re.Pattern: lambda o: o.pattern, + set: list, + _UUID: str, + } +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] + from pydantic.typing import get_args as get_args # type: ignore[no-redef] + from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] + from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] + from pydantic.typing import is_union as is_union # type: ignore[no-redef] + +from .datetime_utils import serialize_datetime +from .serialization import convert_and_respect_annotation_metadata +from typing_extensions import TypeAlias + +T = TypeVar("T") +Model = TypeVar("Model", bound=pydantic.BaseModel) + + +def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: + """ + Extract the discriminator field name and union variants from a discriminated union type. + Supports Annotated[Union[...], Field(discriminator=...)] patterns. + Returns (discriminator, variants) or (None, None) if not a discriminated union. + """ + origin = typing_extensions.get_origin(type_) + + if origin is typing_extensions.Annotated: + args = typing_extensions.get_args(type_) + if len(args) >= 2: + inner_type = args[0] + # Check annotations for discriminator + discriminator = None + for annotation in args[1:]: + if hasattr(annotation, "discriminator"): + discriminator = getattr(annotation, "discriminator", None) + break + + if discriminator: + inner_origin = typing_extensions.get_origin(inner_type) + if inner_origin is Union: + variants = list(typing_extensions.get_args(inner_type)) + return discriminator, variants + return None, None + + +def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: + """Get the type annotation of a field from a Pydantic model.""" + if IS_PYDANTIC_V2: + fields = getattr(model, "model_fields", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.annotation) + else: + fields = getattr(model, "__fields__", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.outer_type_) + return None + + +def _find_variant_by_discriminator( + variants: List[Type[Any]], + discriminator: str, + discriminator_value: Any, +) -> Optional[Type[Any]]: + """Find the union variant that matches the discriminator value.""" + for variant in variants: + if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): + continue + + disc_annotation = _get_field_annotation(variant, discriminator) + if disc_annotation and is_literal_type(disc_annotation): + literal_args = get_args(disc_annotation) + if literal_args and literal_args[0] == discriminator_value: + return variant + return None + + +def _is_string_type(type_: Type[Any]) -> bool: + """Check if a type is str or Optional[str].""" + if type_ is str: + return True + + origin = typing_extensions.get_origin(type_) + if origin is Union: + args = typing_extensions.get_args(type_) + # Optional[str] = Union[str, None] + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1 and non_none_args[0] is str: + return True + + return False + + +def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: + """ + Parse a ServerSentEvent into the appropriate type. + + Handles two scenarios based on where the discriminator field is located: + + 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. + The union describes the data content, not the SSE envelope. + -> Returns: json.loads(data) parsed into the type + + Example: ChatStreamResponse with discriminator='type' + Input: ServerSentEvent(event="message", data='{"type": "content-delta", ...}', id="") + Output: ContentDeltaEvent (parsed from data, SSE envelope stripped) + + 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. + The union describes the full SSE event structure. + -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string + + Example: JobStreamResponse with discriminator='event' + Input: ServerSentEvent(event="ERROR", data='{"code": "FAILED", ...}', id="123") + Output: JobStreamResponse_Error with data as ErrorData object + + But for variants where data is str (like STATUS_UPDATE): + Input: ServerSentEvent(event="STATUS_UPDATE", data='{"status": "processing"}', id="1") + Output: JobStreamResponse_StatusUpdate with data as string (not parsed) + + Args: + sse: The ServerSentEvent object to parse + type_: The target discriminated union type + + Returns: + The parsed object of type T + + Note: + This function is only available in SDK contexts where http_sse module exists. + """ + sse_event = asdict(sse) + discriminator, variants = _get_discriminator_and_variants(type_) + + if discriminator is None or variants is None: + # Not a discriminated union - parse the data field as JSON + data_value = sse_event.get("data") + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + data_value = sse_event.get("data") + + # Check if discriminator is at the top level (event-level discrimination) + if discriminator in sse_event: + # Case 2: Event-level discrimination + # Find the matching variant to check if 'data' field needs JSON parsing + disc_value = sse_event.get(discriminator) + matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) + + if matching_variant is not None: + # Check what type the variant expects for 'data' + data_type = _get_field_annotation(matching_variant, "data") + if data_type is not None and not _is_string_type(data_type): + # Variant expects non-string data - parse JSON + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + new_object = dict(sse_event) + new_object["data"] = parsed_data + return parse_obj_as(type_, new_object) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + # Either no matching variant, data is string type, or JSON parse failed + return parse_obj_as(type_, sse_event) + + else: + # Case 1: Data-level discrimination + # The discriminator is inside the data payload - extract and parse data only + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + +def parse_obj_as(type_: Type[T], object_: Any) -> T: + # convert_and_respect_annotation_metadata is required for TypedDict aliasing. + # + # For Pydantic models, whether we should pre-dealias depends on how the model encodes aliasing: + # - If the model uses real Pydantic aliases (pydantic.Field(alias=...)), then we must pass wire keys through + # unchanged so Pydantic can validate them. + # - If the model encodes aliasing only via FieldMetadata annotations, then we MUST pre-dealias because Pydantic + # will not recognize those aliases during validation. + if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): + has_pydantic_aliases = False + if IS_PYDANTIC_V2: + for field_name, field_info in getattr(type_, "model_fields", {}).items(): # type: ignore[attr-defined] + alias = getattr(field_info, "alias", None) + if alias is not None and alias != field_name: + has_pydantic_aliases = True + break + else: + for field in getattr(type_, "__fields__", {}).values(): + alias = getattr(field, "alias", None) + name = getattr(field, "name", None) + if alias is not None and name is not None and alias != name: + has_pydantic_aliases = True + break + + dealiased_object = ( + object_ + if has_pydantic_aliases + else convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + ) + else: + dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] + return adapter.validate_python(dealiased_object) + return pydantic.parse_obj_as(type_, dealiased_object) + + +def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] + # Allow fields beginning with `model_` to be used in the model + protected_namespaces=(), + ) + + @pydantic.model_validator(mode="before") # type: ignore[attr-defined] + @classmethod + def _coerce_field_names_to_aliases(cls, data: Any) -> Any: + """ + Accept Python field names in input by rewriting them to their Pydantic aliases, + while avoiding silent collisions when a key could refer to multiple fields. + """ + if not isinstance(data, Mapping): + return data + + fields = getattr(cls, "model_fields", {}) # type: ignore[attr-defined] + name_to_alias: Dict[str, str] = {} + alias_to_name: Dict[str, str] = {} + + for name, field_info in fields.items(): + alias = getattr(field_info, "alias", None) or name + name_to_alias[name] = alias + if alias != name: + alias_to_name[alias] = name + + # Detect ambiguous keys: a key that is an alias for one field and a name for another. + ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) + for key in ambiguous_keys: + if key in data and name_to_alias[key] not in data: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + original_keys = set(data.keys()) + rewritten: Dict[str, Any] = dict(data) + for name, alias in name_to_alias.items(): + if alias != name and name in original_keys and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] + def serialize_model(self) -> Any: # type: ignore[name-defined] + serialized = self.dict() # type: ignore[attr-defined] + data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} + return data + + else: + + class Config: + smart_union = True + json_encoders = {dt.datetime: serialize_datetime} + + @pydantic.root_validator(pre=True) + def _coerce_field_names_to_aliases(cls, values: Any) -> Any: + """ + Pydantic v1 equivalent of _coerce_field_names_to_aliases. + """ + if not isinstance(values, Mapping): + return values + + fields = getattr(cls, "__fields__", {}) + name_to_alias: Dict[str, str] = {} + alias_to_name: Dict[str, str] = {} + + for name, field in fields.items(): + alias = getattr(field, "alias", None) or name + name_to_alias[name] = alias + if alias != name: + alias_to_name[alias] = name + + ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) + for key in ambiguous_keys: + if key in values and name_to_alias[key] not in values: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + original_keys = set(values.keys()) + rewritten: Dict[str, Any] = dict(values) + for name, alias in name_to_alias.items(): + if alias != name and name in original_keys and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + @classmethod + def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + return cls.construct(_fields_set, **dealiased_object) + + @classmethod + def construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + if IS_PYDANTIC_V2: + return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc] + return super().construct(_fields_set, **dealiased_object) + + def json(self, **kwargs: Any) -> str: + kwargs_with_defaults = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: Any) -> Dict[str, Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + dict_dump = deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + return cast( + Dict[str, Any], + convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write"), + ) + + +def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: + converted_list: List[Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg] + pass + + UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc] +else: + UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] + + +def encode_by_type(o: Any) -> Any: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: Type["Model"], **localns: Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = Callable[..., Any] + + +def universal_root_validator( + pre: bool = False, +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) + + return decorator + + +PydanticField = Union[ModelField, _FieldInfo] + + +def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] + return cast(Mapping[str, PydanticField], model.__fields__) + + +def _get_field_default(field: PydanticField) -> Any: + try: + value = field.get_default() # type: ignore[union-attr] + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py new file mode 100644 index 000000000000..3183001d4046 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, List, Optional, Tuple + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: + result = [] + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.extend(traverse_query_dict(v, key)) + elif isinstance(v, list): + for arr_v in v: + if isinstance(arr_v, dict): + result.extend(traverse_query_dict(arr_v, key)) + else: + result.append((key, arr_v)) + else: + result.append((key, v)) + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + elif isinstance(query_value, list): + encoded_values: List[Tuple[str, Any]] = [] + for value in query_value: + if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): + if isinstance(value, pydantic.BaseModel): + obj_dict = value.dict(by_alias=True) + elif isinstance(value, dict): + obj_dict = value + + encoded_values.extend(single_query_encoder(query_key, obj_dict)) + else: + encoded_values.append((query_key, value)) + + return encoded_values + + return [(query_key, query_value)] + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: + if query is None: + return None + + encoded_query = [] + for k, v in query.items(): + encoded_query.extend(single_query_encoder(k, v)) + return encoded_query diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py new file mode 100644 index 000000000000..c2298143f14a --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py new file mode 100644 index 000000000000..1b38804432ba --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout_in_seconds: int. The number of seconds to await an API call before timing out. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + + - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. + """ + + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] + chunk_size: NotRequired[int] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py new file mode 100644 index 000000000000..c36e865cc729 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py @@ -0,0 +1,276 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import pydantic +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + try: + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + except NameError: + # The TypedDict contains a circular reference, so + # we use the __annotations__ attribute directly. + annotations = getattr(expected_type, "__annotations__", {}) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py new file mode 100644 index 000000000000..c64358bdc3c7 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum + + +class SeedApiEnvironment(enum.Enum): + DEFAULT = "https://api.example.com" diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/py.typed b/seed/python-sdk/allof-inline/no-custom-config/src/seed/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py new file mode 100644 index 000000000000..2e2501a1fd92 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py @@ -0,0 +1,451 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from .core.api_error import ApiError +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .core.http_response import AsyncHttpResponse, HttpResponse +from .core.parse_error import ParsingError +from .core.pydantic_utilities import parse_obj_as +from .core.request_options import RequestOptions +from .types.combined_entity import CombinedEntity +from .types.organization import Organization +from .types.rule_execution_context import RuleExecutionContext +from .types.rule_response import RuleResponse +from .types.rule_type_search_response import RuleTypeSearchResponse +from .types.user_search_response import UserSearchResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSeedApi: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[RuleTypeSearchResponse]: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[RuleTypeSearchResponse] + Paginated list of rule types + """ + _response = self._client_wrapper.httpx_client.request( + "rule-types", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleTypeSearchResponse, + parse_obj_as( + type_=RuleTypeSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[RuleResponse]: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[RuleResponse] + Created rule + """ + _response = self._client_wrapper.httpx_client.request( + "rules", + method="POST", + json={ + "name": name, + "executionContext": execution_context, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleResponse, + parse_obj_as( + type_=RuleResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def list_users( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[UserSearchResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[UserSearchResponse] + Paginated list of users + """ + _response = self._client_wrapper.httpx_client.request( + "users", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UserSearchResponse, + parse_obj_as( + type_=UserSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[CombinedEntity]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CombinedEntity] + An entity with properties from multiple parents + """ + _response = self._client_wrapper.httpx_client.request( + "entities", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CombinedEntity, + parse_obj_as( + type_=CombinedEntity, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get_organization( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[Organization]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Organization] + An organization whose metadata is merged from two parents + """ + _response = self._client_wrapper.httpx_client.request( + "organizations", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Organization, + parse_obj_as( + type_=Organization, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSeedApi: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[RuleTypeSearchResponse]: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[RuleTypeSearchResponse] + Paginated list of rule types + """ + _response = await self._client_wrapper.httpx_client.request( + "rule-types", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleTypeSearchResponse, + parse_obj_as( + type_=RuleTypeSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[RuleResponse]: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[RuleResponse] + Created rule + """ + _response = await self._client_wrapper.httpx_client.request( + "rules", + method="POST", + json={ + "name": name, + "executionContext": execution_context, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleResponse, + parse_obj_as( + type_=RuleResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list_users( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[UserSearchResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[UserSearchResponse] + Paginated list of users + """ + _response = await self._client_wrapper.httpx_client.request( + "users", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UserSearchResponse, + parse_obj_as( + type_=UserSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get_entity( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CombinedEntity]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CombinedEntity] + An entity with properties from multiple parents + """ + _response = await self._client_wrapper.httpx_client.request( + "entities", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CombinedEntity, + parse_obj_as( + type_=CombinedEntity, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get_organization( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[Organization]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Organization] + An organization whose metadata is merged from two parents + """ + _response = await self._client_wrapper.httpx_client.request( + "organizations", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Organization, + parse_obj_as( + type_=Organization, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py new file mode 100644 index 000000000000..f0fa2b2add13 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py @@ -0,0 +1,95 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .audit_info import AuditInfo + from .base_org import BaseOrg + from .base_org_metadata import BaseOrgMetadata + from .combined_entity import CombinedEntity + from .combined_entity_status import CombinedEntityStatus + from .describable import Describable + from .detailed_org import DetailedOrg + from .detailed_org_metadata import DetailedOrgMetadata + from .identifiable import Identifiable + from .organization import Organization + from .organization_metadata import OrganizationMetadata + from .paginated_result import PaginatedResult + from .paging_cursors import PagingCursors + from .rule_execution_context import RuleExecutionContext + from .rule_response import RuleResponse + from .rule_response_status import RuleResponseStatus + from .rule_type import RuleType + from .rule_type_search_response import RuleTypeSearchResponse + from .user import User + from .user_search_response import UserSearchResponse +_dynamic_imports: typing.Dict[str, str] = { + "AuditInfo": ".audit_info", + "BaseOrg": ".base_org", + "BaseOrgMetadata": ".base_org_metadata", + "CombinedEntity": ".combined_entity", + "CombinedEntityStatus": ".combined_entity_status", + "Describable": ".describable", + "DetailedOrg": ".detailed_org", + "DetailedOrgMetadata": ".detailed_org_metadata", + "Identifiable": ".identifiable", + "Organization": ".organization", + "OrganizationMetadata": ".organization_metadata", + "PaginatedResult": ".paginated_result", + "PagingCursors": ".paging_cursors", + "RuleExecutionContext": ".rule_execution_context", + "RuleResponse": ".rule_response", + "RuleResponseStatus": ".rule_response_status", + "RuleType": ".rule_type", + "RuleTypeSearchResponse": ".rule_type_search_response", + "User": ".user", + "UserSearchResponse": ".user_search_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AuditInfo", + "BaseOrg", + "BaseOrgMetadata", + "CombinedEntity", + "CombinedEntityStatus", + "Describable", + "DetailedOrg", + "DetailedOrgMetadata", + "Identifiable", + "Organization", + "OrganizationMetadata", + "PaginatedResult", + "PagingCursors", + "RuleExecutionContext", + "RuleResponse", + "RuleResponseStatus", + "RuleType", + "RuleTypeSearchResponse", + "User", + "UserSearchResponse", +] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py new file mode 100644 index 000000000000..49474bbcdeeb --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class AuditInfo(UniversalBaseModel): + """ + Common audit metadata. + """ + + created_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="createdBy"), + pydantic.Field(alias="createdBy", description="The user who created this resource."), + ] = None + created_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="createdDateTime"), + pydantic.Field(alias="createdDateTime", description="When this resource was created."), + ] = None + modified_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="modifiedBy"), + pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), + ] = None + modified_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="modifiedDateTime"), + pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py new file mode 100644 index 000000000000..9d77974841d4 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .base_org_metadata import BaseOrgMetadata + + +class BaseOrg(UniversalBaseModel): + id: str + metadata: typing.Optional[BaseOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py new file mode 100644 index 000000000000..69449ce2a499 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class BaseOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from BaseOrg. + """ + + tier: typing.Optional[str] = pydantic.Field(default=None) + """ + Subscription tier. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py new file mode 100644 index 000000000000..0626826b2f98 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .combined_entity_status import CombinedEntityStatus + + +class CombinedEntity(UniversalBaseModel): + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Describable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + status: CombinedEntityStatus + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py new file mode 100644 index 000000000000..42ac60430cd7 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py new file mode 100644 index 000000000000..2b6cafc913ee --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Describable(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Describable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py new file mode 100644 index 000000000000..744634c71d46 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .detailed_org_metadata import DetailedOrgMetadata + + +class DetailedOrg(UniversalBaseModel): + metadata: typing.Optional[DetailedOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py new file mode 100644 index 000000000000..f0f2029eb21c --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DetailedOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from DetailedOrg. + """ + + domain: typing.Optional[str] = pydantic.Field(default=None) + """ + Custom domain name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py new file mode 100644 index 000000000000..b88d9d997229 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Identifiable(UniversalBaseModel): + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Identifiable. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py new file mode 100644 index 000000000000..6e412e61f110 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .organization_metadata import OrganizationMetadata + + +class Organization(UniversalBaseModel): + id: str + metadata: typing.Optional[OrganizationMetadata] = None + name: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py new file mode 100644 index 000000000000..7d61623ad2e1 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class OrganizationMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from DetailedOrg. + """ + + domain: typing.Optional[str] = pydantic.Field(default=None) + """ + Custom domain name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py new file mode 100644 index 000000000000..56a3858ffbde --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors + + +class PaginatedResult(UniversalBaseModel): + paging: PagingCursors + results: typing.List[typing.Any] = pydantic.Field() + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py new file mode 100644 index 000000000000..f786d5462fea --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PagingCursors(UniversalBaseModel): + next: str = pydantic.Field() + """ + Cursor for the next page of results. + """ + + previous: typing.Optional[str] = pydantic.Field(default=None) + """ + Cursor for the previous page of results. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py new file mode 100644 index 000000000000..1d22ed9cabf8 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py new file mode 100644 index 000000000000..50ff8b6dea99 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .rule_execution_context import RuleExecutionContext +from .rule_response_status import RuleResponseStatus + + +class RuleResponse(UniversalBaseModel): + created_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="createdBy"), + pydantic.Field(alias="createdBy", description="The user who created this resource."), + ] = None + created_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="createdDateTime"), + pydantic.Field(alias="createdDateTime", description="When this resource was created."), + ] = None + modified_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="modifiedBy"), + pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), + ] = None + modified_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="modifiedDateTime"), + pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), + ] = None + id: str + name: str + status: RuleResponseStatus + execution_context: typing_extensions.Annotated[ + typing.Optional[RuleExecutionContext], + FieldMetadata(alias="executionContext"), + pydantic.Field(alias="executionContext"), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py new file mode 100644 index 000000000000..4cbd106638cb --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py new file mode 100644 index 000000000000..a5543e06211c --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RuleType(UniversalBaseModel): + id: str + name: str + description: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py new file mode 100644 index 000000000000..1e60dbe9ab78 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .rule_type import RuleType + + +class RuleTypeSearchResponse(UniversalBaseModel): + paging: PagingCursors + results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py new file mode 100644 index 000000000000..5421e32870d3 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class User(UniversalBaseModel): + id: str + email: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py new file mode 100644 index 000000000000..3fbf4eb995d6 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .user import User + + +class UserSearchResponse(UniversalBaseModel): + paging: PagingCursors + results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py new file mode 100644 index 000000000000..00f3a2eb508a --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py @@ -0,0 +1,3 @@ +from importlib import metadata + +__version__ = metadata.version("fern_allof-inline") diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py b/seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py new file mode 100644 index 000000000000..9e586af3b2d4 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py @@ -0,0 +1,147 @@ +""" +Pytest plugin that manages the WireMock container lifecycle for wire tests. + +This plugin is loaded globally for the test suite and is responsible for +starting and stopping the WireMock container exactly once per test run, +including when running with pytest-xdist over the entire project. + +It lives under tests/ (as tests/conftest.py) and is discovered automatically +by pytest's normal test collection rules. +""" + +import os +import subprocess + +import pytest + +_STARTED: bool = False +_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) +_WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts +_PROJECT_NAME: str = "seed-api" + +# This file lives at tests/conftest.py, so the project root is one level up. +_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +_COMPOSE_FILE = os.path.join(_PROJECT_ROOT, "wiremock", "docker-compose.test.yml") + + +def _get_wiremock_port() -> str: + """Gets the dynamically assigned port for the WireMock container.""" + try: + result = subprocess.run( + ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "port", "wiremock", "8080"], + check=True, + capture_output=True, + text=True, + ) + # Output is like "0.0.0.0:32768" or "[::]:32768" + port = result.stdout.strip().split(":")[-1] + return port + except subprocess.CalledProcessError: + return "8080" # Fallback to default + + +def _start_wiremock() -> None: + """Starts the WireMock container using docker-compose.""" + global _STARTED, _EXTERNAL, _WIREMOCK_URL + if _STARTED: + return + + # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management + existing_url = os.environ.get("WIREMOCK_URL") + if existing_url: + _WIREMOCK_URL = existing_url + _EXTERNAL = True + _STARTED = True + print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") + return + + print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") + try: + subprocess.run( + ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "up", "-d", "--wait"], + check=True, + capture_output=True, + text=True, + ) + _WIREMOCK_PORT = _get_wiremock_port() + _WIREMOCK_URL = f"http://localhost:{_WIREMOCK_PORT}" + os.environ["WIREMOCK_URL"] = _WIREMOCK_URL + print(f"WireMock container is ready at {_WIREMOCK_URL}") + _STARTED = True + except subprocess.CalledProcessError as e: + print(f"Failed to start WireMock: {e.stderr}") + raise + + +def _stop_wiremock() -> None: + """Stops and removes the WireMock container.""" + if _EXTERNAL: + # Container is managed externally; nothing to tear down. + return + + print("\nStopping WireMock container...") + subprocess.run( + ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], + check=False, + capture_output=True, + ) + + +def _is_xdist_worker(config: pytest.Config) -> bool: + """ + Determines if the current process is an xdist worker. + + In pytest-xdist, worker processes have a 'workerinput' attribute + on the config object, while the controller process does not. + """ + return hasattr(config, "workerinput") + + +def _has_httpx_aiohttp() -> bool: + """Check if httpx_aiohttp is importable.""" + try: + import httpx_aiohttp # type: ignore[import-not-found] # noqa: F401 + + return True + except ImportError: + return False + + +def pytest_collection_modifyitems(config: pytest.Config, items: list) -> None: + """Auto-skip @pytest.mark.aiohttp tests when httpx_aiohttp is not installed.""" + if _has_httpx_aiohttp(): + return + skip_aiohttp = pytest.mark.skip(reason="httpx_aiohttp not installed") + for item in items: + if "aiohttp" in item.keywords: + item.add_marker(skip_aiohttp) + + +def pytest_configure(config: pytest.Config) -> None: + """ + Pytest hook that runs during test session setup. + + Starts WireMock container only from the controller process (xdist) + or the single process (non-xdist). This ensures only one container + is started regardless of the number of worker processes. + """ + if _is_xdist_worker(config): + # Workers never manage the container lifecycle. + return + + _start_wiremock() + + +def pytest_unconfigure(config: pytest.Config) -> None: + """ + Pytest hook that runs during test session teardown. + + Stops WireMock container only from the controller process (xdist) + or the single process (non-xdist). This ensures the container is + cleaned up after all workers have finished. + """ + if _is_xdist_worker(config): + # Workers never manage the container lifecycle. + return + + _stop_wiremock() diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py b/seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py new file mode 100644 index 000000000000..ab04ce6393ef --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py @@ -0,0 +1,7 @@ +import pytest + + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py b/seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py new file mode 100644 index 000000000000..2ed3ed776ecf --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py @@ -0,0 +1,116 @@ +import importlib +import sys +import unittest +from unittest import mock + +import httpx +import pytest + + +class TestMakeDefaultAsyncClientWithoutAiohttp(unittest.TestCase): + """Tests for _make_default_async_client when httpx_aiohttp is NOT installed.""" + + def test_returns_httpx_async_client(self) -> None: + """When httpx_aiohttp is not installed, returns plain httpx.AsyncClient.""" + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=True) + self.assertIsInstance(client, httpx.AsyncClient) + self.assertEqual(client.timeout.read, 60) + self.assertTrue(client.follow_redirects) + + def test_follow_redirects_none(self) -> None: + """When follow_redirects is None, omits it from httpx.AsyncClient.""" + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=None) + self.assertIsInstance(client, httpx.AsyncClient) + self.assertFalse(client.follow_redirects) + + def test_explicit_httpx_client_bypasses_autodetect(self) -> None: + """When user passes httpx_client explicitly, _make_default_async_client is not called.""" + + explicit_client = httpx.AsyncClient(timeout=120) + with mock.patch("seed.client._make_default_async_client") as mock_make: + # Replicate the generated conditional: httpx_client if httpx_client is not None else _make_default_async_client(...) + result = explicit_client if explicit_client is not None else mock_make(timeout=60, follow_redirects=True) + mock_make.assert_not_called() + self.assertIs(result, explicit_client) + + +@pytest.mark.aiohttp +class TestMakeDefaultAsyncClientWithAiohttp(unittest.TestCase): + """Tests for _make_default_async_client when httpx_aiohttp IS installed.""" + + def test_returns_aiohttp_client(self) -> None: + """When httpx_aiohttp is installed, returns HttpxAiohttpClient.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=True) + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertEqual(client.timeout.read, 60) + self.assertTrue(client.follow_redirects) + + def test_follow_redirects_none(self) -> None: + """When httpx_aiohttp is installed and follow_redirects is None, omits it.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=None) + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertFalse(client.follow_redirects) + + +class TestDefaultClientsWithoutAiohttp(unittest.TestCase): + """Tests for _default_clients.py convenience classes (no aiohttp).""" + + def test_default_async_httpx_client_defaults(self) -> None: + """DefaultAsyncHttpxClient applies SDK defaults.""" + from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAsyncHttpxClient + + client = DefaultAsyncHttpxClient() + self.assertIsInstance(client, httpx.AsyncClient) + self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) + self.assertTrue(client.follow_redirects) + + def test_default_async_httpx_client_overrides(self) -> None: + """DefaultAsyncHttpxClient allows overriding defaults.""" + from seed._default_clients import DefaultAsyncHttpxClient + + client = DefaultAsyncHttpxClient(timeout=30, follow_redirects=False) + self.assertEqual(client.timeout.read, 30) + self.assertFalse(client.follow_redirects) + + def test_default_aiohttp_client_raises_without_package(self) -> None: + """DefaultAioHttpClient raises RuntimeError when httpx_aiohttp not installed.""" + import seed._default_clients + + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + importlib.reload(seed._default_clients) + + with self.assertRaises(RuntimeError) as ctx: + seed._default_clients.DefaultAioHttpClient() + self.assertIn("pip install fern_allof-inline[aiohttp]", str(ctx.exception)) + + importlib.reload(seed._default_clients) + + +@pytest.mark.aiohttp +class TestDefaultClientsWithAiohttp(unittest.TestCase): + """Tests for _default_clients.py when httpx_aiohttp IS installed.""" + + def test_default_aiohttp_client_defaults(self) -> None: + """DefaultAioHttpClient works when httpx_aiohttp is installed.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAioHttpClient + + client = DefaultAioHttpClient() + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) + self.assertTrue(client.follow_redirects) diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py new file mode 100644 index 000000000000..f3ea2659bb1c --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py new file mode 100644 index 000000000000..2cf01263529d --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from .circle import CircleParams +from .object_with_defaults import ObjectWithDefaultsParams +from .object_with_optional_field import ObjectWithOptionalFieldParams +from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams +from .square import SquareParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +__all__ = [ + "CircleParams", + "ObjectWithDefaultsParams", + "ObjectWithOptionalFieldParams", + "ShapeParams", + "Shape_CircleParams", + "Shape_SquareParams", + "SquareParams", + "UndiscriminatedShapeParams", +] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py new file mode 100644 index 000000000000..74ecf38c308b --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + +from seed.core.serialization import FieldMetadata + + +class CircleParams(typing_extensions.TypedDict): + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py new file mode 100644 index 000000000000..2aa2c4c52f0c --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py new file mode 100644 index 000000000000..a977b1d2aa1c --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + + +class ObjectWithDefaultsParams(typing_extensions.TypedDict): + """ + Defines properties with default values and validation rules. + """ + + decimal: typing_extensions.NotRequired[float] + string: typing_extensions.NotRequired[str] + required_string: str diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py new file mode 100644 index 000000000000..6b5608bc05b6 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +import uuid + +import typing_extensions +from .color import Color +from .shape import ShapeParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +from seed.core.serialization import FieldMetadata + + +class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): + literal: typing.Literal["lit_one"] + string: typing_extensions.NotRequired[str] + integer: typing_extensions.NotRequired[int] + long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] + double: typing_extensions.NotRequired[float] + bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] + datetime: typing_extensions.NotRequired[dt.datetime] + date: typing_extensions.NotRequired[dt.date] + uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] + base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] + list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] + set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] + map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] + enum: typing_extensions.NotRequired[Color] + union: typing_extensions.NotRequired[ShapeParams] + second_union: typing_extensions.NotRequired[ShapeParams] + undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] + any: typing.Optional[typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py new file mode 100644 index 000000000000..7e70010a251f --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import typing_extensions + +from seed.core.serialization import FieldMetadata + + +class Base(typing_extensions.TypedDict): + id: str + + +class Shape_CircleParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] + + +class Shape_SquareParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] + + +ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py new file mode 100644 index 000000000000..71c7d25fd4ad --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + +from seed.core.serialization import FieldMetadata + + +class SquareParams(typing_extensions.TypedDict): + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py new file mode 100644 index 000000000000..99f12b300d1d --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .circle import CircleParams +from .square import SquareParams + +UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py new file mode 100644 index 000000000000..aa2a8b4e4700 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py @@ -0,0 +1,662 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +from seed.core.http_client import ( + AsyncHttpClient, + HttpClient, + _build_url, + get_request_body, + remove_none_from_dict, +) +from seed.core.request_options import RequestOptions + + +# Stub clients for testing HttpClient and AsyncHttpClient +class _DummySyncClient: + """A minimal stub for httpx.Client that records request arguments.""" + + def __init__(self) -> None: + self.last_request_kwargs: Dict[str, Any] = {} + + def request(self, **kwargs: Any) -> "_DummyResponse": + self.last_request_kwargs = kwargs + return _DummyResponse() + + +class _DummyAsyncClient: + """A minimal stub for httpx.AsyncClient that records request arguments.""" + + def __init__(self) -> None: + self.last_request_kwargs: Dict[str, Any] = {} + + async def request(self, **kwargs: Any) -> "_DummyResponse": + self.last_request_kwargs = kwargs + return _DummyResponse() + + +class _DummyResponse: + """A minimal stub for httpx.Response.""" + + status_code = 200 + headers: Dict[str, str] = {} + + +def get_request_options() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later"}} + + +def get_request_options_with_none() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later", "optional": None}} + + +def test_get_json_request_body() -> None: + json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) + assert json_body == {"hello": "world"} + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"goodbye": "world", "see you": "later"} + assert data_body_extras is None + + +def test_get_files_request_body() -> None: + json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) + assert data_body == {"hello": "world"} + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None + ) + + assert data_body_extras == {"goodbye": "world", "see you": "later"} + assert json_body_extras is None + + +def test_get_none_request_body() -> None: + json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) + assert data_body is None + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"see you": "later"} + assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + """Test that implicit empty bodies (json=None) are collapsed to None.""" + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + +def test_explicit_empty_json_body_is_preserved() -> None: + """Test that explicit empty bodies (json={}) are preserved and sent as {}. + + This is important for endpoints where the request body is required but all + fields are optional. The server expects valid JSON ({}) not an empty body. + """ + unrelated_request_options: RequestOptions = {"max_retries": 3} + + # Explicit json={} should be preserved + json_body, data_body = get_request_body(json={}, data=None, request_options=unrelated_request_options, omit=None) + assert json_body == {} + assert data_body is None + + # Explicit data={} should also be preserved + json_body2, data_body2 = get_request_body(json=None, data={}, request_options=unrelated_request_options, omit=None) + assert json_body2 is None + assert data_body2 == {} + + +def test_json_body_preserves_none_values() -> None: + """Test that JSON bodies preserve None values (they become JSON null).""" + json_body, data_body = get_request_body( + json={"hello": "world", "optional": None}, data=None, request_options=None, omit=None + ) + # JSON bodies should preserve None values + assert json_body == {"hello": "world", "optional": None} + assert data_body is None + + +def test_data_body_preserves_none_values_without_multipart() -> None: + """Test that data bodies preserve None values when not using multipart. + + The filtering of None values happens in HttpClient.request/stream methods, + not in get_request_body. This test verifies get_request_body doesn't filter None. + """ + json_body, data_body = get_request_body( + json=None, data={"hello": "world", "optional": None}, request_options=None, omit=None + ) + # get_request_body should preserve None values in data body + # The filtering happens later in HttpClient.request when multipart is detected + assert data_body == {"hello": "world", "optional": None} + assert json_body is None + + +def test_remove_none_from_dict_filters_none_values() -> None: + """Test that remove_none_from_dict correctly filters out None values.""" + original = {"hello": "world", "optional": None, "another": "value", "also_none": None} + filtered = remove_none_from_dict(original) + assert filtered == {"hello": "world", "another": "value"} + # Original should not be modified + assert original == {"hello": "world", "optional": None, "another": "value", "also_none": None} + + +def test_remove_none_from_dict_empty_dict() -> None: + """Test that remove_none_from_dict handles empty dict.""" + assert remove_none_from_dict({}) == {} + + +def test_remove_none_from_dict_all_none() -> None: + """Test that remove_none_from_dict handles dict with all None values.""" + assert remove_none_from_dict({"a": None, "b": None}) == {} + + +def test_http_client_does_not_pass_empty_params_list() -> None: + """Test that HttpClient passes params=None when params are empty. + + This prevents httpx from stripping existing query parameters from the URL, + which happens when params=[] or params={} is passed. + """ + dummy_client = _DummySyncClient() + http_client = HttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + ) + + # Use a path with query params (e.g., pagination cursor URL) + http_client.request( + path="resource?after=123", + method="GET", + params=None, + request_options=None, + ) + + # We care that httpx receives params=None, not [] or {} + assert "params" in dummy_client.last_request_kwargs + assert dummy_client.last_request_kwargs["params"] is None + + # Verify the query string in the URL is preserved + url = str(dummy_client.last_request_kwargs["url"]) + assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" + + +def test_http_client_passes_encoded_params_when_present() -> None: + """Test that HttpClient passes encoded params when params are provided.""" + dummy_client = _DummySyncClient() + http_client = HttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com/resource", + ) + + http_client.request( + path="", + method="GET", + params={"after": "456"}, + request_options=None, + ) + + params = dummy_client.last_request_kwargs["params"] + # For a simple dict, encode_query should give a single (key, value) tuple + assert params == [("after", "456")] + + +@pytest.mark.asyncio +async def test_async_http_client_does_not_pass_empty_params_list() -> None: + """Test that AsyncHttpClient passes params=None when params are empty. + + This prevents httpx from stripping existing query parameters from the URL, + which happens when params=[] or params={} is passed. + """ + dummy_client = _DummyAsyncClient() + http_client = AsyncHttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + async_base_headers=None, + ) + + # Use a path with query params (e.g., pagination cursor URL) + await http_client.request( + path="resource?after=123", + method="GET", + params=None, + request_options=None, + ) + + # We care that httpx receives params=None, not [] or {} + assert "params" in dummy_client.last_request_kwargs + assert dummy_client.last_request_kwargs["params"] is None + + # Verify the query string in the URL is preserved + url = str(dummy_client.last_request_kwargs["url"]) + assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" + + +@pytest.mark.asyncio +async def test_async_http_client_passes_encoded_params_when_present() -> None: + """Test that AsyncHttpClient passes encoded params when params are provided.""" + dummy_client = _DummyAsyncClient() + http_client = AsyncHttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com/resource", + async_base_headers=None, + ) + + await http_client.request( + path="", + method="GET", + params={"after": "456"}, + request_options=None, + ) + + params = dummy_client.last_request_kwargs["params"] + # For a simple dict, encode_query should give a single (key, value) tuple + assert params == [("after", "456")] + + +def test_basic_url_joining() -> None: + """Test basic URL joining with a simple base URL and path.""" + result = _build_url("https://api.example.com", "/users") + assert result == "https://api.example.com/users" + + +def test_basic_url_joining_trailing_slash() -> None: + """Test basic URL joining with a simple base URL and path.""" + result = _build_url("https://api.example.com/", "/users") + assert result == "https://api.example.com/users" + + +def test_preserves_base_url_path_prefix() -> None: + """Test that path prefixes in base URL are preserved. + + This is the critical bug fix - urllib.parse.urljoin() would strip + the path prefix when the path starts with '/'. + """ + result = _build_url("https://cloud.example.com/org/tenant/api", "/users") + assert result == "https://cloud.example.com/org/tenant/api/users" + + +def test_preserves_base_url_path_prefix_trailing_slash() -> None: + """Test that path prefixes in base URL are preserved.""" + result = _build_url("https://cloud.example.com/org/tenant/api/", "/users") + assert result == "https://cloud.example.com/org/tenant/api/users" + + +# --------------------------------------------------------------------------- +# Connection error retry tests +# --------------------------------------------------------------------------- + + +def _make_sync_http_client(mock_client: Any) -> HttpClient: + return HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + ) + + +def _make_async_http_client(mock_client: Any) -> AsyncHttpClient: + return AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + async_base_headers=None, + ) + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_retries_on_connect_error(mock_sleep: MagicMock) -> None: + """Sync: connection error retries on httpx.ConnectError.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + http_client = _make_sync_http_client(mock_client) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_retries_on_remote_protocol_error(mock_sleep: MagicMock) -> None: + """Sync: connection error retries on httpx.RemoteProtocolError.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.RemoteProtocolError("Remote end closed connection without response"), + _DummyResponse(), + ] + http_client = _make_sync_http_client(mock_client) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_connection_error_exhausts_retries(mock_sleep: MagicMock) -> None: + """Sync: connection error exhausts retries then raises.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = _make_sync_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + assert mock_sleep.call_count == 2 + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_connection_error_respects_max_retries_zero(mock_sleep: MagicMock) -> None: + """Sync: connection error respects max_retries=0.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = _make_sync_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 0}, + ) + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_retries_on_connect_error(mock_sleep: AsyncMock) -> None: + """Async: connection error retries on httpx.ConnectError.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + ) + http_client = _make_async_http_client(mock_client) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_retries_on_remote_protocol_error(mock_sleep: AsyncMock) -> None: + """Async: connection error retries on httpx.RemoteProtocolError.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.RemoteProtocolError("Remote end closed connection without response"), + _DummyResponse(), + ] + ) + http_client = _make_async_http_client(mock_client) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_connection_error_exhausts_retries(mock_sleep: AsyncMock) -> None: + """Async: connection error exhausts retries then raises.""" + mock_client = MagicMock() + mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) + http_client = _make_async_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + await http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + assert mock_sleep.call_count == 2 + + +# --------------------------------------------------------------------------- +# base_max_retries constructor parameter tests +# --------------------------------------------------------------------------- + + +def test_sync_http_client_default_base_max_retries() -> None: + """HttpClient defaults to base_max_retries=2.""" + http_client = HttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + ) + assert http_client.base_max_retries == 2 + + +def test_async_http_client_default_base_max_retries() -> None: + """AsyncHttpClient defaults to base_max_retries=2.""" + http_client = AsyncHttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + ) + assert http_client.base_max_retries == 2 + + +def test_sync_http_client_custom_base_max_retries() -> None: + """HttpClient accepts a custom base_max_retries value.""" + http_client = HttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_max_retries=5, + ) + assert http_client.base_max_retries == 5 + + +def test_async_http_client_custom_base_max_retries() -> None: + """AsyncHttpClient accepts a custom base_max_retries value.""" + http_client = AsyncHttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_max_retries=5, + ) + assert http_client.base_max_retries == 5 + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_base_max_retries_zero_disables_retries(mock_sleep: MagicMock) -> None: + """Sync: base_max_retries=0 disables retries when no request_options override.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, + ) + + with pytest.raises(httpx.ConnectError): + http_client.request(path="/test", method="GET") + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_base_max_retries_zero_disables_retries(mock_sleep: AsyncMock) -> None: + """Async: base_max_retries=0 disables retries when no request_options override.""" + mock_client = MagicMock() + mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, + ) + + with pytest.raises(httpx.ConnectError): + await http_client.request(path="/test", method="GET") + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_request_options_override_base_max_retries(mock_sleep: MagicMock) -> None: + """Sync: request_options max_retries overrides base_max_retries.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("connection failed"), + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, # base says no retries + ) + + # But request_options overrides to allow 2 retries + response = http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + assert response.status_code == 200 + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_request_options_override_base_max_retries(mock_sleep: AsyncMock) -> None: + """Async: request_options max_retries overrides base_max_retries.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("connection failed"), + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + ) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, # base says no retries + ) + + # But request_options overrides to allow 2 retries + response = await http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + assert response.status_code == 200 + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_base_max_retries_used_as_default(mock_sleep: MagicMock) -> None: + """Sync: base_max_retries is used when request_options has no max_retries.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + _DummyResponse(), + ] + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=3, + ) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + # 1 initial + 3 retries = 4 total attempts + assert mock_client.request.call_count == 4 + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_base_max_retries_used_as_default(mock_sleep: AsyncMock) -> None: + """Async: base_max_retries is used when request_options has no max_retries.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + _DummyResponse(), + ] + ) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=3, + ) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + # 1 initial + 3 retries = 4 total attempts + assert mock_client.request.call_count == 4 diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py new file mode 100644 index 000000000000..ef5fd7094f9b --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.query_encoder import encode_query + + +def test_query_encoding_deep_objects() -> None: + assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")] + assert encode_query({"hello_world": {"hello": "world"}}) == [("hello_world[hello]", "world")] + assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == [ + ("hello_world[hello][world]", "today"), + ("hello_world[test]", "this"), + ("hi", "there"), + ] + + +def test_query_encoding_deep_object_arrays() -> None: + assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [ + ("objects[key]", "hello"), + ("objects[value]", "world"), + ("objects[key]", "foo"), + ("objects[value]", "bar"), + ] + assert encode_query( + {"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]} + ) == [ + ("users[name]", "string"), + ("users[tags]", "string"), + ("users[name]", "string2"), + ("users[tags]", "string2"), + ("users[tags]", "string3"), + ] + + +def test_encode_query_with_none() -> None: + encoded = encode_query(None) + assert encoded is None diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py new file mode 100644 index 000000000000..b298db89c4bd --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py @@ -0,0 +1,72 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, List + +from .assets.models import ObjectWithOptionalFieldParams, ShapeParams + +from seed.core.serialization import convert_and_respect_annotation_metadata + +UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} +UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} + + +def test_convert_and_respect_annotation_metadata() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "bool_": True, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} + + +def test_convert_and_respect_annotation_metadata_in_list() -> None: + data: List[ObjectWithOptionalFieldParams] = [ + {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, + ] + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write" + ) + + assert converted == [ + {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, + ] + + +def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "union": UNION_TEST, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + + assert converted == { + "string": "string", + "long": 12345, + "union": UNION_TEST_CONVERTED, + "literal": "lit_one", + "any": "any", + } + + +def test_convert_and_respect_annotation_metadata_in_union() -> None: + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write") + + assert converted == UNION_TEST_CONVERTED + + +def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: + data: Any = {} + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write") + assert converted == data diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py new file mode 100644 index 000000000000..2782ac72dd88 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py @@ -0,0 +1,78 @@ +""" +Pytest configuration for wire tests. + +This module provides helpers for creating a configured client that talks to +WireMock and for verifying requests in WireMock. + +The WireMock container lifecycle itself is managed by a top-level pytest +plugin (tests/conftest.py) so that the container is started exactly once +per test run, even when using pytest-xdist. +""" + +import inspect +import os +from typing import Any, Dict, Optional + +import httpx + +from seed.client import SeedApi + +# Check once at import time whether the client constructor accepts a headers kwarg. +try: + _CLIENT_SUPPORTS_HEADERS: bool = "headers" in inspect.signature(SeedApi).parameters +except (TypeError, ValueError): + _CLIENT_SUPPORTS_HEADERS = False + + +def _get_wiremock_base_url() -> str: + """Returns the WireMock base URL from the WIREMOCK_URL environment variable.""" + return os.environ.get("WIREMOCK_URL", "http://localhost:8080") + + +def get_client(test_id: str) -> SeedApi: + """ + Creates a configured client instance for wire tests. + + Args: + test_id: Unique identifier for the test, used for request tracking. + + Returns: + A configured client instance with all required auth parameters. + """ + test_headers = {"X-Test-Id": test_id} + base_url = _get_wiremock_base_url() + + if _CLIENT_SUPPORTS_HEADERS: + return SeedApi( + base_url=base_url, + headers=test_headers, + ) + + return SeedApi( + base_url=base_url, + httpx_client=httpx.Client(headers=test_headers), + ) + + +def verify_request_count( + test_id: str, + method: str, + url_path: str, + query_params: Optional[Dict[str, str]], + expected: int, +) -> None: + """Verifies the number of requests made to WireMock filtered by test ID for concurrency safety.""" + wiremock_admin_url = f"{_get_wiremock_base_url()}/__admin" + request_body: Dict[str, Any] = { + "method": method, + "urlPath": url_path, + "headers": {"X-Test-Id": {"equalTo": test_id}}, + } + if query_params: + query_parameters = {k: {"equalTo": v} for k, v in query_params.items()} + request_body["queryParameters"] = query_parameters + response = httpx.post(f"{wiremock_admin_url}/requests/find", json=request_body) + assert response.status_code == 200, "Failed to query WireMock requests" + result = response.json() + requests_found = len(result.get("requests", [])) + assert requests_found == expected, f"Expected {expected} requests, found {requests_found}" diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py new file mode 100644 index 000000000000..2f638bc05b10 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py @@ -0,0 +1,44 @@ +from .conftest import get_client, verify_request_count + + +def test__search_rule_types() -> None: + """Test searchRuleTypes endpoint with WireMock""" + test_id = "search_rule_types.0" + client = get_client(test_id) + client.search_rule_types() + verify_request_count(test_id, "GET", "/rule-types", None, 1) + + +def test__create_rule() -> None: + """Test createRule endpoint with WireMock""" + test_id = "create_rule.0" + client = get_client(test_id) + client.create_rule( + name="name", + execution_context="prod", + ) + verify_request_count(test_id, "POST", "/rules", None, 1) + + +def test__list_users() -> None: + """Test listUsers endpoint with WireMock""" + test_id = "list_users.0" + client = get_client(test_id) + client.list_users() + verify_request_count(test_id, "GET", "/users", None, 1) + + +def test__get_entity() -> None: + """Test getEntity endpoint with WireMock""" + test_id = "get_entity.0" + client = get_client(test_id) + client.get_entity() + verify_request_count(test_id, "GET", "/entities", None, 1) + + +def test__get_organization() -> None: + """Test getOrganization endpoint with WireMock""" + test_id = "get_organization.0" + client = get_client(test_id) + client.get_organization() + verify_request_count(test_id, "GET", "/organizations", None, 1) diff --git a/seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml b/seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml new file mode 100644 index 000000000000..58747d54a46b --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml @@ -0,0 +1,14 @@ +services: + wiremock: + image: wiremock/wiremock:3.9.1 + ports: + - "0:8080" # Use dynamic port to avoid conflicts with concurrent tests + volumes: + - ./wiremock-mappings.json:/home/wiremock/mappings/wiremock-mappings.json + command: ["--global-response-templating", "--verbose"] + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/__admin/health"] + interval: 2s + timeout: 5s + retries: 15 + start_period: 5s diff --git a/seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json b/seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json new file mode 100644 index 000000000000..801bfb5883a2 --- /dev/null +++ b/seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json @@ -0,0 +1,141 @@ +{ + "mappings": [ + { + "id": "19b28390-baa1-4ec9-87a3-cd8485b994a5", + "name": "Search rule types with paginated results - default", + "request": { + "urlPathTemplate": "/rule-types", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"name\": \"name\",\n \"description\": \"description\"\n }\n ]\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "19b28390-baa1-4ec9-87a3-cd8485b994a5", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + }, + { + "id": "1a934299-52ab-4d86-aa31-9f873790df88", + "name": "Create a rule with constrained execution context - default", + "request": { + "urlPathTemplate": "/rules", + "method": "POST" + }, + "response": { + "status": 200, + "body": "{\n \"createdBy\": \"createdBy\",\n \"createdDateTime\": \"2024-01-15T09:30:00Z\",\n \"modifiedBy\": \"modifiedBy\",\n \"modifiedDateTime\": \"2024-01-15T09:30:00Z\",\n \"id\": \"id\",\n \"name\": \"name\",\n \"status\": \"active\",\n \"executionContext\": \"prod\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "1a934299-52ab-4d86-aa31-9f873790df88", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + } + }, + { + "id": "c4083f9d-e8ad-4944-abe5-163125679505", + "name": "List users with paginated results - default", + "request": { + "urlPathTemplate": "/users", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"email\": \"email\"\n }\n ]\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "c4083f9d-e8ad-4944-abe5-163125679505", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + }, + { + "id": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", + "name": "Get an entity that combines multiple parents - default", + "request": { + "urlPathTemplate": "/entities", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"id\": \"id\",\n \"name\": \"name\",\n \"summary\": \"summary\",\n \"status\": \"active\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + }, + { + "id": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", + "name": "Get an organization with merged object-typed properties - default", + "request": { + "urlPathTemplate": "/organizations", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"id\": \"id\",\n \"metadata\": {\n \"region\": \"region\",\n \"domain\": \"domain\"\n },\n \"name\": \"name\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + } + ], + "meta": { + "total": 5 + } +} \ No newline at end of file diff --git a/seed/python-sdk/allof/no-custom-config/.fern/metadata.json b/seed/python-sdk/allof/no-custom-config/.fern/metadata.json new file mode 100644 index 000000000000..e489d22a79b5 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/.fern/metadata.json @@ -0,0 +1,10 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-python-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "enable_wire_tests": true + }, + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml b/seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml new file mode 100644 index 000000000000..fd1df043d08d --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: ci +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest -rP -n auto . + + - name: Install aiohttp extra + run: poetry install --extras aiohttp + + - name: Test (aiohttp) + run: poetry run pytest -rP -n auto -m aiohttp . + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" + env: + PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/python-sdk/allof/no-custom-config/.gitignore b/seed/python-sdk/allof/no-custom-config/.gitignore new file mode 100644 index 000000000000..d2e4ca808d21 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/.gitignore @@ -0,0 +1,5 @@ +.mypy_cache/ +.ruff_cache/ +__pycache__/ +dist/ +poetry.toml diff --git a/seed/python-sdk/allof/no-custom-config/README.md b/seed/python-sdk/allof/no-custom-config/README.md new file mode 100644 index 000000000000..8a3e78f60ee6 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/README.md @@ -0,0 +1,176 @@ +# Seed Python Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPython) +[![pypi](https://img.shields.io/pypi/v/fern_allof)](https://pypi.python.org/pypi/fern_allof) + +The Seed Python library provides convenient access to the Seed APIs from Python. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Async Client](#async-client) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Access Raw Response Data](#access-raw-response-data) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom Client](#custom-client) +- [Contributing](#contributing) + +## Installation + +```sh +pip install fern_allof +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```python +from seed import SeedApi + +client = SeedApi() + +client.create_rule( + name="name", + execution_context="prod", +) +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) +``` + +## Async Client + +The SDK also exports an `async` client so that you can make non-blocking calls to our API. Note that if you are constructing an Async httpx client class to pass into this client, use `httpx.AsyncClient()` instead of `httpx.Client()` (e.g. for the `httpx_client` parameter of this client). + +```python +import asyncio + +from seed import AsyncSeedApi + +client = AsyncSeedApi() + + +async def main() -> None: + await client.create_rule( + name="name", + execution_context="prod", + ) + + +asyncio.run(main()) +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```python +from seed.core.api_error import ApiError + +try: + client.create_rule(...) +except ApiError as e: + print(e.status_code) + print(e.body) +``` + +## Advanced + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.with_raw_response` property. +The `.with_raw_response` property returns a "raw" client that can be used to access the `.headers` and `.data` attributes. + +```python +from seed import SeedApi + +client = SeedApi(...) +response = client.with_raw_response.create_rule(...) +print(response.headers) # access the response headers +print(response.status_code) # access the response status code +print(response.data) # access the underlying object +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` request option to configure this behavior. + +```python +client.create_rule(..., request_options={ + "max_retries": 1 +}) +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```python +from seed import SeedApi + +client = SeedApi(..., timeout=20.0) + +# Override timeout for a specific method +client.create_rule(..., request_options={ + "timeout_in_seconds": 1 +}) +``` + +### Custom Client + +You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies +and transports. + +```python +import httpx +from seed import SeedApi + +client = SeedApi( + ..., + httpx_client=httpx.Client( + proxy="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/allof/no-custom-config/poetry.lock b/seed/python-sdk/allof/no-custom-config/poetry.lock new file mode 100644 index 000000000000..75cfea1d0649 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/poetry.lock @@ -0,0 +1,1613 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = true +python-versions = ">=3.9" +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.13.5" +description = "Async http client/server framework (asyncio)" +optional = true +python-versions = ">=3.9" +files = [ + {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, + {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, + {file = "aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7"}, + {file = "aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9"}, + {file = "aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3"}, + {file = "aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06"}, + {file = "aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14"}, + {file = "aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3"}, + {file = "aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c"}, + {file = "aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc"}, + {file = "aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b"}, + {file = "aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3"}, + {file = "aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9"}, + {file = "aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8"}, + {file = "aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:347542f0ea3f95b2a955ee6656461fa1c776e401ac50ebce055a6c38454a0adf"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:178c7b5e62b454c2bc790786e6058c3cc968613b4419251b478c153a4aec32b1"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af545c2cffdb0967a96b6249e6f5f7b0d92cdfd267f9d5238d5b9ca63e8edb10"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:206b7b3ef96e4ce211754f0cd003feb28b7d81f0ad26b8d077a5d5161436067f"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee5e86776273de1795947d17bddd6bb19e0365fd2af4289c0d2c5454b6b1d36b"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95d14ca7abefde230f7639ec136ade282655431fd5db03c343b19dda72dd1643"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:912d4b6af530ddb1338a66229dac3a25ff11d4448be3ec3d6340583995f56031"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e999f0c88a458c836d5fb521814e92ed2172c649200336a6df514987c1488258"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39380e12bd1f2fdab4285b6e055ad48efbaed5c836433b142ed4f5b9be71036a"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9efcc0f11d850cefcafdd9275b9576ad3bfb539bed96807663b32ad99c4d4b88"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:147b4f501d0292077f29d5268c16bb7c864a1f054d7001c4c1812c0421ea1ed0"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d147004fede1b12f6013a6dbb2a26a986a671a03c6ea740ddc76500e5f1c399f"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9277145d36a01653863899c665243871434694bcc3431922c3b35c978061bdb8"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4e704c52438f66fdd89588346183d898bb42167cf88f8b7ff1c0f9fc957c348f"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8a4d3427e8de1312ddf309cc482186466c79895b3a139fed3259fc01dfa9a5b"}, + {file = "aiohttp-3.13.5-cp39-cp39-win32.whl", hash = "sha256:6f497a6876aa4b1a102b04996ce4c1170c7040d83faa9387dd921c16e30d5c83"}, + {file = "aiohttp-3.13.5-cp39-cp39-win_amd64.whl", hash = "sha256:cb979826071c0986a5f08333a36104153478ce6018c58cba7f9caddaf63d5d67"}, + {file = "aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = true +python-versions = ">=3.9" +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + +[[package]] +name = "anyio" +version = "4.13.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.10" +files = [ + {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, + {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +trio = ["trio (>=0.32.0)"] + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = true +python-versions = ">=3.8" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "attrs" +version = "26.1.0" +description = "Classes Without Boilerplate" +optional = true +python-versions = ">=3.9" +files = [ + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +files = [ + {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, + {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +files = [ + {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, + {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, + {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.1.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "frozenlist" +version = "1.8.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = true +python-versions = ">=3.9" +files = [ + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, + {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, + {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, + {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, + {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, + {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, + {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, + {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, + {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, +] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "httpx-aiohttp" +version = "0.1.8" +description = "Aiohttp transport for HTTPX" +optional = true +python-versions = ">=3.8" +files = [ + {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, + {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, +] + +[package.dependencies] +aiohttp = ">=3.10.0,<4" +httpx = ">=0.27.0" + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "multidict" +version = "6.7.1" +description = "multidict implementation" +optional = true +python-versions = ">=3.9" +files = [ + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, + {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, + {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, + {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, + {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, + {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, + {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, + {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, + {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, + {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, + {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, + {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, + {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, + {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, + {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, + {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, + {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, + {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, + {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, + {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, + {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, + {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, + {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, + {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, + {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, + {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, + {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "propcache" +version = "0.4.1" +description = "Accelerated property cache" +optional = true +python-versions = ">=3.9" +files = [ + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, + {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, + {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, + {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, + {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, + {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, + {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, + {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, + {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, + {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, + {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, + {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, + {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, + {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, + {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, + {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, + {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, + {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, + {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, + {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, + {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, + {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, + {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, + {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, + {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, + {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, + {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, +] + +[[package]] +name = "pydantic" +version = "1.10.26" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546"}, + {file = "pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572"}, + {file = "pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464"}, + {file = "pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53"}, + {file = "pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945"}, + {file = "pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637"}, + {file = "pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77"}, + {file = "pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f"}, + {file = "pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5"}, + {file = "pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df"}, + {file = "pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f"}, + {file = "pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f"}, + {file = "pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb"}, + {file = "pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b"}, + {file = "pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c"}, + {file = "pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c"}, + {file = "pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba"}, + {file = "pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62"}, + {file = "pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847"}, + {file = "pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314"}, + {file = "pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba"}, + {file = "pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37"}, + {file = "pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a"}, + {file = "pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962"}, + {file = "pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c"}, + {file = "pydantic-1.10.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc5c91a3b3106caf07ac6735ec6efad8ba37b860b9eb569923386debe65039ad"}, + {file = "pydantic-1.10.26-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dde599e0388e04778480d57f49355c9cc7916de818bf674de5d5429f2feebfb6"}, + {file = "pydantic-1.10.26-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be08b5cfe88e58198722861c7aab737c978423c3a27300911767931e5311d0d"}, + {file = "pydantic-1.10.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0141f4bafe5eda539d98c9755128a9ea933654c6ca4306b5059fc87a01a38573"}, + {file = "pydantic-1.10.26-cp38-cp38-win_amd64.whl", hash = "sha256:eb664305ffca8a9766a8629303bb596607d77eae35bb5f32ff9245984881b638"}, + {file = "pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d"}, + {file = "pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15"}, + {file = "pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90"}, + {file = "pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba"}, + {file = "pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96"}, + {file = "pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917"}, + {file = "pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pydantic-core" +version = "2.42.0" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.42.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:0ae7d50a47ada2a04f7296be9a7a2bf447118a25855f41fc52c8fc4bfb70c105"}, + {file = "pydantic_core-2.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c9d04d4bd8de1dcd5c8845faf6c11e36cda34c2efffa29d70ad83cc6f6a6c9a8"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e459e89453bb1bc69853272260afb5328ae404f854ddec485f5427fbace8d7e"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:def66968fbe20274093fd4fc85d82b2ec42dbe20d9e51d27bbf3b5c7428c7a10"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:272fab515dc7da0f456c49747b87b4e8721a33ab352a54760cc8fd1a4fd5348a"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa82dec59f36106738ae981878e0001074e2b3a949f21a5b3bea20485b9c6db4"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a70fe4db00ab03a9f976d28471c8e696ebd3b8455ccfa5e36e5d1a2ff301a7"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b4c0f656b4fa218413a485c550ac3e4ddf2f343a9c46b6137394bd77c4128445"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a4396ffc8b42499d14662f958b3f00656b62a67bde7f156580fd618827bebf5a"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:36067825f365a5c3065f17d08421a72b036ff4588c450afe54d5750b80cc220d"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eec64367de940786c0b686d47bd952692018dd7cd895027aa82023186e469b7d"}, + {file = "pydantic_core-2.42.0-cp310-cp310-win32.whl", hash = "sha256:ff9f0737f487277721682d8518434557cfcef141ba55b89381c92700594a8b65"}, + {file = "pydantic_core-2.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:77f0a8ab035d3bc319b759d8215f51846e9ea582dacbabb2777e5e3e135a048e"}, + {file = "pydantic_core-2.42.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1159b9ee73511ae7c5631b108d80373577bc14f22d18d85bb2aa1fa1051dabc"}, + {file = "pydantic_core-2.42.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff8e49b22225445d3e078aaa9bead90c37c852aee8f8a169ba15fdaaa13d1ecb"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe777d9a1a932c6b3ef32b201985324d06d9c74028adef1e1c7ea226fca2ba34"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e92592c1040ed17968d603e05b72acec321662ef9bf88fef443ceae4d1a130c2"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:557a6eb6dc4db8a3f071929710feb29c6b5d7559218ab547a4e60577fb404f2f"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4035f81e7d1a5e065543061376ca52ccb0accaf970911ba0a9ec9d22062806ca"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63a4e073f8def1c7fd100a355b3a96e1bbaf0446b6a8530ae58f1afaa0478a46"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd8469c8d9f6c81befd10c72a0268079e929ba494cd27fa63e868964b0e04fb6"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bdebfd610a02bdb82f8e36dc7d4683e03e420624a2eda63e1205730970021308"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9577eb5221abd4e5adf8a232a65f74c509b82b57b7b96b3667dac22f03ff9e94"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6d36841b61100128c2374341a7c2c0ab347ef4b63aa4b6837b4431465d4d4fd"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win32.whl", hash = "sha256:1d9d45333a28b0b8fb8ecedf67d280dc3318899988093e4d3a81618396270697"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:4631b4d1a3fe460aadd3822af032bb6c2e7ad77071fbf71c4e95ef9083c7c1a8"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d46bfc6175a4b4b80b9f98f76133fbf68d5a02d7469b3090ca922d40f23d32d"}, + {file = "pydantic_core-2.42.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a11b9115364681779bcc39c6b9cdc20d48a9812a4bf3ed986fec4f694ed3a1e7"}, + {file = "pydantic_core-2.42.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c43088e8a44ccb2a2329d83892110587ebe661090b546dd03624a933fc4cfd0d"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13a7f9dde97c8400de559b2b2dcd9439f7b2b8951dad9b19711ef8c6e3f68ac0"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6380214c627f702993ea6b65b6aa8afc0f1481a179cdd169a2fc80a195e21158"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:606f80d8c61d4680ff82a34e9c49b7ab069b544b93393cc3c5906ac9e8eec7c9"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ab80ae93cb739de6c9ccc06a12cd731b079e1b25b03e2dcdccbc914389cc7e0"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:638f04b55bea04ec5bbda57a4743a51051f24b884abcb155b0ed2c3cb59ba448"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec72ba5c7555f69757b64b398509c7079fb22da705a6c67ac613e3f14a05f729"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0364f6cd61be57bcd629c34788c197db211e91ce1c3009bf4bf97f6bb0eb21f"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:856f0fd81173b308cd6ceb714332cd9ea3c66ce43176c7defaed6b2ed51d745c"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1be705396e480ea96fd3cccd7512affda86823b8a2a8c196d9028ec37cb1ca77"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win32.whl", hash = "sha256:acacf0795d68e42d01ae8cc77ae19a5b3c80593e0fd60e4e2d336ec13d3de906"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win_amd64.whl", hash = "sha256:475a1a5ecf3a748a0d066b56138d258018c8145873ee899745c9f0e0af1cc4d4"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win_arm64.whl", hash = "sha256:e2369cef245dd5aeafe6964cf43d571fb478f317251749c152c0ae564127053a"}, + {file = "pydantic_core-2.42.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:02fd2b4a62efa12e004fce2bfd2648cf8c39efc5dfc5ed5f196eb4ccefc7db4e"}, + {file = "pydantic_core-2.42.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c042694870c20053b8814a57c416cd2c6273fe462a440460005c791c24c39baf"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f905f3a082e7498dfaa70c204b236e92d448ba966ad112a96fcaaba2c4984fba"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4762081e8acc5458bf907373817cf93c927d451a1b294c1d0535b0570890d939"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4a433bbf6304bd114b96b0ce3ed9add2ee686df448892253bca5f622c030f31"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd695305724cfce8b19a18e87809c518f56905e5c03a19e3ad061974970f717d"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f352ffa0ec2983b849a93714571063bfc57413b5df2f1027d7a04b6e8bdd25"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e61f2a194291338d76307a29e4881a8007542150b750900c1217117fc9bb698e"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:032f990dc1759f11f6b287e5c6eb1b0bcfbc18141779414a77269b420360b3bf"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:9c28b42768da6b9238554ae23b39291c3bbe6f53c4810aea6414d83efd59b96a"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b22af1ac75fa873d81a65cce22ada1d840583b73a129b06133097c81f6f9e53b"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win32.whl", hash = "sha256:1de0350645c8643003176659ee70b637cd80e8514a063fff36f088fcda2dba06"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win_amd64.whl", hash = "sha256:d34b481a8a3eba3678a96e166c6e547c0c8b026844c13d9deb70c9f1fd2b0979"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win_arm64.whl", hash = "sha256:5e0a65358eef041d95eef93fcf8834c2c8b83cc5a92d32f84bb3a7955dfe21c9"}, + {file = "pydantic_core-2.42.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:de4c9ad4615983b3fb2ee57f5c570cf964bda13353c6c41a54dac394927f0e54"}, + {file = "pydantic_core-2.42.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:129d5e6357814e4567e18b2ded4c210919aafd9ef0887235561f8d853fd34123"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c45582a5dac4649e512840ad212a5c2f9d168622f8db8863e8a29b54a29dfd"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a97fc19afb730b45de55d2e80093f1a36effc29538dec817204c929add8f2b4a"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45d83d38d94f22ffe9a0f0393b23e25bfefe4804ae63c8013906b76ab8de8ed"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3060192d8b63611a2abb26eccadddff5602a66491b8fafd9ae34fb67302ae84"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f17739150af9dc58b5c8fc3c4a1826ff84461f11b9f8ad5618445fcdd1ccec6"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d14e4c229467a7c27aa7c71e21584b3d77352ccb64e968fdbed4633373f73f7"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:aaef75e1b54366c7ccfbf4fc949ceaaa0f4c87e106df850354be6c7d45143db0"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:d2e362dceeeb4d56fd63e649c2de3ad4c3aa448b13ab8a9976e23a669f9c1854"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:a8edee724b527818bf0a6c8e677549794c0d0caffd14492851bd7a4ceab0f258"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win32.whl", hash = "sha256:a10c105c221f68221cb81be71f063111172f5ddf8b06f6494560e826c148f872"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win_amd64.whl", hash = "sha256:232d86e00870aceee7251aa5f4ab17e3e4864a4656c015f8e03d1223bf8e17ba"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win_arm64.whl", hash = "sha256:9a6fce4e778c2fe2b3f1df63bfaa522c147668517ba040c49ad7f67a66867cff"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f4d1670fbc5488cfb18dd9fc71a2c7c8e12caeeb6e5bb641aa351ac5e01963cf"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:baeae16666139d0110f1006a06809228f5293ab84e77f4b9dda2bdee95d6c4e8"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a77c7a8cedf5557a4e5547dabf55a8ec99949162bd7925b312f6ec37c24101c"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:133fccf13546ff2a0610cc5b978dd4ee2c7f55a7a86b6b722fd6e857694bacc5"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad5dbebfbab92cf0f6d0b13d55bf0a239880a1534377edf6387e2e7a4469f131"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6c0181016cb29ba4824940246606a8e13b1135de8306e00b5bd9d1efbc4cf85"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:020cfd7041cb71eac4dc93a29a6d5ec34f10b1fdc37f4f189c25bcc6748a2f97"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73c6de3ee24f2b614d344491eda5628c4cdf3e7b79c0ac69bb40884ced2d319"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b2b448da50e1e8d5aac786dcf441afa761d26f1be4532b52cdf50864b47bd784"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:0df0488b1f548ef874b45bbc60a70631eee0177b79b5527344d7a253e77a5ed2"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:b8aa32697701dc36c956f4a78172549adbe25eacba952bbfbde786fb66316151"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win32.whl", hash = "sha256:173de56229897ff81b650ca9ed6f4c62401c49565234d3e9ae251119f6fd45c6"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2db227cf6797c286361f8d1e52b513f358a3ff9ebdede335e55a5edf4c59f06b"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a983862733ecaf0b5c7275145f86397bde4ee1ad84cf650e1d7af7febe5f7073"}, + {file = "pydantic_core-2.42.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fc0834a2d658189c89d7a009ae19462da1d70fc4786d2b8e5c8c6971f4d3bcc1"}, + {file = "pydantic_core-2.42.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff69cf1eb517600d40c903dbc3507360e0a6c1ffa2dcf3cfa49a1c6fe203a46a"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3eab236da1c53a8cdf741765e31190906eb2838837bfedcaa6c0206b8f5975e"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15df82e324fa5b2b1403d5eb1bb186d14214c3ce0aebc9a3594435b82154d402"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ee7047297892d4fec68658898b7495be8c1a8a2932774e2d6810c3de1173783"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aec13272d859be1dd3344b75aab4d1d6690bfef78bd241628f6903c2bf101f8d"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e7adfd7794da8ae101d2d5e6a7be7cb39bb90d45b6aa42ecb502a256e94f8e0"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e3cfcacb42193479ead3aaba26a79e7df4c1c2415aefc43f1a60b57f50f8aa4"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf89cee72f88db54763f800d32948bd6b1b9bf03e0ecb0a9cb93eac513caec5f"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c6ae4c08e6c4b08e35eb2b114803d09c5012602983d8bbd3564013d555dfe5fd"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dfedd24ce01a3ea32f29c257e5a7fc79ed635cff0bd1a1aed12a22d3440cb39f"}, + {file = "pydantic_core-2.42.0-cp39-cp39-win32.whl", hash = "sha256:26ab24eecdec230bdf7ec519b9cd0c65348ec6e97304e87f9d3409749ea3377b"}, + {file = "pydantic_core-2.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:f93228d630913af3bc2d55a50a96e0d33446b219aea9591bfdc0a06677f689ff"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:53ab90bed3a191750a6726fe2570606a9794608696063823d2deea734c100bf6"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:b8d9911a3cdb8062f4102499b666303c9a976202b420200a26606eafa0bfecf8"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe6b7b22dd1d326a1ab23b9e611a69c41d606cb723839755bb00456ebff3f672"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5e36849ca8e2e39828a70f1a86aa2b86f645a1d710223b6653f2fa8a130b703"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4d7e36c2a1f3c0020742190714388884a11282a0179f3d1c55796ee26b32dba5"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:41a702c2ac3dbbafa7d13bea142b3e04c8676d1fca199bac52b5ee24e6cdb737"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad5cb8ed96ffac804a0298f5d03f002769514700d79cbe77b66a27a6e605a65a"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51e33cf940cddcad333f85e15a25a2a949ac0a7f26fe8f43dc2d6816ce974ec4"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495e70705f553c3b8f939965fa7cf77825c81417ff3c7ac046be9509b94c292c"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8757702cc696d48f9fdcb65cb835ca18bda5d83169fe6d13efd706e4195aea81"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32cc3087f38e4a9ee679f6184670a1b6591b8c3840c483f3342e176e215194d1"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e824d8f372aa717eeb435ee220c8247e514283a4fc0ecdc4ce44c09ee485a5b8"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e5900b257abb20371135f28b686d6990202dcdd9b7d8ff2e2290568aa0058280"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:f6705c73ab2abaebef81cad882a75afd6b8a0550e853768933610dce2945705e"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5ed95136324ceef6f33bd96ee3a299d36169175401204590037983aeb5bc73de"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9d729a3934e0ef3bc171025f0414d422aa6397d6bbd8176d5402739140e50616"}, + {file = "pydantic_core-2.42.0.tar.gz", hash = "sha256:34068adadf673c872f01265fa17ec00073e99d7f53f6d499bdfae652f330b3d2"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, +] + +[package.dependencies] +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "requests" +version = "2.33.1" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.10" +files = [ + {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, + {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, +] + +[package.dependencies] +certifi = ">=2023.5.7" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.26,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] + +[[package]] +name = "ruff" +version = "0.11.5" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, + {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, + {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, + {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, + {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, + {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, + {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tomli" +version = "2.4.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20260408" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.10" +files = [ + {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, + {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, +] + +[[package]] +name = "types-requests" +version = "2.33.0.20260408" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.10" +files = [ + {file = "types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f"}, + {file = "types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +files = [ + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, +] + +[package.extras] +brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["backports-zstd (>=1.0.0)"] + +[[package]] +name = "yarl" +version = "1.23.0" +description = "Yet another URL library" +optional = true +python-versions = ">=3.10" +files = [ + {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, + {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, + {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, + {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, + {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, + {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, + {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, + {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, + {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, + {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, + {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, + {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, + {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, + {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, + {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, + {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, + {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, + {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, + {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, + {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, + {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, + {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, + {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, + {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, + {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, + {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + +[extras] +aiohttp = ["aiohttp", "httpx-aiohttp"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "a8bb006c9955dae7967f3694092cf6799a89e0aac94e82e903d8b308c4d0cfc4" diff --git a/seed/python-sdk/allof/no-custom-config/pyproject.toml b/seed/python-sdk/allof/no-custom-config/pyproject.toml new file mode 100644 index 000000000000..8dfe9ad891fd --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/pyproject.toml @@ -0,0 +1,102 @@ +[project] +name = "fern_allof" +dynamic = ["version"] + +[tool.poetry] +name = "fern_allof" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [ + "fern", + "test" +] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed", from = "src"} +] + +[tool.poetry.urls] +Documentation = 'https://buildwithfern.com/learn' +Homepage = 'https://buildwithfern.com/' +Repository = 'https://github.com/allof/fern' + +[tool.poetry.dependencies] +python = "^3.10" +aiohttp = { version = ">=3.10.0,<4", optional = true} +httpx = ">=0.21.2" +httpx-aiohttp = { version = "0.1.8", optional = true} +pydantic = "^1.10" +pydantic-core = ">=2.18.2,<2.44.0" +typing_extensions = ">= 4.0.0" + +[tool.poetry.group.dev.dependencies] +mypy = "==1.13.0" +pytest = "^8.2.0" +pytest-asyncio = "^1.0.0" +pytest-xdist = "^3.6.1" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" +requests = "^2.31.0" +types-requests = "^2.31.0" +ruff = "==0.11.5" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" +markers = [ + "aiohttp: tests that require httpx_aiohttp to be installed", +] + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort +] +ignore = [ + "E402", # Module level import not at top of file + "E501", # Line too long + "E711", # Comparison to `None` should be `cond is not None` + "E712", # Avoid equality comparisons to `True`; use `if ...:` checks + "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks + "E722", # Do not use bare `except` + "E731", # Do not assign a `lambda` expression, use a `def` + "F821", # Undefined name + "F841" # Local variable ... is assigned to but never used +] + +[tool.ruff.lint.isort] +section-order = ["future", "standard-library", "third-party", "first-party"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.extras] +aiohttp=["aiohttp", "httpx-aiohttp"] diff --git a/seed/python-sdk/allof/no-custom-config/reference.md b/seed/python-sdk/allof/no-custom-config/reference.md new file mode 100644 index 000000000000..e92bd3a6925d --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/reference.md @@ -0,0 +1,268 @@ +# Reference +
client.search_rule_types(...) -> RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.search_rule_types() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `typing.Optional[str]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.create_rule(...) -> RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.create_rule( + name="name", + execution_context="prod", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `str` + +
+
+ +
+
+ +**execution_context:** `RuleExecutionContext` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.list_users() -> UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.list_users() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.get_entity() -> CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.get_entity() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.get_organization() -> Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi +from seed.environment import SeedApiEnvironment + +client = SeedApi( + environment=SeedApiEnvironment.DEFAULT, +) + +client.get_organization() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/python-sdk/allof/no-custom-config/requirements.txt b/seed/python-sdk/allof/no-custom-config/requirements.txt new file mode 100644 index 000000000000..0141a1a5014b --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/requirements.txt @@ -0,0 +1,4 @@ +httpx>=0.21.2 +pydantic>= 1.9.2 +pydantic-core>=2.18.2,<2.44.0 +typing_extensions>= 4.0.0 diff --git a/seed/python-sdk/allof/no-custom-config/snippet.json b/seed/python-sdk/allof/no-custom-config/snippet.json new file mode 100644 index 000000000000..f000c2ddf786 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/snippet.json @@ -0,0 +1,70 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": "default", + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.search_rule_types()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.search_rule_types()\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.list_users()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.list_users()\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_entity()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_entity()\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_organization()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_organization()\n\n\nasyncio.run(main())\n", + "type": "python" + } + } + ] +} \ No newline at end of file diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/__init__.py new file mode 100644 index 000000000000..20ed7e281f4c --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/__init__.py @@ -0,0 +1,110 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + AuditInfo, + BaseOrg, + BaseOrgMetadata, + CombinedEntity, + CombinedEntityStatus, + Describable, + DetailedOrg, + DetailedOrgMetadata, + Identifiable, + Organization, + PaginatedResult, + PagingCursors, + RuleExecutionContext, + RuleResponse, + RuleResponseStatus, + RuleType, + RuleTypeSearchResponse, + User, + UserSearchResponse, + ) + from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient + from .client import AsyncSeedApi, SeedApi + from .environment import SeedApiEnvironment + from .version import __version__ +_dynamic_imports: typing.Dict[str, str] = { + "AsyncSeedApi": ".client", + "AuditInfo": ".types", + "BaseOrg": ".types", + "BaseOrgMetadata": ".types", + "CombinedEntity": ".types", + "CombinedEntityStatus": ".types", + "DefaultAioHttpClient": "._default_clients", + "DefaultAsyncHttpxClient": "._default_clients", + "Describable": ".types", + "DetailedOrg": ".types", + "DetailedOrgMetadata": ".types", + "Identifiable": ".types", + "Organization": ".types", + "PaginatedResult": ".types", + "PagingCursors": ".types", + "RuleExecutionContext": ".types", + "RuleResponse": ".types", + "RuleResponseStatus": ".types", + "RuleType": ".types", + "RuleTypeSearchResponse": ".types", + "SeedApi": ".client", + "SeedApiEnvironment": ".environment", + "User": ".types", + "UserSearchResponse": ".types", + "__version__": ".version", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AsyncSeedApi", + "AuditInfo", + "BaseOrg", + "BaseOrgMetadata", + "CombinedEntity", + "CombinedEntityStatus", + "DefaultAioHttpClient", + "DefaultAsyncHttpxClient", + "Describable", + "DetailedOrg", + "DetailedOrgMetadata", + "Identifiable", + "Organization", + "PaginatedResult", + "PagingCursors", + "RuleExecutionContext", + "RuleResponse", + "RuleResponseStatus", + "RuleType", + "RuleTypeSearchResponse", + "SeedApi", + "SeedApiEnvironment", + "User", + "UserSearchResponse", + "__version__", +] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py b/seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py new file mode 100644 index 000000000000..de71e3bb16d7 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +SDK_DEFAULT_TIMEOUT = 60 + +try: + import httpx_aiohttp # type: ignore[import-not-found] +except ImportError: + + class DefaultAioHttpClient(httpx.AsyncClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + raise RuntimeError("To use the aiohttp client, install the aiohttp extra: pip install fern_allof[aiohttp]") + +else: + + class DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +class DefaultAsyncHttpxClient(httpx.AsyncClient): + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/client.py b/seed/python-sdk/allof/no-custom-config/src/seed/client.py new file mode 100644 index 000000000000..65adf5ca16bc --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/client.py @@ -0,0 +1,500 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .core.logging import LogConfig, Logger +from .core.request_options import RequestOptions +from .environment import SeedApiEnvironment +from .raw_client import AsyncRawSeedApi, RawSeedApi +from .types.combined_entity import CombinedEntity +from .types.organization import Organization +from .types.rule_execution_context import RuleExecutionContext +from .types.rule_response import RuleResponse +from .types.rule_type_search_response import RuleTypeSearchResponse +from .types.user_search_response import UserSearchResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : SeedApiEnvironment + The environment to use for requests from the client. from .environment import SeedApiEnvironment + + + + Defaults to SeedApiEnvironment.DEFAULT + + + + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = ( + timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read + ) + self._client_wrapper = SyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + headers=headers, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + logging=logging, + ) + self._raw_client = RawSeedApi(client_wrapper=self._client_wrapper) + + @property + def with_raw_response(self) -> RawSeedApi: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSeedApi + """ + return self._raw_client + + def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> RuleTypeSearchResponse: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleTypeSearchResponse + Paginated list of rule types + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.search_rule_types() + """ + _response = self._raw_client.search_rule_types(query=query, request_options=request_options) + return _response.data + + def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> RuleResponse: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleResponse + Created rule + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.create_rule( + name="name", + execution_context="prod", + ) + """ + _response = self._raw_client.create_rule( + name=name, execution_context=execution_context, request_options=request_options + ) + return _response.data + + def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UserSearchResponse + Paginated list of users + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.list_users() + """ + _response = self._raw_client.list_users(request_options=request_options) + return _response.data + + def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CombinedEntity + An entity with properties from multiple parents + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.get_entity() + """ + _response = self._raw_client.get_entity(request_options=request_options) + return _response.data + + def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Organization + An organization whose metadata is merged from two parents + + Examples + -------- + from seed import SeedApi + + client = SeedApi() + client.get_organization() + """ + _response = self._raw_client.get_organization(request_options=request_options) + return _response.data + + +def _make_default_async_client( + timeout: typing.Optional[float], + follow_redirects: typing.Optional[bool], +) -> httpx.AsyncClient: + try: + import httpx_aiohttp # type: ignore[import-not-found] + except ImportError: + pass + else: + if follow_redirects is not None: + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout) + + if follow_redirects is not None: + return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx.AsyncClient(timeout=timeout) + + +class AsyncSeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : SeedApiEnvironment + The environment to use for requests from the client. from .environment import SeedApiEnvironment + + + + Defaults to SeedApiEnvironment.DEFAULT + + + + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from seed import AsyncSeedApi + + client = AsyncSeedApi() + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = ( + timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read + ) + self._client_wrapper = AsyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + headers=headers, + httpx_client=httpx_client + if httpx_client is not None + else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), + timeout=_defaulted_timeout, + logging=logging, + ) + self._raw_client = AsyncRawSeedApi(client_wrapper=self._client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSeedApi: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSeedApi + """ + return self._raw_client + + async def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> RuleTypeSearchResponse: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleTypeSearchResponse + Paginated list of rule types + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.search_rule_types() + + + asyncio.run(main()) + """ + _response = await self._raw_client.search_rule_types(query=query, request_options=request_options) + return _response.data + + async def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> RuleResponse: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RuleResponse + Created rule + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.create_rule( + name="name", + execution_context="prod", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_rule( + name=name, execution_context=execution_context, request_options=request_options + ) + return _response.data + + async def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UserSearchResponse + Paginated list of users + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.list_users() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_users(request_options=request_options) + return _response.data + + async def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CombinedEntity + An entity with properties from multiple parents + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.get_entity() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_entity(request_options=request_options) + return _response.data + + async def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Organization + An organization whose metadata is merged from two parents + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi() + + + async def main() -> None: + await client.get_organization() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_organization(request_options=request_options) + return _response.data + + +def _get_base_url(*, base_url: typing.Optional[str] = None, environment: SeedApiEnvironment) -> str: + if base_url is not None: + return base_url + elif environment is not None: + return environment.value + else: + raise Exception("Please pass in either base_url or environment to construct the client") diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py new file mode 100644 index 000000000000..5bc159a110f2 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py @@ -0,0 +1,127 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .api_error import ApiError + from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper + from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime + from .file import File, convert_file_dict_to_httpx_tuples, with_content_type + from .http_client import AsyncHttpClient, HttpClient + from .http_response import AsyncHttpResponse, HttpResponse + from .jsonable_encoder import encode_path_param, jsonable_encoder + from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger + from .parse_error import ParsingError + from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, + ) + from .query_encoder import encode_query + from .remove_none_from_dict import remove_none_from_dict + from .request_options import RequestOptions + from .serialization import FieldMetadata, convert_and_respect_annotation_metadata +_dynamic_imports: typing.Dict[str, str] = { + "ApiError": ".api_error", + "AsyncClientWrapper": ".client_wrapper", + "AsyncHttpClient": ".http_client", + "AsyncHttpResponse": ".http_response", + "BaseClientWrapper": ".client_wrapper", + "ConsoleLogger": ".logging", + "FieldMetadata": ".serialization", + "File": ".file", + "HttpClient": ".http_client", + "HttpResponse": ".http_response", + "ILogger": ".logging", + "IS_PYDANTIC_V2": ".pydantic_utilities", + "LogConfig": ".logging", + "LogLevel": ".logging", + "Logger": ".logging", + "ParsingError": ".parse_error", + "RequestOptions": ".request_options", + "Rfc2822DateTime": ".datetime_utils", + "SyncClientWrapper": ".client_wrapper", + "UniversalBaseModel": ".pydantic_utilities", + "UniversalRootModel": ".pydantic_utilities", + "convert_and_respect_annotation_metadata": ".serialization", + "convert_file_dict_to_httpx_tuples": ".file", + "create_logger": ".logging", + "encode_path_param": ".jsonable_encoder", + "encode_query": ".query_encoder", + "jsonable_encoder": ".jsonable_encoder", + "parse_obj_as": ".pydantic_utilities", + "parse_rfc2822_datetime": ".datetime_utils", + "remove_none_from_dict": ".remove_none_from_dict", + "serialize_datetime": ".datetime_utils", + "universal_field_validator": ".pydantic_utilities", + "universal_root_validator": ".pydantic_utilities", + "update_forward_refs": ".pydantic_utilities", + "with_content_type": ".file", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "AsyncHttpResponse", + "BaseClientWrapper", + "ConsoleLogger", + "FieldMetadata", + "File", + "HttpClient", + "HttpResponse", + "ILogger", + "IS_PYDANTIC_V2", + "LogConfig", + "LogLevel", + "Logger", + "ParsingError", + "RequestOptions", + "Rfc2822DateTime", + "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "create_logger", + "encode_path_param", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "parse_rfc2822_datetime", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", + "with_content_type", +] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py new file mode 100644 index 000000000000..6f850a60cba3 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ApiError(Exception): + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py new file mode 100644 index 000000000000..a305bde51b00 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py @@ -0,0 +1,95 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx +from .http_client import AsyncHttpClient, HttpClient +from .logging import LogConfig, Logger + + +class BaseClientWrapper: + def __init__( + self, + *, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self._headers = headers + self._base_url = base_url + self._timeout = timeout + self._logging = logging + + def get_headers(self) -> typing.Dict[str, str]: + import platform + + headers: typing.Dict[str, str] = { + "User-Agent": "fern_allof/0.0.1", + "X-Fern-Language": "Python", + "X-Fern-Runtime": f"python/{platform.python_version()}", + "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", + "X-Fern-SDK-Name": "fern_allof", + "X-Fern-SDK-Version": "0.0.1", + **(self.get_custom_headers() or {}), + } + return headers + + def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]: + return self._headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + +class SyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + httpx_client: httpx.Client, + ): + super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + logging_config=self._logging, + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, + httpx_client: httpx.AsyncClient, + ): + super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) + self._async_token = async_token + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + async_base_headers=self.async_get_headers, + logging_config=self._logging, + ) + + async def async_get_headers(self) -> typing.Dict[str, str]: + headers = self.get_headers() + if self._async_token is not None: + token = await self._async_token() + headers["Authorization"] = f"Bearer {token}" + return headers diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py new file mode 100644 index 000000000000..a12b2ad03c53 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +from email.utils import parsedate_to_datetime +from typing import Any + +import pydantic + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + + +def parse_rfc2822_datetime(v: Any) -> dt.datetime: + """ + Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") + into a datetime object. If the value is already a datetime, return it as-is. + Falls back to ISO 8601 parsing if RFC 2822 parsing fails. + """ + if isinstance(v, dt.datetime): + return v + if isinstance(v, str): + try: + return parsedate_to_datetime(v) + except Exception: + pass + # Fallback to ISO 8601 parsing + return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) + raise ValueError(f"Expected str or datetime, got {type(v)}") + + +class Rfc2822DateTime(dt.datetime): + """A datetime subclass that parses RFC 2822 date strings. + + On Pydantic V1, uses __get_validators__ for pre-validation. + On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. + """ + + @classmethod + def __get_validators__(cls): # type: ignore[no-untyped-def] + yield parse_rfc2822_datetime + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] + from pydantic_core import core_schema + + return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/file.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/file.py new file mode 100644 index 000000000000..44b0d27c0895 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/file.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = Union[IO[bytes], bytes, str] +File = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[ + Optional[str], + FileContent, + Optional[str], + Mapping[str, str], + ], +] + + +def convert_file_dict_to_httpx_tuples( + d: Dict[str, Union[File, List[File]]], +) -> List[Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples + + +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ + if isinstance(file, tuple): + if len(file) == 2: + filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore + return (filename, content, default_content_type) + elif len(file) == 3: + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) + elif len(file) == 4: + filename, content, file_content_type, headers = cast( # type: ignore + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file + ) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) + else: + raise ValueError(f"Unexpected tuple length: {len(file)}") + return (None, file, default_content_type) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py new file mode 100644 index 000000000000..5440913fd4bc --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict + + +class ForceMultipartDict(Dict[str, Any]): + """ + A dictionary subclass that always evaluates to True in boolean contexts. + + This is used to force multipart/form-data encoding in HTTP requests even when + the dictionary is empty, which would normally evaluate to False. + """ + + def __bool__(self) -> bool: + return True + + +FORCE_MULTIPART = ForceMultipartDict() diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py new file mode 100644 index 000000000000..f0a39ca8243a --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py @@ -0,0 +1,840 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import re +import time +import typing +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx +from .file import File, convert_file_dict_to_httpx_tuples +from .force_multipart import FORCE_MULTIPART +from .jsonable_encoder import jsonable_encoder +from .logging import LogConfig, Logger, create_logger +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict +from .request_options import RequestOptions +from httpx._types import RequestFiles + +INITIAL_RETRY_DELAY_SECONDS = 1.0 +MAX_RETRY_DELAY_SECONDS = 60.0 +JITTER_FACTOR = 0.2 # 20% random jitter + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _add_positive_jitter(delay: float) -> float: + """Add positive jitter (0-20%) to prevent thundering herd.""" + jitter_multiplier = 1 + random() * JITTER_FACTOR + return delay * jitter_multiplier + + +def _add_symmetric_jitter(delay: float) -> float: + """Add symmetric jitter (±10%) for exponential backoff.""" + jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR + return delay * jitter_multiplier + + +def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + Parse the X-RateLimit-Reset header (Unix timestamp in seconds). + Returns seconds to wait, or None if header is missing/invalid. + """ + reset_time_str = response_headers.get("x-ratelimit-reset") + if reset_time_str is None: + return None + + try: + reset_time = int(reset_time_str) + delay = reset_time - time.time() + if delay > 0: + return delay + except (ValueError, TypeError): + pass + + return None + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # 1. Check Retry-After header first + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after > 0: + return min(retry_after, MAX_RETRY_DELAY_SECONDS) + + # 2. Check X-RateLimit-Reset header (with positive jitter) + ratelimit_reset = _parse_x_ratelimit_reset(response.headers) + if ratelimit_reset is not None: + return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS)) + + # 3. Fall back to exponential backoff (with symmetric jitter) + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _retry_timeout_from_retries(retries: int) -> float: + """Determine retry timeout using exponential backoff when no response is available.""" + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _should_retry(response: httpx.Response) -> bool: + retryable_400s = [429, 408, 409] + return response.status_code >= 500 or response.status_code in retryable_400s + + +_SENSITIVE_HEADERS = frozenset( + { + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "cookie", + "set-cookie", + "proxy-authorization", + "proxy-authenticate", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token", + } +) + + +def _redact_headers(headers: typing.Dict[str, str]) -> typing.Dict[str, str]: + return {k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()} + + +def _build_url(base_url: str, path: typing.Optional[str]) -> str: + """ + Build a full URL by joining a base URL with a path. + + This function correctly handles base URLs that contain path prefixes (e.g., tenant-based URLs) + by using string concatenation instead of urllib.parse.urljoin(), which would incorrectly + strip path components when the path starts with '/'. + + Example: + >>> _build_url("https://cloud.example.com/org/tenant/api", "/users") + 'https://cloud.example.com/org/tenant/api/users' + + Args: + base_url: The base URL, which may contain path prefixes. + path: The path to append. Can be None or empty string. + + Returns: + The full URL with base_url and path properly joined. + """ + if not path: + return base_url + return f"{base_url.rstrip('/')}/{path.lstrip('/')}" + + +def _maybe_filter_none_from_multipart_data( + data: typing.Optional[typing.Any], + request_files: typing.Optional[RequestFiles], + force_multipart: typing.Optional[bool], +) -> typing.Optional[typing.Any]: + """ + Filter None values from data body for multipart/form requests. + This prevents httpx from converting None to empty strings in multipart encoding. + Only applies when files are present or force_multipart is True. + """ + if data is not None and isinstance(data, typing.Mapping) and (request_files or force_multipart): + return remove_none_from_dict(data) + return data + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], + omit: typing.Optional[typing.Any], +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + has_additional_body_parameters = bool( + request_options is not None and request_options.get("additional_body_parameters") + ) + + # Only collapse empty dict to None when the body was not explicitly provided + # and there are no additional body parameters. This preserves explicit empty + # bodies (e.g., when an endpoint has a request body type but all fields are optional). + if json_body == {} and json is None and not has_additional_body_parameters: + json_body = None + if data_body == {} and data is None and not has_additional_body_parameters: + data_body = None + + return json_body, data_body + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + time.sleep(_retry_timeout_from_retries(retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + async_base_headers: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]]] = None, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.async_base_headers = async_base_headers + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + async def _get_headers(self) -> typing.Dict[str, str]: + if self.async_base_headers is not None: + return await self.async_base_headers() + return self.base_headers() + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = await self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + await asyncio.sleep(_retry_timeout_from_retries(retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + async with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py new file mode 100644 index 000000000000..00bb1096d2d0 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Dict, Generic, TypeVar + +import httpx + +# Generic to represent the underlying type of the data wrapped by the HTTP response. +T = TypeVar("T") + + +class BaseHttpResponse: + """Minimalist HTTP response wrapper that exposes response headers and status code.""" + + _response: httpx.Response + + def __init__(self, response: httpx.Response): + self._response = response + + @property + def headers(self) -> Dict[str, str]: + return dict(self._response.headers) + + @property + def status_code(self) -> int: + return self._response.status_code + + +class HttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + def close(self) -> None: + self._response.close() + + +class AsyncHttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + async def close(self) -> None: + await self._response.aclose() diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py new file mode 100644 index 000000000000..730e5a3382eb --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from ._api import EventSource, aconnect_sse, connect_sse + from ._exceptions import SSEError + from ._models import ServerSentEvent +_dynamic_imports: typing.Dict[str, str] = { + "EventSource": "._api", + "SSEError": "._exceptions", + "ServerSentEvent": "._models", + "aconnect_sse": "._api", + "connect_sse": "._api", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py new file mode 100644 index 000000000000..f900b3b686de --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +import re +from contextlib import asynccontextmanager, contextmanager +from typing import Any, AsyncGenerator, AsyncIterator, Iterator, cast + +import httpx +from ._decoders import SSEDecoder +from ._exceptions import SSEError +from ._models import ServerSentEvent + + +class EventSource: + def __init__(self, response: httpx.Response) -> None: + self._response = response + + def _check_content_type(self) -> None: + content_type = self._response.headers.get("content-type", "").partition(";")[0] + if "text/event-stream" not in content_type: + raise SSEError( + f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}" + ) + + def _get_charset(self) -> str: + """Extract charset from Content-Type header, fallback to UTF-8.""" + content_type = self._response.headers.get("content-type", "") + + # Parse charset parameter using regex + charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE) + if charset_match: + charset = charset_match.group(1).strip("\"'") + # Validate that it's a known encoding + try: + # Test if the charset is valid by trying to encode/decode + "test".encode(charset).decode(charset) + return charset + except (LookupError, UnicodeError): + # If charset is invalid, fall back to UTF-8 + pass + + # Default to UTF-8 if no charset specified or invalid charset + return "utf-8" + + @property + def response(self) -> httpx.Response: + return self._response + + def iter_sse(self) -> Iterator[ServerSentEvent]: + self._check_content_type() + decoder = SSEDecoder() + charset = self._get_charset() + + buffer = "" + for chunk in self._response.iter_bytes(): + # Decode chunk using detected charset + text_chunk = chunk.decode(charset, errors="replace") + buffer += text_chunk + + # Process complete lines + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) + line = line.rstrip("\r") + sse = decoder.decode(line) + # when we reach a "\n\n" => line = '' + # => decoder will attempt to return an SSE Event + if sse is not None: + yield sse + + # Process any remaining data in buffer + if buffer.strip(): + line = buffer.rstrip("\r") + sse = decoder.decode(line) + if sse is not None: + yield sse + + async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: + self._check_content_type() + decoder = SSEDecoder() + lines = cast(AsyncGenerator[str, None], self._response.aiter_lines()) + try: + async for line in lines: + line = line.rstrip("\n") + sse = decoder.decode(line) + if sse is not None: + yield sse + finally: + await lines.aclose() + + +@contextmanager +def connect_sse(client: httpx.Client, method: str, url: str, **kwargs: Any) -> Iterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) + + +@asynccontextmanager +async def aconnect_sse( + client: httpx.AsyncClient, + method: str, + url: str, + **kwargs: Any, +) -> AsyncIterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + async with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py new file mode 100644 index 000000000000..339b08901381 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import List, Optional + +from ._models import ServerSentEvent + + +class SSEDecoder: + def __init__(self) -> None: + self._event = "" + self._data: List[str] = [] + self._last_event_id = "" + self._retry: Optional[int] = None + + def decode(self, line: str) -> Optional[ServerSentEvent]: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if not self._event and not self._data and not self._last_event_id and self._retry is None: + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = "" + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py new file mode 100644 index 000000000000..81605a8a65ed --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import httpx + + +class SSEError(httpx.TransportError): + pass diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py new file mode 100644 index 000000000000..1af57f8fd0d2 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass(frozen=True) +class ServerSentEvent: + event: str = "message" + data: str = "" + id: str = "" + retry: Optional[int] = None + + def json(self) -> Any: + """Parse the data field as JSON.""" + return json.loads(self.data) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py new file mode 100644 index 000000000000..5b0902ebcde3 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py @@ -0,0 +1,120 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union + +import pydantic +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + encode_by_type, + to_jsonable_with_fallback, +) + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT". + # OMIT values should be excluded from serialized payloads. + if obj is Ellipsis: + return None + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + if value is Ellipsis: + continue + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + if item is Ellipsis: + continue + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) + + +def encode_path_param(obj: Any) -> str: + """Encode a value for use in a URL path segment. + + Ensures proper string conversion for all types, including + booleans which need lowercase 'true'/'false' rather than + Python's 'True'/'False'. + """ + if isinstance(obj, bool): + return "true" if obj else "false" + return str(jsonable_encoder(obj)) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py new file mode 100644 index 000000000000..e5e572458bc8 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py @@ -0,0 +1,107 @@ +# This file was auto-generated by Fern from our API Definition. + +import logging +import typing + +LogLevel = typing.Literal["debug", "info", "warn", "error"] + +_LOG_LEVEL_MAP: typing.Dict[LogLevel, int] = { + "debug": 1, + "info": 2, + "warn": 3, + "error": 4, +} + + +class ILogger(typing.Protocol): + def debug(self, message: str, **kwargs: typing.Any) -> None: ... + def info(self, message: str, **kwargs: typing.Any) -> None: ... + def warn(self, message: str, **kwargs: typing.Any) -> None: ... + def error(self, message: str, **kwargs: typing.Any) -> None: ... + + +class ConsoleLogger: + _logger: logging.Logger + + def __init__(self) -> None: + self._logger = logging.getLogger("fern") + if not self._logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s")) + self._logger.addHandler(handler) + self._logger.setLevel(logging.DEBUG) + + def debug(self, message: str, **kwargs: typing.Any) -> None: + self._logger.debug(message, extra=kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + self._logger.info(message, extra=kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + self._logger.warning(message, extra=kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + self._logger.error(message, extra=kwargs) + + +class LogConfig(typing.TypedDict, total=False): + level: LogLevel + logger: ILogger + silent: bool + + +class Logger: + _level: int + _logger: ILogger + _silent: bool + + def __init__(self, *, level: LogLevel, logger: ILogger, silent: bool) -> None: + self._level = _LOG_LEVEL_MAP[level] + self._logger = logger + self._silent = silent + + def _should_log(self, level: LogLevel) -> bool: + return not self._silent and self._level <= _LOG_LEVEL_MAP[level] + + def is_debug(self) -> bool: + return self._should_log("debug") + + def is_info(self) -> bool: + return self._should_log("info") + + def is_warn(self) -> bool: + return self._should_log("warn") + + def is_error(self) -> bool: + return self._should_log("error") + + def debug(self, message: str, **kwargs: typing.Any) -> None: + if self.is_debug(): + self._logger.debug(message, **kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + if self.is_info(): + self._logger.info(message, **kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + if self.is_warn(): + self._logger.warn(message, **kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + if self.is_error(): + self._logger.error(message, **kwargs) + + +_default_logger: Logger = Logger(level="info", logger=ConsoleLogger(), silent=True) + + +def create_logger(config: typing.Optional[typing.Union[LogConfig, Logger]] = None) -> Logger: + if config is None: + return _default_logger + if isinstance(config, Logger): + return config + return Logger( + level=config.get("level", "info"), + logger=config.get("logger", ConsoleLogger()), + silent=config.get("silent", True), + ) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py new file mode 100644 index 000000000000..4527c6a8adec --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ParsingError(Exception): + """ + Raised when the SDK fails to parse/validate a response from the server. + This typically indicates that the server returned a response whose shape + does not match the expected schema. + """ + + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + cause: Optional[Exception] + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + cause: Optional[Exception] = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + self.cause = cause + super().__init__() + if cause is not None: + self.__cause__ = cause + + def __str__(self) -> str: + cause_str = f", cause: {self.cause}" if self.cause is not None else "" + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py new file mode 100644 index 000000000000..fea3a08d3268 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py @@ -0,0 +1,634 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import inspect +import json +import logging +from collections import defaultdict +from dataclasses import asdict +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +import pydantic +import typing_extensions +from pydantic.fields import FieldInfo as _FieldInfo + +_logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from .http_sse._models import ServerSentEvent + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] + _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] + + def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value + return _datetime_adapter.validate_python(value) + + def parse_date(value: Any) -> dt.date: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value.date() + if isinstance(value, dt.date): + return value + return _date_adapter.validate_python(value) + + # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. + from typing import get_args as get_args # type: ignore[assignment] + from typing import get_origin as get_origin # type: ignore[assignment] + + def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return typing_extensions.get_origin(tp) is typing_extensions.Literal + + def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] + + # Inline encoders_by_type to avoid importing from pydantic.v1.json + import re as _re + from collections import deque as _deque + from decimal import Decimal as _Decimal + from enum import Enum as _Enum + from ipaddress import ( + IPv4Address as _IPv4Address, + ) + from ipaddress import ( + IPv4Interface as _IPv4Interface, + ) + from ipaddress import ( + IPv4Network as _IPv4Network, + ) + from ipaddress import ( + IPv6Address as _IPv6Address, + ) + from ipaddress import ( + IPv6Interface as _IPv6Interface, + ) + from ipaddress import ( + IPv6Network as _IPv6Network, + ) + from pathlib import Path as _Path + from types import GeneratorType as _GeneratorType + from uuid import UUID as _UUID + + from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] + + def _decimal_encoder(dec_value: Any) -> Any: + if dec_value.as_tuple().exponent >= 0: + return int(dec_value) + return float(dec_value) + + encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] + bytes: lambda o: o.decode(), + dt.date: lambda o: o.isoformat(), + dt.datetime: lambda o: o.isoformat(), + dt.time: lambda o: o.isoformat(), + dt.timedelta: lambda td: td.total_seconds(), + _Decimal: _decimal_encoder, + _Enum: lambda o: o.value, + frozenset: list, + _deque: list, + _GeneratorType: list, + _IPv4Address: str, + _IPv4Interface: str, + _IPv4Network: str, + _IPv6Address: str, + _IPv6Interface: str, + _IPv6Network: str, + _Path: str, + _re.Pattern: lambda o: o.pattern, + set: list, + _UUID: str, + } +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] + from pydantic.typing import get_args as get_args # type: ignore[no-redef] + from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] + from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] + from pydantic.typing import is_union as is_union # type: ignore[no-redef] + +from .datetime_utils import serialize_datetime +from .serialization import convert_and_respect_annotation_metadata +from typing_extensions import TypeAlias + +T = TypeVar("T") +Model = TypeVar("Model", bound=pydantic.BaseModel) + + +def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: + """ + Extract the discriminator field name and union variants from a discriminated union type. + Supports Annotated[Union[...], Field(discriminator=...)] patterns. + Returns (discriminator, variants) or (None, None) if not a discriminated union. + """ + origin = typing_extensions.get_origin(type_) + + if origin is typing_extensions.Annotated: + args = typing_extensions.get_args(type_) + if len(args) >= 2: + inner_type = args[0] + # Check annotations for discriminator + discriminator = None + for annotation in args[1:]: + if hasattr(annotation, "discriminator"): + discriminator = getattr(annotation, "discriminator", None) + break + + if discriminator: + inner_origin = typing_extensions.get_origin(inner_type) + if inner_origin is Union: + variants = list(typing_extensions.get_args(inner_type)) + return discriminator, variants + return None, None + + +def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: + """Get the type annotation of a field from a Pydantic model.""" + if IS_PYDANTIC_V2: + fields = getattr(model, "model_fields", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.annotation) + else: + fields = getattr(model, "__fields__", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.outer_type_) + return None + + +def _find_variant_by_discriminator( + variants: List[Type[Any]], + discriminator: str, + discriminator_value: Any, +) -> Optional[Type[Any]]: + """Find the union variant that matches the discriminator value.""" + for variant in variants: + if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): + continue + + disc_annotation = _get_field_annotation(variant, discriminator) + if disc_annotation and is_literal_type(disc_annotation): + literal_args = get_args(disc_annotation) + if literal_args and literal_args[0] == discriminator_value: + return variant + return None + + +def _is_string_type(type_: Type[Any]) -> bool: + """Check if a type is str or Optional[str].""" + if type_ is str: + return True + + origin = typing_extensions.get_origin(type_) + if origin is Union: + args = typing_extensions.get_args(type_) + # Optional[str] = Union[str, None] + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1 and non_none_args[0] is str: + return True + + return False + + +def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: + """ + Parse a ServerSentEvent into the appropriate type. + + Handles two scenarios based on where the discriminator field is located: + + 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. + The union describes the data content, not the SSE envelope. + -> Returns: json.loads(data) parsed into the type + + Example: ChatStreamResponse with discriminator='type' + Input: ServerSentEvent(event="message", data='{"type": "content-delta", ...}', id="") + Output: ContentDeltaEvent (parsed from data, SSE envelope stripped) + + 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. + The union describes the full SSE event structure. + -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string + + Example: JobStreamResponse with discriminator='event' + Input: ServerSentEvent(event="ERROR", data='{"code": "FAILED", ...}', id="123") + Output: JobStreamResponse_Error with data as ErrorData object + + But for variants where data is str (like STATUS_UPDATE): + Input: ServerSentEvent(event="STATUS_UPDATE", data='{"status": "processing"}', id="1") + Output: JobStreamResponse_StatusUpdate with data as string (not parsed) + + Args: + sse: The ServerSentEvent object to parse + type_: The target discriminated union type + + Returns: + The parsed object of type T + + Note: + This function is only available in SDK contexts where http_sse module exists. + """ + sse_event = asdict(sse) + discriminator, variants = _get_discriminator_and_variants(type_) + + if discriminator is None or variants is None: + # Not a discriminated union - parse the data field as JSON + data_value = sse_event.get("data") + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + data_value = sse_event.get("data") + + # Check if discriminator is at the top level (event-level discrimination) + if discriminator in sse_event: + # Case 2: Event-level discrimination + # Find the matching variant to check if 'data' field needs JSON parsing + disc_value = sse_event.get(discriminator) + matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) + + if matching_variant is not None: + # Check what type the variant expects for 'data' + data_type = _get_field_annotation(matching_variant, "data") + if data_type is not None and not _is_string_type(data_type): + # Variant expects non-string data - parse JSON + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + new_object = dict(sse_event) + new_object["data"] = parsed_data + return parse_obj_as(type_, new_object) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + # Either no matching variant, data is string type, or JSON parse failed + return parse_obj_as(type_, sse_event) + + else: + # Case 1: Data-level discrimination + # The discriminator is inside the data payload - extract and parse data only + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + +def parse_obj_as(type_: Type[T], object_: Any) -> T: + # convert_and_respect_annotation_metadata is required for TypedDict aliasing. + # + # For Pydantic models, whether we should pre-dealias depends on how the model encodes aliasing: + # - If the model uses real Pydantic aliases (pydantic.Field(alias=...)), then we must pass wire keys through + # unchanged so Pydantic can validate them. + # - If the model encodes aliasing only via FieldMetadata annotations, then we MUST pre-dealias because Pydantic + # will not recognize those aliases during validation. + if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): + has_pydantic_aliases = False + if IS_PYDANTIC_V2: + for field_name, field_info in getattr(type_, "model_fields", {}).items(): # type: ignore[attr-defined] + alias = getattr(field_info, "alias", None) + if alias is not None and alias != field_name: + has_pydantic_aliases = True + break + else: + for field in getattr(type_, "__fields__", {}).values(): + alias = getattr(field, "alias", None) + name = getattr(field, "name", None) + if alias is not None and name is not None and alias != name: + has_pydantic_aliases = True + break + + dealiased_object = ( + object_ + if has_pydantic_aliases + else convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + ) + else: + dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] + return adapter.validate_python(dealiased_object) + return pydantic.parse_obj_as(type_, dealiased_object) + + +def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] + # Allow fields beginning with `model_` to be used in the model + protected_namespaces=(), + ) + + @pydantic.model_validator(mode="before") # type: ignore[attr-defined] + @classmethod + def _coerce_field_names_to_aliases(cls, data: Any) -> Any: + """ + Accept Python field names in input by rewriting them to their Pydantic aliases, + while avoiding silent collisions when a key could refer to multiple fields. + """ + if not isinstance(data, Mapping): + return data + + fields = getattr(cls, "model_fields", {}) # type: ignore[attr-defined] + name_to_alias: Dict[str, str] = {} + alias_to_name: Dict[str, str] = {} + + for name, field_info in fields.items(): + alias = getattr(field_info, "alias", None) or name + name_to_alias[name] = alias + if alias != name: + alias_to_name[alias] = name + + # Detect ambiguous keys: a key that is an alias for one field and a name for another. + ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) + for key in ambiguous_keys: + if key in data and name_to_alias[key] not in data: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + original_keys = set(data.keys()) + rewritten: Dict[str, Any] = dict(data) + for name, alias in name_to_alias.items(): + if alias != name and name in original_keys and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] + def serialize_model(self) -> Any: # type: ignore[name-defined] + serialized = self.dict() # type: ignore[attr-defined] + data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} + return data + + else: + + class Config: + smart_union = True + json_encoders = {dt.datetime: serialize_datetime} + + @pydantic.root_validator(pre=True) + def _coerce_field_names_to_aliases(cls, values: Any) -> Any: + """ + Pydantic v1 equivalent of _coerce_field_names_to_aliases. + """ + if not isinstance(values, Mapping): + return values + + fields = getattr(cls, "__fields__", {}) + name_to_alias: Dict[str, str] = {} + alias_to_name: Dict[str, str] = {} + + for name, field in fields.items(): + alias = getattr(field, "alias", None) or name + name_to_alias[name] = alias + if alias != name: + alias_to_name[alias] = name + + ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) + for key in ambiguous_keys: + if key in values and name_to_alias[key] not in values: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + original_keys = set(values.keys()) + rewritten: Dict[str, Any] = dict(values) + for name, alias in name_to_alias.items(): + if alias != name and name in original_keys and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + @classmethod + def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + return cls.construct(_fields_set, **dealiased_object) + + @classmethod + def construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + if IS_PYDANTIC_V2: + return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc] + return super().construct(_fields_set, **dealiased_object) + + def json(self, **kwargs: Any) -> str: + kwargs_with_defaults = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: Any) -> Dict[str, Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + dict_dump = deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + return cast( + Dict[str, Any], + convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write"), + ) + + +def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: + converted_list: List[Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg] + pass + + UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc] +else: + UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] + + +def encode_by_type(o: Any) -> Any: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: Type["Model"], **localns: Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = Callable[..., Any] + + +def universal_root_validator( + pre: bool = False, +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) + + return decorator + + +PydanticField = Union[ModelField, _FieldInfo] + + +def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] + return cast(Mapping[str, PydanticField], model.__fields__) + + +def _get_field_default(field: PydanticField) -> Any: + try: + value = field.get_default() # type: ignore[union-attr] + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py new file mode 100644 index 000000000000..3183001d4046 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, List, Optional, Tuple + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: + result = [] + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.extend(traverse_query_dict(v, key)) + elif isinstance(v, list): + for arr_v in v: + if isinstance(arr_v, dict): + result.extend(traverse_query_dict(arr_v, key)) + else: + result.append((key, arr_v)) + else: + result.append((key, v)) + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + elif isinstance(query_value, list): + encoded_values: List[Tuple[str, Any]] = [] + for value in query_value: + if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): + if isinstance(value, pydantic.BaseModel): + obj_dict = value.dict(by_alias=True) + elif isinstance(value, dict): + obj_dict = value + + encoded_values.extend(single_query_encoder(query_key, obj_dict)) + else: + encoded_values.append((query_key, value)) + + return encoded_values + + return [(query_key, query_value)] + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: + if query is None: + return None + + encoded_query = [] + for k, v in query.items(): + encoded_query.extend(single_query_encoder(k, v)) + return encoded_query diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py new file mode 100644 index 000000000000..c2298143f14a --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py new file mode 100644 index 000000000000..1b38804432ba --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout_in_seconds: int. The number of seconds to await an API call before timing out. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + + - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. + """ + + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] + chunk_size: NotRequired[int] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py new file mode 100644 index 000000000000..c36e865cc729 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py @@ -0,0 +1,276 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import pydantic +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + try: + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + except NameError: + # The TypedDict contains a circular reference, so + # we use the __annotations__ attribute directly. + annotations = getattr(expected_type, "__annotations__", {}) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/environment.py b/seed/python-sdk/allof/no-custom-config/src/seed/environment.py new file mode 100644 index 000000000000..c64358bdc3c7 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/environment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum + + +class SeedApiEnvironment(enum.Enum): + DEFAULT = "https://api.example.com" diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/py.typed b/seed/python-sdk/allof/no-custom-config/src/seed/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py b/seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py new file mode 100644 index 000000000000..2e2501a1fd92 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py @@ -0,0 +1,451 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from .core.api_error import ApiError +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .core.http_response import AsyncHttpResponse, HttpResponse +from .core.parse_error import ParsingError +from .core.pydantic_utilities import parse_obj_as +from .core.request_options import RequestOptions +from .types.combined_entity import CombinedEntity +from .types.organization import Organization +from .types.rule_execution_context import RuleExecutionContext +from .types.rule_response import RuleResponse +from .types.rule_type_search_response import RuleTypeSearchResponse +from .types.user_search_response import UserSearchResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSeedApi: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[RuleTypeSearchResponse]: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[RuleTypeSearchResponse] + Paginated list of rule types + """ + _response = self._client_wrapper.httpx_client.request( + "rule-types", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleTypeSearchResponse, + parse_obj_as( + type_=RuleTypeSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[RuleResponse]: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[RuleResponse] + Created rule + """ + _response = self._client_wrapper.httpx_client.request( + "rules", + method="POST", + json={ + "name": name, + "executionContext": execution_context, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleResponse, + parse_obj_as( + type_=RuleResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def list_users( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[UserSearchResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[UserSearchResponse] + Paginated list of users + """ + _response = self._client_wrapper.httpx_client.request( + "users", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UserSearchResponse, + parse_obj_as( + type_=UserSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[CombinedEntity]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CombinedEntity] + An entity with properties from multiple parents + """ + _response = self._client_wrapper.httpx_client.request( + "entities", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CombinedEntity, + parse_obj_as( + type_=CombinedEntity, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get_organization( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[Organization]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Organization] + An organization whose metadata is merged from two parents + """ + _response = self._client_wrapper.httpx_client.request( + "organizations", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Organization, + parse_obj_as( + type_=Organization, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSeedApi: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def search_rule_types( + self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[RuleTypeSearchResponse]: + """ + Parameters + ---------- + query : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[RuleTypeSearchResponse] + Paginated list of rule types + """ + _response = await self._client_wrapper.httpx_client.request( + "rule-types", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleTypeSearchResponse, + parse_obj_as( + type_=RuleTypeSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create_rule( + self, + *, + name: str, + execution_context: RuleExecutionContext, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[RuleResponse]: + """ + Parameters + ---------- + name : str + + execution_context : RuleExecutionContext + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[RuleResponse] + Created rule + """ + _response = await self._client_wrapper.httpx_client.request( + "rules", + method="POST", + json={ + "name": name, + "executionContext": execution_context, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RuleResponse, + parse_obj_as( + type_=RuleResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list_users( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[UserSearchResponse]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[UserSearchResponse] + Paginated list of users + """ + _response = await self._client_wrapper.httpx_client.request( + "users", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UserSearchResponse, + parse_obj_as( + type_=UserSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get_entity( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CombinedEntity]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CombinedEntity] + An entity with properties from multiple parents + """ + _response = await self._client_wrapper.httpx_client.request( + "entities", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CombinedEntity, + parse_obj_as( + type_=CombinedEntity, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get_organization( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[Organization]: + """ + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Organization] + An organization whose metadata is merged from two parents + """ + _response = await self._client_wrapper.httpx_client.request( + "organizations", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Organization, + parse_obj_as( + type_=Organization, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py new file mode 100644 index 000000000000..94ad021213b9 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py @@ -0,0 +1,92 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .audit_info import AuditInfo + from .base_org import BaseOrg + from .base_org_metadata import BaseOrgMetadata + from .combined_entity import CombinedEntity + from .combined_entity_status import CombinedEntityStatus + from .describable import Describable + from .detailed_org import DetailedOrg + from .detailed_org_metadata import DetailedOrgMetadata + from .identifiable import Identifiable + from .organization import Organization + from .paginated_result import PaginatedResult + from .paging_cursors import PagingCursors + from .rule_execution_context import RuleExecutionContext + from .rule_response import RuleResponse + from .rule_response_status import RuleResponseStatus + from .rule_type import RuleType + from .rule_type_search_response import RuleTypeSearchResponse + from .user import User + from .user_search_response import UserSearchResponse +_dynamic_imports: typing.Dict[str, str] = { + "AuditInfo": ".audit_info", + "BaseOrg": ".base_org", + "BaseOrgMetadata": ".base_org_metadata", + "CombinedEntity": ".combined_entity", + "CombinedEntityStatus": ".combined_entity_status", + "Describable": ".describable", + "DetailedOrg": ".detailed_org", + "DetailedOrgMetadata": ".detailed_org_metadata", + "Identifiable": ".identifiable", + "Organization": ".organization", + "PaginatedResult": ".paginated_result", + "PagingCursors": ".paging_cursors", + "RuleExecutionContext": ".rule_execution_context", + "RuleResponse": ".rule_response", + "RuleResponseStatus": ".rule_response_status", + "RuleType": ".rule_type", + "RuleTypeSearchResponse": ".rule_type_search_response", + "User": ".user", + "UserSearchResponse": ".user_search_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AuditInfo", + "BaseOrg", + "BaseOrgMetadata", + "CombinedEntity", + "CombinedEntityStatus", + "Describable", + "DetailedOrg", + "DetailedOrgMetadata", + "Identifiable", + "Organization", + "PaginatedResult", + "PagingCursors", + "RuleExecutionContext", + "RuleResponse", + "RuleResponseStatus", + "RuleType", + "RuleTypeSearchResponse", + "User", + "UserSearchResponse", +] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py new file mode 100644 index 000000000000..49474bbcdeeb --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class AuditInfo(UniversalBaseModel): + """ + Common audit metadata. + """ + + created_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="createdBy"), + pydantic.Field(alias="createdBy", description="The user who created this resource."), + ] = None + created_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="createdDateTime"), + pydantic.Field(alias="createdDateTime", description="When this resource was created."), + ] = None + modified_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="modifiedBy"), + pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), + ] = None + modified_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="modifiedDateTime"), + pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py new file mode 100644 index 000000000000..9d77974841d4 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .base_org_metadata import BaseOrgMetadata + + +class BaseOrg(UniversalBaseModel): + id: str + metadata: typing.Optional[BaseOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py new file mode 100644 index 000000000000..69449ce2a499 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class BaseOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from BaseOrg. + """ + + tier: typing.Optional[str] = pydantic.Field(default=None) + """ + Subscription tier. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py new file mode 100644 index 000000000000..11accd97dd63 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .combined_entity_status import CombinedEntityStatus + + +class CombinedEntity(UniversalBaseModel): + status: CombinedEntityStatus + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Identifiable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py new file mode 100644 index 000000000000..42ac60430cd7 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py new file mode 100644 index 000000000000..2b6cafc913ee --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Describable(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Describable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py new file mode 100644 index 000000000000..744634c71d46 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .detailed_org_metadata import DetailedOrgMetadata + + +class DetailedOrg(UniversalBaseModel): + metadata: typing.Optional[DetailedOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py new file mode 100644 index 000000000000..f0f2029eb21c --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DetailedOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from DetailedOrg. + """ + + domain: typing.Optional[str] = pydantic.Field(default=None) + """ + Custom domain name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py new file mode 100644 index 000000000000..b88d9d997229 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Identifiable(UniversalBaseModel): + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Identifiable. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py new file mode 100644 index 000000000000..7a590d908e14 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .base_org_metadata import BaseOrgMetadata + + +class Organization(UniversalBaseModel): + name: str + id: str + metadata: typing.Optional[BaseOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py new file mode 100644 index 000000000000..56a3858ffbde --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors + + +class PaginatedResult(UniversalBaseModel): + paging: PagingCursors + results: typing.List[typing.Any] = pydantic.Field() + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py new file mode 100644 index 000000000000..f786d5462fea --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PagingCursors(UniversalBaseModel): + next: str = pydantic.Field() + """ + Cursor for the next page of results. + """ + + previous: typing.Optional[str] = pydantic.Field(default=None) + """ + Cursor for the previous page of results. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py new file mode 100644 index 000000000000..1d22ed9cabf8 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py new file mode 100644 index 000000000000..ae6e7a5ad7ca --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.serialization import FieldMetadata +from .audit_info import AuditInfo +from .rule_execution_context import RuleExecutionContext +from .rule_response_status import RuleResponseStatus + + +class RuleResponse(AuditInfo): + id: str + name: str + status: RuleResponseStatus + execution_context: typing_extensions.Annotated[ + typing.Optional[RuleExecutionContext], + FieldMetadata(alias="executionContext"), + pydantic.Field(alias="executionContext"), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py new file mode 100644 index 000000000000..4cbd106638cb --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py new file mode 100644 index 000000000000..a5543e06211c --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RuleType(UniversalBaseModel): + id: str + name: str + description: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py new file mode 100644 index 000000000000..1c4401f421b6 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .rule_type import RuleType + + +class RuleTypeSearchResponse(UniversalBaseModel): + results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + paging: PagingCursors + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/user.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/user.py new file mode 100644 index 000000000000..5421e32870d3 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/user.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class User(UniversalBaseModel): + id: str + email: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py new file mode 100644 index 000000000000..b361b76c9f3d --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .user import User + + +class UserSearchResponse(UniversalBaseModel): + results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + paging: PagingCursors + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/version.py b/seed/python-sdk/allof/no-custom-config/src/seed/version.py new file mode 100644 index 000000000000..33a8861c5d9e --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/src/seed/version.py @@ -0,0 +1,3 @@ +from importlib import metadata + +__version__ = metadata.version("fern_allof") diff --git a/seed/python-sdk/allof/no-custom-config/tests/conftest.py b/seed/python-sdk/allof/no-custom-config/tests/conftest.py new file mode 100644 index 000000000000..9e586af3b2d4 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/conftest.py @@ -0,0 +1,147 @@ +""" +Pytest plugin that manages the WireMock container lifecycle for wire tests. + +This plugin is loaded globally for the test suite and is responsible for +starting and stopping the WireMock container exactly once per test run, +including when running with pytest-xdist over the entire project. + +It lives under tests/ (as tests/conftest.py) and is discovered automatically +by pytest's normal test collection rules. +""" + +import os +import subprocess + +import pytest + +_STARTED: bool = False +_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) +_WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts +_PROJECT_NAME: str = "seed-api" + +# This file lives at tests/conftest.py, so the project root is one level up. +_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +_COMPOSE_FILE = os.path.join(_PROJECT_ROOT, "wiremock", "docker-compose.test.yml") + + +def _get_wiremock_port() -> str: + """Gets the dynamically assigned port for the WireMock container.""" + try: + result = subprocess.run( + ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "port", "wiremock", "8080"], + check=True, + capture_output=True, + text=True, + ) + # Output is like "0.0.0.0:32768" or "[::]:32768" + port = result.stdout.strip().split(":")[-1] + return port + except subprocess.CalledProcessError: + return "8080" # Fallback to default + + +def _start_wiremock() -> None: + """Starts the WireMock container using docker-compose.""" + global _STARTED, _EXTERNAL, _WIREMOCK_URL + if _STARTED: + return + + # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management + existing_url = os.environ.get("WIREMOCK_URL") + if existing_url: + _WIREMOCK_URL = existing_url + _EXTERNAL = True + _STARTED = True + print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") + return + + print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") + try: + subprocess.run( + ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "up", "-d", "--wait"], + check=True, + capture_output=True, + text=True, + ) + _WIREMOCK_PORT = _get_wiremock_port() + _WIREMOCK_URL = f"http://localhost:{_WIREMOCK_PORT}" + os.environ["WIREMOCK_URL"] = _WIREMOCK_URL + print(f"WireMock container is ready at {_WIREMOCK_URL}") + _STARTED = True + except subprocess.CalledProcessError as e: + print(f"Failed to start WireMock: {e.stderr}") + raise + + +def _stop_wiremock() -> None: + """Stops and removes the WireMock container.""" + if _EXTERNAL: + # Container is managed externally; nothing to tear down. + return + + print("\nStopping WireMock container...") + subprocess.run( + ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], + check=False, + capture_output=True, + ) + + +def _is_xdist_worker(config: pytest.Config) -> bool: + """ + Determines if the current process is an xdist worker. + + In pytest-xdist, worker processes have a 'workerinput' attribute + on the config object, while the controller process does not. + """ + return hasattr(config, "workerinput") + + +def _has_httpx_aiohttp() -> bool: + """Check if httpx_aiohttp is importable.""" + try: + import httpx_aiohttp # type: ignore[import-not-found] # noqa: F401 + + return True + except ImportError: + return False + + +def pytest_collection_modifyitems(config: pytest.Config, items: list) -> None: + """Auto-skip @pytest.mark.aiohttp tests when httpx_aiohttp is not installed.""" + if _has_httpx_aiohttp(): + return + skip_aiohttp = pytest.mark.skip(reason="httpx_aiohttp not installed") + for item in items: + if "aiohttp" in item.keywords: + item.add_marker(skip_aiohttp) + + +def pytest_configure(config: pytest.Config) -> None: + """ + Pytest hook that runs during test session setup. + + Starts WireMock container only from the controller process (xdist) + or the single process (non-xdist). This ensures only one container + is started regardless of the number of worker processes. + """ + if _is_xdist_worker(config): + # Workers never manage the container lifecycle. + return + + _start_wiremock() + + +def pytest_unconfigure(config: pytest.Config) -> None: + """ + Pytest hook that runs during test session teardown. + + Stops WireMock container only from the controller process (xdist) + or the single process (non-xdist). This ensures the container is + cleaned up after all workers have finished. + """ + if _is_xdist_worker(config): + # Workers never manage the container lifecycle. + return + + _stop_wiremock() diff --git a/seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py b/seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py new file mode 100644 index 000000000000..ab04ce6393ef --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py @@ -0,0 +1,7 @@ +import pytest + + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True diff --git a/seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py b/seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py new file mode 100644 index 000000000000..73f7f0b99b1d --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py @@ -0,0 +1,116 @@ +import importlib +import sys +import unittest +from unittest import mock + +import httpx +import pytest + + +class TestMakeDefaultAsyncClientWithoutAiohttp(unittest.TestCase): + """Tests for _make_default_async_client when httpx_aiohttp is NOT installed.""" + + def test_returns_httpx_async_client(self) -> None: + """When httpx_aiohttp is not installed, returns plain httpx.AsyncClient.""" + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=True) + self.assertIsInstance(client, httpx.AsyncClient) + self.assertEqual(client.timeout.read, 60) + self.assertTrue(client.follow_redirects) + + def test_follow_redirects_none(self) -> None: + """When follow_redirects is None, omits it from httpx.AsyncClient.""" + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=None) + self.assertIsInstance(client, httpx.AsyncClient) + self.assertFalse(client.follow_redirects) + + def test_explicit_httpx_client_bypasses_autodetect(self) -> None: + """When user passes httpx_client explicitly, _make_default_async_client is not called.""" + + explicit_client = httpx.AsyncClient(timeout=120) + with mock.patch("seed.client._make_default_async_client") as mock_make: + # Replicate the generated conditional: httpx_client if httpx_client is not None else _make_default_async_client(...) + result = explicit_client if explicit_client is not None else mock_make(timeout=60, follow_redirects=True) + mock_make.assert_not_called() + self.assertIs(result, explicit_client) + + +@pytest.mark.aiohttp +class TestMakeDefaultAsyncClientWithAiohttp(unittest.TestCase): + """Tests for _make_default_async_client when httpx_aiohttp IS installed.""" + + def test_returns_aiohttp_client(self) -> None: + """When httpx_aiohttp is installed, returns HttpxAiohttpClient.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=True) + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertEqual(client.timeout.read, 60) + self.assertTrue(client.follow_redirects) + + def test_follow_redirects_none(self) -> None: + """When httpx_aiohttp is installed and follow_redirects is None, omits it.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from seed.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=None) + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertFalse(client.follow_redirects) + + +class TestDefaultClientsWithoutAiohttp(unittest.TestCase): + """Tests for _default_clients.py convenience classes (no aiohttp).""" + + def test_default_async_httpx_client_defaults(self) -> None: + """DefaultAsyncHttpxClient applies SDK defaults.""" + from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAsyncHttpxClient + + client = DefaultAsyncHttpxClient() + self.assertIsInstance(client, httpx.AsyncClient) + self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) + self.assertTrue(client.follow_redirects) + + def test_default_async_httpx_client_overrides(self) -> None: + """DefaultAsyncHttpxClient allows overriding defaults.""" + from seed._default_clients import DefaultAsyncHttpxClient + + client = DefaultAsyncHttpxClient(timeout=30, follow_redirects=False) + self.assertEqual(client.timeout.read, 30) + self.assertFalse(client.follow_redirects) + + def test_default_aiohttp_client_raises_without_package(self) -> None: + """DefaultAioHttpClient raises RuntimeError when httpx_aiohttp not installed.""" + import seed._default_clients + + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + importlib.reload(seed._default_clients) + + with self.assertRaises(RuntimeError) as ctx: + seed._default_clients.DefaultAioHttpClient() + self.assertIn("pip install fern_allof[aiohttp]", str(ctx.exception)) + + importlib.reload(seed._default_clients) + + +@pytest.mark.aiohttp +class TestDefaultClientsWithAiohttp(unittest.TestCase): + """Tests for _default_clients.py when httpx_aiohttp IS installed.""" + + def test_default_aiohttp_client_defaults(self) -> None: + """DefaultAioHttpClient works when httpx_aiohttp is installed.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAioHttpClient + + client = DefaultAioHttpClient() + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) + self.assertTrue(client.follow_redirects) diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py b/seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py new file mode 100644 index 000000000000..f3ea2659bb1c --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py new file mode 100644 index 000000000000..2cf01263529d --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from .circle import CircleParams +from .object_with_defaults import ObjectWithDefaultsParams +from .object_with_optional_field import ObjectWithOptionalFieldParams +from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams +from .square import SquareParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +__all__ = [ + "CircleParams", + "ObjectWithDefaultsParams", + "ObjectWithOptionalFieldParams", + "ShapeParams", + "Shape_CircleParams", + "Shape_SquareParams", + "SquareParams", + "UndiscriminatedShapeParams", +] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py new file mode 100644 index 000000000000..74ecf38c308b --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + +from seed.core.serialization import FieldMetadata + + +class CircleParams(typing_extensions.TypedDict): + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py new file mode 100644 index 000000000000..2aa2c4c52f0c --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py new file mode 100644 index 000000000000..a977b1d2aa1c --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + + +class ObjectWithDefaultsParams(typing_extensions.TypedDict): + """ + Defines properties with default values and validation rules. + """ + + decimal: typing_extensions.NotRequired[float] + string: typing_extensions.NotRequired[str] + required_string: str diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py new file mode 100644 index 000000000000..6b5608bc05b6 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +import uuid + +import typing_extensions +from .color import Color +from .shape import ShapeParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +from seed.core.serialization import FieldMetadata + + +class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): + literal: typing.Literal["lit_one"] + string: typing_extensions.NotRequired[str] + integer: typing_extensions.NotRequired[int] + long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] + double: typing_extensions.NotRequired[float] + bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] + datetime: typing_extensions.NotRequired[dt.datetime] + date: typing_extensions.NotRequired[dt.date] + uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] + base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] + list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] + set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] + map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] + enum: typing_extensions.NotRequired[Color] + union: typing_extensions.NotRequired[ShapeParams] + second_union: typing_extensions.NotRequired[ShapeParams] + undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] + any: typing.Optional[typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py new file mode 100644 index 000000000000..7e70010a251f --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import typing_extensions + +from seed.core.serialization import FieldMetadata + + +class Base(typing_extensions.TypedDict): + id: str + + +class Shape_CircleParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] + + +class Shape_SquareParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] + + +ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py new file mode 100644 index 000000000000..71c7d25fd4ad --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + +from seed.core.serialization import FieldMetadata + + +class SquareParams(typing_extensions.TypedDict): + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py new file mode 100644 index 000000000000..99f12b300d1d --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .circle import CircleParams +from .square import SquareParams + +UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py new file mode 100644 index 000000000000..aa2a8b4e4700 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py @@ -0,0 +1,662 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +from seed.core.http_client import ( + AsyncHttpClient, + HttpClient, + _build_url, + get_request_body, + remove_none_from_dict, +) +from seed.core.request_options import RequestOptions + + +# Stub clients for testing HttpClient and AsyncHttpClient +class _DummySyncClient: + """A minimal stub for httpx.Client that records request arguments.""" + + def __init__(self) -> None: + self.last_request_kwargs: Dict[str, Any] = {} + + def request(self, **kwargs: Any) -> "_DummyResponse": + self.last_request_kwargs = kwargs + return _DummyResponse() + + +class _DummyAsyncClient: + """A minimal stub for httpx.AsyncClient that records request arguments.""" + + def __init__(self) -> None: + self.last_request_kwargs: Dict[str, Any] = {} + + async def request(self, **kwargs: Any) -> "_DummyResponse": + self.last_request_kwargs = kwargs + return _DummyResponse() + + +class _DummyResponse: + """A minimal stub for httpx.Response.""" + + status_code = 200 + headers: Dict[str, str] = {} + + +def get_request_options() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later"}} + + +def get_request_options_with_none() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later", "optional": None}} + + +def test_get_json_request_body() -> None: + json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) + assert json_body == {"hello": "world"} + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"goodbye": "world", "see you": "later"} + assert data_body_extras is None + + +def test_get_files_request_body() -> None: + json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) + assert data_body == {"hello": "world"} + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None + ) + + assert data_body_extras == {"goodbye": "world", "see you": "later"} + assert json_body_extras is None + + +def test_get_none_request_body() -> None: + json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) + assert data_body is None + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"see you": "later"} + assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + """Test that implicit empty bodies (json=None) are collapsed to None.""" + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + +def test_explicit_empty_json_body_is_preserved() -> None: + """Test that explicit empty bodies (json={}) are preserved and sent as {}. + + This is important for endpoints where the request body is required but all + fields are optional. The server expects valid JSON ({}) not an empty body. + """ + unrelated_request_options: RequestOptions = {"max_retries": 3} + + # Explicit json={} should be preserved + json_body, data_body = get_request_body(json={}, data=None, request_options=unrelated_request_options, omit=None) + assert json_body == {} + assert data_body is None + + # Explicit data={} should also be preserved + json_body2, data_body2 = get_request_body(json=None, data={}, request_options=unrelated_request_options, omit=None) + assert json_body2 is None + assert data_body2 == {} + + +def test_json_body_preserves_none_values() -> None: + """Test that JSON bodies preserve None values (they become JSON null).""" + json_body, data_body = get_request_body( + json={"hello": "world", "optional": None}, data=None, request_options=None, omit=None + ) + # JSON bodies should preserve None values + assert json_body == {"hello": "world", "optional": None} + assert data_body is None + + +def test_data_body_preserves_none_values_without_multipart() -> None: + """Test that data bodies preserve None values when not using multipart. + + The filtering of None values happens in HttpClient.request/stream methods, + not in get_request_body. This test verifies get_request_body doesn't filter None. + """ + json_body, data_body = get_request_body( + json=None, data={"hello": "world", "optional": None}, request_options=None, omit=None + ) + # get_request_body should preserve None values in data body + # The filtering happens later in HttpClient.request when multipart is detected + assert data_body == {"hello": "world", "optional": None} + assert json_body is None + + +def test_remove_none_from_dict_filters_none_values() -> None: + """Test that remove_none_from_dict correctly filters out None values.""" + original = {"hello": "world", "optional": None, "another": "value", "also_none": None} + filtered = remove_none_from_dict(original) + assert filtered == {"hello": "world", "another": "value"} + # Original should not be modified + assert original == {"hello": "world", "optional": None, "another": "value", "also_none": None} + + +def test_remove_none_from_dict_empty_dict() -> None: + """Test that remove_none_from_dict handles empty dict.""" + assert remove_none_from_dict({}) == {} + + +def test_remove_none_from_dict_all_none() -> None: + """Test that remove_none_from_dict handles dict with all None values.""" + assert remove_none_from_dict({"a": None, "b": None}) == {} + + +def test_http_client_does_not_pass_empty_params_list() -> None: + """Test that HttpClient passes params=None when params are empty. + + This prevents httpx from stripping existing query parameters from the URL, + which happens when params=[] or params={} is passed. + """ + dummy_client = _DummySyncClient() + http_client = HttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + ) + + # Use a path with query params (e.g., pagination cursor URL) + http_client.request( + path="resource?after=123", + method="GET", + params=None, + request_options=None, + ) + + # We care that httpx receives params=None, not [] or {} + assert "params" in dummy_client.last_request_kwargs + assert dummy_client.last_request_kwargs["params"] is None + + # Verify the query string in the URL is preserved + url = str(dummy_client.last_request_kwargs["url"]) + assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" + + +def test_http_client_passes_encoded_params_when_present() -> None: + """Test that HttpClient passes encoded params when params are provided.""" + dummy_client = _DummySyncClient() + http_client = HttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com/resource", + ) + + http_client.request( + path="", + method="GET", + params={"after": "456"}, + request_options=None, + ) + + params = dummy_client.last_request_kwargs["params"] + # For a simple dict, encode_query should give a single (key, value) tuple + assert params == [("after", "456")] + + +@pytest.mark.asyncio +async def test_async_http_client_does_not_pass_empty_params_list() -> None: + """Test that AsyncHttpClient passes params=None when params are empty. + + This prevents httpx from stripping existing query parameters from the URL, + which happens when params=[] or params={} is passed. + """ + dummy_client = _DummyAsyncClient() + http_client = AsyncHttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + async_base_headers=None, + ) + + # Use a path with query params (e.g., pagination cursor URL) + await http_client.request( + path="resource?after=123", + method="GET", + params=None, + request_options=None, + ) + + # We care that httpx receives params=None, not [] or {} + assert "params" in dummy_client.last_request_kwargs + assert dummy_client.last_request_kwargs["params"] is None + + # Verify the query string in the URL is preserved + url = str(dummy_client.last_request_kwargs["url"]) + assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" + + +@pytest.mark.asyncio +async def test_async_http_client_passes_encoded_params_when_present() -> None: + """Test that AsyncHttpClient passes encoded params when params are provided.""" + dummy_client = _DummyAsyncClient() + http_client = AsyncHttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com/resource", + async_base_headers=None, + ) + + await http_client.request( + path="", + method="GET", + params={"after": "456"}, + request_options=None, + ) + + params = dummy_client.last_request_kwargs["params"] + # For a simple dict, encode_query should give a single (key, value) tuple + assert params == [("after", "456")] + + +def test_basic_url_joining() -> None: + """Test basic URL joining with a simple base URL and path.""" + result = _build_url("https://api.example.com", "/users") + assert result == "https://api.example.com/users" + + +def test_basic_url_joining_trailing_slash() -> None: + """Test basic URL joining with a simple base URL and path.""" + result = _build_url("https://api.example.com/", "/users") + assert result == "https://api.example.com/users" + + +def test_preserves_base_url_path_prefix() -> None: + """Test that path prefixes in base URL are preserved. + + This is the critical bug fix - urllib.parse.urljoin() would strip + the path prefix when the path starts with '/'. + """ + result = _build_url("https://cloud.example.com/org/tenant/api", "/users") + assert result == "https://cloud.example.com/org/tenant/api/users" + + +def test_preserves_base_url_path_prefix_trailing_slash() -> None: + """Test that path prefixes in base URL are preserved.""" + result = _build_url("https://cloud.example.com/org/tenant/api/", "/users") + assert result == "https://cloud.example.com/org/tenant/api/users" + + +# --------------------------------------------------------------------------- +# Connection error retry tests +# --------------------------------------------------------------------------- + + +def _make_sync_http_client(mock_client: Any) -> HttpClient: + return HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + ) + + +def _make_async_http_client(mock_client: Any) -> AsyncHttpClient: + return AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + async_base_headers=None, + ) + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_retries_on_connect_error(mock_sleep: MagicMock) -> None: + """Sync: connection error retries on httpx.ConnectError.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + http_client = _make_sync_http_client(mock_client) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_retries_on_remote_protocol_error(mock_sleep: MagicMock) -> None: + """Sync: connection error retries on httpx.RemoteProtocolError.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.RemoteProtocolError("Remote end closed connection without response"), + _DummyResponse(), + ] + http_client = _make_sync_http_client(mock_client) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_connection_error_exhausts_retries(mock_sleep: MagicMock) -> None: + """Sync: connection error exhausts retries then raises.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = _make_sync_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + assert mock_sleep.call_count == 2 + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_connection_error_respects_max_retries_zero(mock_sleep: MagicMock) -> None: + """Sync: connection error respects max_retries=0.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = _make_sync_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 0}, + ) + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_retries_on_connect_error(mock_sleep: AsyncMock) -> None: + """Async: connection error retries on httpx.ConnectError.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + ) + http_client = _make_async_http_client(mock_client) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_retries_on_remote_protocol_error(mock_sleep: AsyncMock) -> None: + """Async: connection error retries on httpx.RemoteProtocolError.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.RemoteProtocolError("Remote end closed connection without response"), + _DummyResponse(), + ] + ) + http_client = _make_async_http_client(mock_client) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_connection_error_exhausts_retries(mock_sleep: AsyncMock) -> None: + """Async: connection error exhausts retries then raises.""" + mock_client = MagicMock() + mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) + http_client = _make_async_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + await http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + assert mock_sleep.call_count == 2 + + +# --------------------------------------------------------------------------- +# base_max_retries constructor parameter tests +# --------------------------------------------------------------------------- + + +def test_sync_http_client_default_base_max_retries() -> None: + """HttpClient defaults to base_max_retries=2.""" + http_client = HttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + ) + assert http_client.base_max_retries == 2 + + +def test_async_http_client_default_base_max_retries() -> None: + """AsyncHttpClient defaults to base_max_retries=2.""" + http_client = AsyncHttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + ) + assert http_client.base_max_retries == 2 + + +def test_sync_http_client_custom_base_max_retries() -> None: + """HttpClient accepts a custom base_max_retries value.""" + http_client = HttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_max_retries=5, + ) + assert http_client.base_max_retries == 5 + + +def test_async_http_client_custom_base_max_retries() -> None: + """AsyncHttpClient accepts a custom base_max_retries value.""" + http_client = AsyncHttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_max_retries=5, + ) + assert http_client.base_max_retries == 5 + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_base_max_retries_zero_disables_retries(mock_sleep: MagicMock) -> None: + """Sync: base_max_retries=0 disables retries when no request_options override.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, + ) + + with pytest.raises(httpx.ConnectError): + http_client.request(path="/test", method="GET") + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_base_max_retries_zero_disables_retries(mock_sleep: AsyncMock) -> None: + """Async: base_max_retries=0 disables retries when no request_options override.""" + mock_client = MagicMock() + mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, + ) + + with pytest.raises(httpx.ConnectError): + await http_client.request(path="/test", method="GET") + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_request_options_override_base_max_retries(mock_sleep: MagicMock) -> None: + """Sync: request_options max_retries overrides base_max_retries.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("connection failed"), + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, # base says no retries + ) + + # But request_options overrides to allow 2 retries + response = http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + assert response.status_code == 200 + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_request_options_override_base_max_retries(mock_sleep: AsyncMock) -> None: + """Async: request_options max_retries overrides base_max_retries.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("connection failed"), + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + ) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, # base says no retries + ) + + # But request_options overrides to allow 2 retries + response = await http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + assert response.status_code == 200 + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + + +@patch("seed.core.http_client.time.sleep", return_value=None) +def test_sync_base_max_retries_used_as_default(mock_sleep: MagicMock) -> None: + """Sync: base_max_retries is used when request_options has no max_retries.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + _DummyResponse(), + ] + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=3, + ) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + # 1 initial + 3 retries = 4 total attempts + assert mock_client.request.call_count == 4 + + +@pytest.mark.asyncio +@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_base_max_retries_used_as_default(mock_sleep: AsyncMock) -> None: + """Async: base_max_retries is used when request_options has no max_retries.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + _DummyResponse(), + ] + ) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=3, + ) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + # 1 initial + 3 retries = 4 total attempts + assert mock_client.request.call_count == 4 diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py b/seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py new file mode 100644 index 000000000000..ef5fd7094f9b --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.query_encoder import encode_query + + +def test_query_encoding_deep_objects() -> None: + assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")] + assert encode_query({"hello_world": {"hello": "world"}}) == [("hello_world[hello]", "world")] + assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == [ + ("hello_world[hello][world]", "today"), + ("hello_world[test]", "this"), + ("hi", "there"), + ] + + +def test_query_encoding_deep_object_arrays() -> None: + assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [ + ("objects[key]", "hello"), + ("objects[value]", "world"), + ("objects[key]", "foo"), + ("objects[value]", "bar"), + ] + assert encode_query( + {"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]} + ) == [ + ("users[name]", "string"), + ("users[tags]", "string"), + ("users[name]", "string2"), + ("users[tags]", "string2"), + ("users[tags]", "string3"), + ] + + +def test_encode_query_with_none() -> None: + encoded = encode_query(None) + assert encoded is None diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py b/seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py new file mode 100644 index 000000000000..b298db89c4bd --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py @@ -0,0 +1,72 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, List + +from .assets.models import ObjectWithOptionalFieldParams, ShapeParams + +from seed.core.serialization import convert_and_respect_annotation_metadata + +UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} +UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} + + +def test_convert_and_respect_annotation_metadata() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "bool_": True, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} + + +def test_convert_and_respect_annotation_metadata_in_list() -> None: + data: List[ObjectWithOptionalFieldParams] = [ + {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, + ] + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write" + ) + + assert converted == [ + {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, + ] + + +def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "union": UNION_TEST, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + + assert converted == { + "string": "string", + "long": 12345, + "union": UNION_TEST_CONVERTED, + "literal": "lit_one", + "any": "any", + } + + +def test_convert_and_respect_annotation_metadata_in_union() -> None: + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write") + + assert converted == UNION_TEST_CONVERTED + + +def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: + data: Any = {} + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write") + assert converted == data diff --git a/seed/python-sdk/allof/no-custom-config/tests/wire/__init__.py b/seed/python-sdk/allof/no-custom-config/tests/wire/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py b/seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py new file mode 100644 index 000000000000..2782ac72dd88 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py @@ -0,0 +1,78 @@ +""" +Pytest configuration for wire tests. + +This module provides helpers for creating a configured client that talks to +WireMock and for verifying requests in WireMock. + +The WireMock container lifecycle itself is managed by a top-level pytest +plugin (tests/conftest.py) so that the container is started exactly once +per test run, even when using pytest-xdist. +""" + +import inspect +import os +from typing import Any, Dict, Optional + +import httpx + +from seed.client import SeedApi + +# Check once at import time whether the client constructor accepts a headers kwarg. +try: + _CLIENT_SUPPORTS_HEADERS: bool = "headers" in inspect.signature(SeedApi).parameters +except (TypeError, ValueError): + _CLIENT_SUPPORTS_HEADERS = False + + +def _get_wiremock_base_url() -> str: + """Returns the WireMock base URL from the WIREMOCK_URL environment variable.""" + return os.environ.get("WIREMOCK_URL", "http://localhost:8080") + + +def get_client(test_id: str) -> SeedApi: + """ + Creates a configured client instance for wire tests. + + Args: + test_id: Unique identifier for the test, used for request tracking. + + Returns: + A configured client instance with all required auth parameters. + """ + test_headers = {"X-Test-Id": test_id} + base_url = _get_wiremock_base_url() + + if _CLIENT_SUPPORTS_HEADERS: + return SeedApi( + base_url=base_url, + headers=test_headers, + ) + + return SeedApi( + base_url=base_url, + httpx_client=httpx.Client(headers=test_headers), + ) + + +def verify_request_count( + test_id: str, + method: str, + url_path: str, + query_params: Optional[Dict[str, str]], + expected: int, +) -> None: + """Verifies the number of requests made to WireMock filtered by test ID for concurrency safety.""" + wiremock_admin_url = f"{_get_wiremock_base_url()}/__admin" + request_body: Dict[str, Any] = { + "method": method, + "urlPath": url_path, + "headers": {"X-Test-Id": {"equalTo": test_id}}, + } + if query_params: + query_parameters = {k: {"equalTo": v} for k, v in query_params.items()} + request_body["queryParameters"] = query_parameters + response = httpx.post(f"{wiremock_admin_url}/requests/find", json=request_body) + assert response.status_code == 200, "Failed to query WireMock requests" + result = response.json() + requests_found = len(result.get("requests", [])) + assert requests_found == expected, f"Expected {expected} requests, found {requests_found}" diff --git a/seed/python-sdk/allof/no-custom-config/tests/wire/test_.py b/seed/python-sdk/allof/no-custom-config/tests/wire/test_.py new file mode 100644 index 000000000000..2f638bc05b10 --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/tests/wire/test_.py @@ -0,0 +1,44 @@ +from .conftest import get_client, verify_request_count + + +def test__search_rule_types() -> None: + """Test searchRuleTypes endpoint with WireMock""" + test_id = "search_rule_types.0" + client = get_client(test_id) + client.search_rule_types() + verify_request_count(test_id, "GET", "/rule-types", None, 1) + + +def test__create_rule() -> None: + """Test createRule endpoint with WireMock""" + test_id = "create_rule.0" + client = get_client(test_id) + client.create_rule( + name="name", + execution_context="prod", + ) + verify_request_count(test_id, "POST", "/rules", None, 1) + + +def test__list_users() -> None: + """Test listUsers endpoint with WireMock""" + test_id = "list_users.0" + client = get_client(test_id) + client.list_users() + verify_request_count(test_id, "GET", "/users", None, 1) + + +def test__get_entity() -> None: + """Test getEntity endpoint with WireMock""" + test_id = "get_entity.0" + client = get_client(test_id) + client.get_entity() + verify_request_count(test_id, "GET", "/entities", None, 1) + + +def test__get_organization() -> None: + """Test getOrganization endpoint with WireMock""" + test_id = "get_organization.0" + client = get_client(test_id) + client.get_organization() + verify_request_count(test_id, "GET", "/organizations", None, 1) diff --git a/seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml b/seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml new file mode 100644 index 000000000000..58747d54a46b --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml @@ -0,0 +1,14 @@ +services: + wiremock: + image: wiremock/wiremock:3.9.1 + ports: + - "0:8080" # Use dynamic port to avoid conflicts with concurrent tests + volumes: + - ./wiremock-mappings.json:/home/wiremock/mappings/wiremock-mappings.json + command: ["--global-response-templating", "--verbose"] + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/__admin/health"] + interval: 2s + timeout: 5s + retries: 15 + start_period: 5s diff --git a/seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json b/seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json new file mode 100644 index 000000000000..bfeb998eabae --- /dev/null +++ b/seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json @@ -0,0 +1,141 @@ +{ + "mappings": [ + { + "id": "19b28390-baa1-4ec9-87a3-cd8485b994a5", + "name": "Search rule types with paginated results - default", + "request": { + "urlPathTemplate": "/rule-types", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"name\": \"name\",\n \"description\": \"description\"\n }\n ]\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "19b28390-baa1-4ec9-87a3-cd8485b994a5", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + }, + { + "id": "1a934299-52ab-4d86-aa31-9f873790df88", + "name": "Create a rule with constrained execution context - default", + "request": { + "urlPathTemplate": "/rules", + "method": "POST" + }, + "response": { + "status": 200, + "body": "{\n \"createdBy\": \"createdBy\",\n \"createdDateTime\": \"2024-01-15T09:30:00Z\",\n \"modifiedBy\": \"modifiedBy\",\n \"modifiedDateTime\": \"2024-01-15T09:30:00Z\",\n \"id\": \"id\",\n \"name\": \"name\",\n \"status\": \"active\",\n \"executionContext\": \"prod\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "1a934299-52ab-4d86-aa31-9f873790df88", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + } + }, + { + "id": "c4083f9d-e8ad-4944-abe5-163125679505", + "name": "List users with paginated results - default", + "request": { + "urlPathTemplate": "/users", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"email\": \"email\"\n }\n ]\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "c4083f9d-e8ad-4944-abe5-163125679505", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + }, + { + "id": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", + "name": "Get an entity that combines multiple parents - default", + "request": { + "urlPathTemplate": "/entities", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"name\": \"name\",\n \"summary\": \"summary\",\n \"id\": \"id\",\n \"status\": \"active\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + }, + { + "id": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", + "name": "Get an organization with merged object-typed properties - default", + "request": { + "urlPathTemplate": "/organizations", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"metadata\": {\n \"region\": \"region\",\n \"tier\": \"tier\"\n },\n \"id\": \"id\",\n \"name\": \"name\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + }, + "postServeActions": [] + } + ], + "meta": { + "total": 5 + } +} \ No newline at end of file diff --git a/seed/python-sdk/seed.yml b/seed/python-sdk/seed.yml index 74d0902fecbb..d0c53dc50b72 100644 --- a/seed/python-sdk/seed.yml +++ b/seed/python-sdk/seed.yml @@ -454,6 +454,8 @@ scripts: - poetry run pytest -rP -n auto -m aiohttp . allowedFailures: - schemaless-request-body-examples + - allof:no-custom-config + - allof-inline:no-custom-config - any-auth - endpoint-security-auth - examples:legacy-wire-tests diff --git a/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json b/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..885f09fc37f2 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-ruby-sdk-v2", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml b/seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..72178ea4c8f1 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +name: ci + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Install dependencies + run: bundle install + + - name: Run Rubocop + run: bundle exec rubocop + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Install dependencies + run: bundle install + + - name: Run Tests + run: bundle exec rake test + + publish: + name: Publish to RubyGems.org + runs-on: ubuntu-latest + needs: [lint, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + + permissions: + id-token: write + contents: write + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Install dependencies + run: bundle install + + - name: Configure RubyGems credentials + uses: rubygems/configure-rubygems-credentials@v1.0.0 + + - name: Build gem + run: bundle exec rake build + + - name: Push gem to RubyGems + run: gem push pkg/*.gem diff --git a/seed/ruby-sdk-v2/allof-inline/.gitignore b/seed/ruby-sdk-v2/allof-inline/.gitignore new file mode 100644 index 000000000000..c111b331371a --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/.gitignore @@ -0,0 +1 @@ +*.gem diff --git a/seed/ruby-sdk-v2/allof-inline/.rubocop.yml b/seed/ruby-sdk-v2/allof-inline/.rubocop.yml new file mode 100644 index 000000000000..75d8f836f2f0 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/.rubocop.yml @@ -0,0 +1,69 @@ +plugins: + - rubocop-minitest + +AllCops: + TargetRubyVersion: 3.3 + NewCops: enable + +Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/AccessModifierDeclarations: + Enabled: false + +Lint/ConstantDefinitionInBlock: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/BlockLength: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/ParameterLists: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Layout/LineLength: + Enabled: false + +Naming/VariableNumber: + EnforcedStyle: normalcase + +Style/Documentation: + Enabled: false + +Style/Lambda: + EnforcedStyle: literal + +Minitest/MultipleAssertions: + Enabled: false + +Minitest/UselessAssertion: + Enabled: false + +# Dynamic snippets are code samples for documentation, not standalone Ruby files. +Style/FrozenStringLiteralComment: + Exclude: + - "dynamic-snippets/**/*" + +Layout/FirstHashElementIndentation: + Exclude: + - "dynamic-snippets/**/*" diff --git a/seed/ruby-sdk-v2/allof-inline/Gemfile b/seed/ruby-sdk-v2/allof-inline/Gemfile new file mode 100644 index 000000000000..16877f89f300 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/Gemfile @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +group :test, :development do + gem "minitest", "~> 5.16" + gem "minitest-rg" + gem "pry" + gem "rake", "~> 13.0" + gem "rubocop", "~> 1.21" + gem "rubocop-minitest" + gem "webmock" +end + +# Load custom Gemfile configuration if it exists +custom_gemfile = File.join(__dir__, "Gemfile.custom") +eval_gemfile(custom_gemfile) if File.exist?(custom_gemfile) diff --git a/seed/ruby-sdk-v2/allof-inline/Gemfile.custom b/seed/ruby-sdk-v2/allof-inline/Gemfile.custom new file mode 100644 index 000000000000..11bdfaf13f2d --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/Gemfile.custom @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Custom Gemfile configuration file +# This file is automatically loaded by the main Gemfile. You can add custom gems, +# groups, or other Gemfile configurations here. If you do make changes to this file, +# you will need to add it to the .fernignore file to prevent your changes from being +# overwritten by the generator. + +# Example usage: +# group :test, :development do +# gem 'custom-gem', '~> 2.0' +# end + +# Add your custom gem dependencies here \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof-inline/README.md b/seed/ruby-sdk-v2/allof-inline/README.md new file mode 100644 index 000000000000..8c9724fb7644 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/README.md @@ -0,0 +1,165 @@ +# Seed Ruby Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRuby) + +The Seed Ruby library provides convenient access to the Seed APIs from Ruby. + +## Table of Contents + +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Additional Headers](#additional-headers) + - [Additional Query Parameters](#additional-query-parameters) +- [Contributing](#contributing) + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```ruby +require "seed" + +client = Seed::Client.new + +client.create_rule( + name: "name", + execution_context: "prod" +) +``` + +## Environments + +This SDK allows you to configure different environments or custom URLs for API requests. You can either use the predefined environments or specify your own custom URL. +### Environments +```ruby +require "seed" + +seed = Seed::Client.new( + base_url: Seed::Environment::DEFAULT +) +``` + +### Custom URL +```ruby +require "seed" + +client = Seed::Client.new( + base_url: "https://example.com" +) +``` + +## Errors + +Failed API calls will raise errors that can be rescued from granularly. + +```ruby +require "seed" + +client = Seed::Client.new( + base_url: "https://example.com" +) + +begin + result = client.create_rule +rescue Seed::Errors::TimeoutError + puts "API didn't respond before our timeout elapsed" +rescue Seed::Errors::ServiceUnavailableError + puts "API returned status 503, is probably overloaded, try again later" +rescue Seed::Errors::ServerError + puts "API returned some other 5xx status, this is probably a bug" +rescue Seed::Errors::ResponseError => e + puts "API returned an unexpected status other than 5xx: #{e.code} #{e.message}" +rescue Seed::Errors::ApiError => e + puts "Some other error occurred when calling the API: #{e.message}" +end +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries. A request will be retried as long as the request is deemed +retryable and the number of retry attempts has not grown larger than the configured retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` option to configure this behavior. + +```ruby +require "seed" + +client = Seed::Client.new( + base_url: "https://example.com", + max_retries: 3 # Configure max retries (default is 2) +) +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeout` option to configure this behavior. + +```ruby +require "seed" + +response = client.create_rule( + ..., + timeout: 30 # 30 second timeout +) +``` + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `additional_headers` request option. + +```ruby +require "seed" + +response = client.create_rule( + ..., + request_options: { + additional_headers: { + "X-Custom-Header" => "custom-value" + } + } +) +``` + +### Additional Query Parameters + +If you would like to send additional query parameters as part of the request, use the `additional_query_parameters` request option. + +```ruby +require "seed" + +response = client.create_rule( + ..., + request_options: { + additional_query_parameters: { + "custom_param" => "custom-value" + } + } +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ruby-sdk-v2/allof-inline/Rakefile b/seed/ruby-sdk-v2/allof-inline/Rakefile new file mode 100644 index 000000000000..9bdd4a6ce80b --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/Rakefile @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "minitest/test_task" + +Minitest::TestTask.create + +require "rubocop/rake_task" + +RuboCop::RakeTask.new + +task default: %i[test] + +task lint: %i[rubocop] + +# Run only the custom test file +Minitest::TestTask.create(:customtest) do |t| + t.libs << "test" + t.test_globs = ["test/custom.test.rb"] +end diff --git a/seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb b/seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb new file mode 100644 index 000000000000..86d8efd3cd3c --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Custom gemspec configuration file +# This file is automatically loaded by the main gemspec file. The 'spec' variable is available +# in this context from the main gemspec file. You can modify this file to add custom metadata, +# dependencies, or other gemspec configurations. If you do make changes to this file, you will +# need to add it to the .fernignore file to prevent your changes from being overwritten. + +def add_custom_gemspec_data(spec) + # Example custom configurations (uncomment and modify as needed) + + # spec.authors = ["Your name"] + # spec.email = ["your.email@example.com"] + # spec.homepage = "https://github.com/your-org/seed-ruby" + # spec.license = "Your license" +end diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb new file mode 100644 index 000000000000..f8b3f168d71b --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.search_rule_types diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb new file mode 100644 index 000000000000..4b8e2cf5a383 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.search_rule_types(query: "query") diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb new file mode 100644 index 000000000000..96d978fcde17 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb @@ -0,0 +1,8 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.create_rule( + name: "name", + execution_context: "prod" +) diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb new file mode 100644 index 000000000000..96d978fcde17 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb @@ -0,0 +1,8 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.create_rule( + name: "name", + execution_context: "prod" +) diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb new file mode 100644 index 000000000000..99237c7c6437 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.list_users diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb new file mode 100644 index 000000000000..99237c7c6437 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.list_users diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb new file mode 100644 index 000000000000..7769816ca1be --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_entity diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb new file mode 100644 index 000000000000..7769816ca1be --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_entity diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb new file mode 100644 index 000000000000..f47ada1c5233 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_organization diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb new file mode 100644 index 000000000000..f47ada1c5233 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_organization diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed.rb new file mode 100644 index 000000000000..add97d9e68d8 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "json" +require "net/http" +require "securerandom" + +require_relative "seed/internal/json/serializable" +require_relative "seed/internal/types/type" +require_relative "seed/internal/types/utils" +require_relative "seed/internal/types/union" +require_relative "seed/internal/errors/constraint_error" +require_relative "seed/internal/errors/type_error" +require_relative "seed/internal/http/base_request" +require_relative "seed/internal/json/request" +require_relative "seed/internal/http/raw_client" +require_relative "seed/internal/multipart/multipart_encoder" +require_relative "seed/internal/multipart/multipart_form_data_part" +require_relative "seed/internal/multipart/multipart_form_data" +require_relative "seed/internal/multipart/multipart_request" +require_relative "seed/internal/types/model/field" +require_relative "seed/internal/types/model" +require_relative "seed/internal/types/array" +require_relative "seed/internal/types/boolean" +require_relative "seed/internal/types/enum" +require_relative "seed/internal/types/hash" +require_relative "seed/internal/types/unknown" +require_relative "seed/errors/api_error" +require_relative "seed/errors/response_error" +require_relative "seed/errors/client_error" +require_relative "seed/errors/redirect_error" +require_relative "seed/errors/server_error" +require_relative "seed/errors/timeout_error" +require_relative "seed/internal/iterators/item_iterator" +require_relative "seed/internal/iterators/cursor_item_iterator" +require_relative "seed/internal/iterators/offset_item_iterator" +require_relative "seed/internal/iterators/cursor_page_iterator" +require_relative "seed/internal/iterators/offset_page_iterator" +require_relative "seed/types/paging_cursors" +require_relative "seed/types/paginated_result" +require_relative "seed/types/rule_execution_context" +require_relative "seed/types/audit_info" +require_relative "seed/types/rule_type" +require_relative "seed/types/rule_type_search_response" +require_relative "seed/types/user" +require_relative "seed/types/user_search_response" +require_relative "seed/types/rule_response_status" +require_relative "seed/types/rule_response" +require_relative "seed/types/identifiable" +require_relative "seed/types/describable" +require_relative "seed/types/combined_entity_status" +require_relative "seed/types/combined_entity" +require_relative "seed/types/base_org_metadata" +require_relative "seed/types/base_org" +require_relative "seed/types/detailed_org_metadata" +require_relative "seed/types/detailed_org" +require_relative "seed/types/organization_metadata" +require_relative "seed/types/organization" +require_relative "seed/environment" diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb new file mode 100644 index 000000000000..e994144e9573 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Seed + class Environment + DEFAULT = "https://api.example.com" + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb new file mode 100644 index 000000000000..b8ba53889b36 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ApiError < StandardError + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb new file mode 100644 index 000000000000..c3c6033641e2 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ClientError < ResponseError + end + + class UnauthorizedError < ClientError + end + + class ForbiddenError < ClientError + end + + class NotFoundError < ClientError + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb new file mode 100644 index 000000000000..f663c01e7615 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Seed + module Errors + class RedirectError < ResponseError + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb new file mode 100644 index 000000000000..beb4a1baf959 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ResponseError < ApiError + attr_reader :code + + def initialize(msg, code:) + @code = code + super(msg) + end + + def inspect + "#<#{self.class.name} @code=#{code} @body=#{message}>" + end + + # Returns the most appropriate error class for the given code. + # + # @return [Class] + def self.subclass_for_code(code) + case code + when 300..399 + RedirectError + when 401 + UnauthorizedError + when 403 + ForbiddenError + when 404 + NotFoundError + when 400..499 + ClientError + when 503 + ServiceUnavailableError + when 500..599 + ServerError + else + ResponseError + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb new file mode 100644 index 000000000000..1838027cdeab --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ServerError < ResponseError + end + + class ServiceUnavailableError < ApiError + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb new file mode 100644 index 000000000000..ec3a24bb7e96 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Seed + module Errors + class TimeoutError < ApiError + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb new file mode 100644 index 000000000000..e2f0bd66ac37 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Errors + class ConstraintError < StandardError + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb new file mode 100644 index 000000000000..6aec80f59f05 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Errors + class TypeError < StandardError + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb new file mode 100644 index 000000000000..d35df463e5b0 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Http + # @api private + class BaseRequest + attr_reader :base_url, :path, :method, :headers, :query, :request_options + + # @param base_url [String] The base URL for the request + # @param path [String] The path for the request + # @param method [String] The HTTP method for the request (:get, :post, etc.) + # @param headers [Hash] Additional headers for the request (optional) + # @param query [Hash] Query parameters for the request (optional) + # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] + def initialize(base_url:, path:, method:, headers: {}, query: {}, request_options: {}) + @base_url = base_url + @path = path + @method = method + @headers = headers + @query = query + @request_options = request_options + end + + # @return [Hash] The query parameters merged with additional query parameters from request options. + def encode_query + additional_query = @request_options&.dig(:additional_query_parameters) || @request_options&.dig("additional_query_parameters") || {} + @query.merge(additional_query) + end + + # Child classes should implement: + # - encode_headers: Returns the encoded HTTP request headers. + # - encode_body: Returns the encoded HTTP request body. + + private + + # Merges additional_headers from request_options into sdk_headers, filtering out + # any keys that collide with SDK-set or client-protected headers (case-insensitive). + # @param sdk_headers [Hash] Headers set by the SDK for this request type. + # @param protected_keys [Array] Additional header keys that must not be overridden. + # @return [Hash] The merged headers. + def merge_additional_headers(sdk_headers, protected_keys: []) + additional_headers = @request_options&.dig(:additional_headers) || @request_options&.dig("additional_headers") || {} + all_protected = (sdk_headers.keys + protected_keys).to_set { |k| k.to_s.downcase } + filtered = additional_headers.reject { |key, _| all_protected.include?(key.to_s.downcase) } + sdk_headers.merge(filtered) + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb new file mode 100644 index 000000000000..482ab9517714 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Http + # @api private + class RawClient + # Default HTTP status codes that trigger a retry + RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504, 521, 522, 524].freeze + # Initial delay between retries in seconds + INITIAL_RETRY_DELAY = 0.5 + # Maximum delay between retries in seconds + MAX_RETRY_DELAY = 60.0 + # Jitter factor for randomizing retry delays (20%) + JITTER_FACTOR = 0.2 + + # @return [String] The base URL for requests + attr_reader :base_url + + # @param base_url [String] The base url for the request. + # @param max_retries [Integer] The number of times to retry a failed request, defaults to 2. + # @param timeout [Float] The timeout for the request, defaults to 60.0 seconds. + # @param headers [Hash] The headers for the request. + def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) + @base_url = base_url + @max_retries = max_retries + @timeout = timeout + @default_headers = { + "X-Fern-Language": "Ruby", + "X-Fern-SDK-Name": "seed", + "X-Fern-SDK-Version": "0.0.1" + }.merge(headers) + end + + # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. + # @return [HTTP::Response] The HTTP response. + def send(request) + url = build_url(request) + attempt = 0 + response = nil + + loop do + http_request = build_http_request( + url:, + method: request.method, + headers: request.encode_headers(protected_keys: @default_headers.keys), + body: request.encode_body + ) + + conn = connect(url) + conn.open_timeout = @timeout + conn.read_timeout = @timeout + conn.write_timeout = @timeout + conn.continue_timeout = @timeout + + response = conn.request(http_request) + + break unless should_retry?(response, attempt) + + delay = retry_delay(response, attempt) + sleep(delay) + attempt += 1 + end + + response + end + + # Determines if a request should be retried based on the response status code. + # @param response [Net::HTTPResponse] The HTTP response. + # @param attempt [Integer] The current retry attempt (0-indexed). + # @return [Boolean] Whether the request should be retried. + def should_retry?(response, attempt) + return false if attempt >= @max_retries + + status = response.code.to_i + RETRYABLE_STATUSES.include?(status) + end + + # Calculates the delay before the next retry attempt using exponential backoff with jitter. + # Respects Retry-After header if present. + # @param response [Net::HTTPResponse] The HTTP response. + # @param attempt [Integer] The current retry attempt (0-indexed). + # @return [Float] The delay in seconds before the next retry. + def retry_delay(response, attempt) + # Check for Retry-After header (can be seconds or HTTP date) + retry_after = response["Retry-After"] + if retry_after + delay = parse_retry_after(retry_after) + return [delay, MAX_RETRY_DELAY].min if delay&.positive? + end + + # Exponential backoff with jitter: base_delay * 2^attempt + base_delay = INITIAL_RETRY_DELAY * (2**attempt) + add_jitter([base_delay, MAX_RETRY_DELAY].min) + end + + # Parses the Retry-After header value. + # @param value [String] The Retry-After header value (seconds or HTTP date). + # @return [Float, nil] The delay in seconds, or nil if parsing fails. + def parse_retry_after(value) + # Try parsing as integer (seconds) + seconds = Integer(value, exception: false) + return seconds.to_f if seconds + + # Try parsing as HTTP date + begin + retry_time = Time.httpdate(value) + delay = retry_time - Time.now + delay.positive? ? delay : nil + rescue ArgumentError + nil + end + end + + # Adds random jitter to a delay value. + # @param delay [Float] The base delay in seconds. + # @return [Float] The delay with jitter applied. + def add_jitter(delay) + jitter = delay * JITTER_FACTOR * (rand - 0.5) * 2 + [delay + jitter, 0].max + end + + LOCALHOST_HOSTS = %w[localhost 127.0.0.1 [::1]].freeze + + # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. + # @return [URI::Generic] The URL. + def build_url(request) + encoded_query = request.encode_query + + # If the path is already an absolute URL, use it directly + if request.path.start_with?("http://", "https://") + url = request.path + url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? + parsed = URI.parse(url) + validate_https!(parsed) + return parsed + end + + path = request.path.start_with?("/") ? request.path[1..] : request.path + base = request.base_url || @base_url + url = "#{base.chomp("/")}/#{path}" + url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? + parsed = URI.parse(url) + validate_https!(parsed) + parsed + end + + # Raises if the URL uses http:// for a non-localhost host, which would + # send authentication credentials in plaintext. + # @param url [URI::Generic] The parsed URL. + def validate_https!(url) + return if url.scheme != "http" + return if LOCALHOST_HOSTS.include?(url.host) + + raise ArgumentError, "Refusing to send request to non-HTTPS URL: #{url}. " \ + "HTTP is only allowed for localhost. Use HTTPS or pass a localhost URL." + end + + # @param url [URI::Generic] The url to the resource. + # @param method [String] The HTTP method to use. + # @param headers [Hash] The headers for the request. + # @param body [String, nil] The body for the request. + # @return [HTTP::Request] The HTTP request. + def build_http_request(url:, method:, headers: {}, body: nil) + request = Net::HTTPGenericRequest.new( + method, + !body.nil?, + method != "HEAD", + url + ) + + request_headers = @default_headers.merge(headers) + request_headers.each { |name, value| request[name] = value } + request.body = body if body + + request + end + + # @param query [Hash] The query for the request. + # @return [String, nil] The encoded query. + def encode_query(query) + query.to_h.empty? ? nil : URI.encode_www_form(query) + end + + # @param url [URI::Generic] The url to connect to. + # @return [Net::HTTP] The HTTP connection. + def connect(url) + is_https = (url.scheme == "https") + + port = if url.port + url.port + elsif is_https + Net::HTTP.https_default_port + else + Net::HTTP.http_default_port + end + + http = Net::HTTP.new(url.host, port) + http.use_ssl = is_https + http.verify_mode = OpenSSL::SSL::VERIFY_PEER if is_https + # NOTE: We handle retries at the application level with HTTP status code awareness, + # so we set max_retries to 0 to disable Net::HTTP's built-in network-level retries. + http.max_retries = 0 + http + end + + # @return [String] + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} @base_url=#{@base_url.inspect}>" + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb new file mode 100644 index 000000000000..ab627ffc7025 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Seed + module Internal + class CursorItemIterator < ItemIterator + # Instantiates a CursorItemIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields individual items from it. + # + # @param initial_cursor [String] The initial cursor to use when iterating, if any. + # @param cursor_field [Symbol] The field in API responses to extract the next cursor from. + # @param item_field [Symbol] The field in API responses to extract the items to iterate over. + # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. + # @return [Seed::Internal::CursorItemIterator] + def initialize(initial_cursor:, cursor_field:, item_field:, &) + super() + @item_field = item_field + @page_iterator = CursorPageIterator.new(initial_cursor:, cursor_field:, &) + @page = nil + end + + # Returns the CursorPageIterator mediating access to the underlying API. + # + # @return [Seed::Internal::CursorPageIterator] + def pages + @page_iterator + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb new file mode 100644 index 000000000000..f479a749fef9 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Seed + module Internal + class CursorPageIterator + include Enumerable + + # Instantiates a CursorPageIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields pages of items. + # + # @param initial_cursor [String] The initial cursor to use when iterating, if any. + # @param cursor_field [Symbol] The name of the field in API responses to extract the next cursor from. + # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. + # @return [Seed::Internal::CursorPageIterator] + def initialize(initial_cursor:, cursor_field:, &block) + @need_initial_load = initial_cursor.nil? + @cursor = initial_cursor + @cursor_field = cursor_field + @get_next_page = block + end + + # Iterates over each page returned by the API. + # + # @param block [Proc] The block which each retrieved page is yielded to. + # @return [NilClass] + def each(&block) + while (page = next_page) + block.call(page) + end + end + + # Whether another page will be available from the API. + # + # @return [Boolean] + def next? + @need_initial_load || !@cursor.nil? + end + + # Retrieves the next page from the API. + # + # @return [Boolean] + def next_page + return if !@need_initial_load && @cursor.nil? + + @need_initial_load = false + fetched_page = @get_next_page.call(@cursor) + @cursor = fetched_page.send(@cursor_field) + fetched_page + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb new file mode 100644 index 000000000000..1284fb0fd367 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Seed + module Internal + class ItemIterator + include Enumerable + + # Iterates over each item returned by the API. + # + # @param block [Proc] The block which each retrieved item is yielded to. + # @return [NilClass] + def each(&block) + while (item = next_element) + block.call(item) + end + end + + # Whether another item will be available from the API. + # + # @return [Boolean] + def next? + load_next_page if @page.nil? + return false if @page.nil? + + return true if any_items_in_cached_page? + + load_next_page + any_items_in_cached_page? + end + + # Retrieves the next item from the API. + def next_element + item = next_item_from_cached_page + return item if item + + load_next_page + next_item_from_cached_page + end + + private + + def next_item_from_cached_page + return unless @page + + @page.send(@item_field).shift + end + + def any_items_in_cached_page? + return false unless @page + + !@page.send(@item_field).empty? + end + + def load_next_page + @page = @page_iterator.next_page + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb new file mode 100644 index 000000000000..f8840246686d --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Seed + module Internal + class OffsetItemIterator < ItemIterator + # Instantiates an OffsetItemIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields the individual items from it. + # + # @param initial_page [Integer] The initial page or offset to start from when iterating. + # @param item_field [Symbol] The name of the field in API responses to extract the items to iterate over. + # @param has_next_field [Symbol] The name of the field in API responses containing a boolean of whether another page exists. + # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) + # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. + # + # @return [Seed::Internal::OffsetItemIterator] + def initialize(initial_page:, item_field:, has_next_field:, step:, &) + super() + @item_field = item_field + @page_iterator = OffsetPageIterator.new(initial_page:, item_field:, has_next_field:, step:, &) + @page = nil + end + + # Returns the OffsetPageIterator that is mediating access to the underlying API. + # + # @return [Seed::Internal::OffsetPageIterator] + def pages + @page_iterator + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb new file mode 100644 index 000000000000..051b65c5774c --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Seed + module Internal + class OffsetPageIterator + include Enumerable + + # Instantiates an OffsetPageIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields pages of items from it. + # + # @param initial_page [Integer] The initial page to use when iterating, if any. + # @param item_field [Symbol] The field to pull the list of items to iterate over. + # @param has_next_field [Symbol] The field to pull the boolean of whether a next page exists from, if any. + # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) + # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. + # @return [Seed::Internal::OffsetPageIterator] + def initialize(initial_page:, item_field:, has_next_field:, step:, &block) + @page_number = initial_page || (step ? 0 : 1) + @item_field = item_field + @has_next_field = has_next_field + @step = step + @get_next_page = block + + # A cache of whether the API has another page, if it gives us that information... + @next_page = nil + # ...or the actual next page, preloaded, if it doesn't. + @has_next_page = nil + end + + # Iterates over each page returned by the API. + # + # @param block [Proc] The block which each retrieved page is yielded to. + # @return [NilClass] + def each(&block) + while (page = next_page) + block.call(page) + end + end + + # Whether another page will be available from the API. + # + # @return [Boolean] + def next? + return @has_next_page unless @has_next_page.nil? + return true if @next_page + + fetched_page = @get_next_page.call(@page_number) + fetched_page_items = fetched_page&.send(@item_field) + if fetched_page_items.nil? || fetched_page_items.empty? + @has_next_page = false + else + @next_page = fetched_page + true + end + end + + # Returns the next page from the API. + def next_page + return nil if @page_number.nil? + + if @next_page + this_page = @next_page + @next_page = nil + else + this_page = @get_next_page.call(@page_number) + end + + @has_next_page = this_page&.send(@has_next_field) if @has_next_field + + items = this_page.send(@item_field) + if items.nil? || items.empty? + @page_number = nil + return nil + elsif @step + @page_number += items.length + else + @page_number += 1 + end + + this_page + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb new file mode 100644 index 000000000000..667ceae8ac59 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Seed + module Internal + module JSON + # @api private + class Request < Seed::Internal::Http::BaseRequest + attr_reader :body + + # @param base_url [String] The base URL for the request + # @param path [String] The path for the request + # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) + # @param headers [Hash] Additional headers for the request (optional) + # @param query [Hash] Query parameters for the request (optional) + # @param body [Object, nil] The JSON request body (optional) + # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] + def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) + super(base_url:, path:, method:, headers:, query:, request_options:) + + @body = body + end + + # @return [Hash] The encoded HTTP request headers. + # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) + # that must not be overridden by additional_headers from request_options. + def encode_headers(protected_keys: []) + sdk_headers = { + "Content-Type" => "application/json", + "Accept" => "application/json" + }.merge(@headers) + merge_additional_headers(sdk_headers, protected_keys:) + end + + # @return [String, nil] The encoded HTTP request body. + def encode_body + @body.nil? ? nil : ::JSON.generate(@body) + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb new file mode 100644 index 000000000000..f80a15fb962c --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Seed + module Internal + module JSON + module Serializable + # Loads data from JSON into its deserialized form + # + # @param str [String] Raw JSON to load into an object + # @return [Object] + def load(str) + raise NotImplementedError + end + + # Dumps data from its deserialized form into JSON + # + # @param value [Object] The deserialized value + # @return [String] + def dump(value) + raise NotImplementedError + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb new file mode 100644 index 000000000000..307ad7436a57 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Multipart + # Encodes parameters into a `multipart/form-data` payload as described by RFC + # 2388: + # + # https://tools.ietf.org/html/rfc2388 + # + # This is most useful for transferring file-like objects. + # + # Parameters should be added with `#encode`. When ready, use `#body` to get + # the encoded result and `#content_type` to get the value that should be + # placed in the `Content-Type` header of a subsequent request (which includes + # a boundary value). + # + # This abstraction is heavily inspired by Stripe's multipart/form-data implementation, + # which can be found here: + # + # https://github.com/stripe/stripe-ruby/blob/ca00b676f04ac421cf5cb5ff0325f243651677b6/lib/stripe/multipart_encoder.rb#L18 + # + # @api private + class Encoder + CONTENT_TYPE = "multipart/form-data" + CRLF = "\r\n" + + attr_reader :boundary, :body + + def initialize + # Chose the same number of random bytes that Go uses in its standard + # library implementation. Easily enough entropy to ensure that it won't + # be present in a file we're sending. + @boundary = SecureRandom.hex(30) + + @body = String.new + @closed = false + @first_field = true + end + + # Gets the content type string including the boundary. + # + # @return [String] The content type with boundary + def content_type + "#{CONTENT_TYPE}; boundary=#{@boundary}" + end + + # Encode the given FormData object into a multipart/form-data payload. + # + # @param form_data [FormData] The form data to encode + # @return [String] The encoded body. + def encode(form_data) + return "" if form_data.parts.empty? + + form_data.parts.each do |part| + write_part(part) + end + close + + @body + end + + # Writes a FormDataPart to the encoder. + # + # @param part [FormDataPart] The part to write + # @return [nil] + def write_part(part) + raise "Cannot write to closed encoder" if @closed + + write_field( + name: part.name, + data: part.contents, + filename: part.filename, + headers: part.headers + ) + + nil + end + + # Writes a field to the encoder. + # + # @param name [String] The field name + # @param data [String] The field data + # @param filename [String, nil] Optional filename + # @param headers [Hash, nil] Optional additional headers + # @return [nil] + def write_field(name:, data:, filename: nil, headers: nil) + raise "Cannot write to closed encoder" if @closed + + if @first_field + @first_field = false + else + @body << CRLF + end + + @body << "--#{@boundary}#{CRLF}" + @body << %(Content-Disposition: form-data; name="#{escape(name.to_s)}") + @body << %(; filename="#{escape(filename)}") if filename + @body << CRLF + + if headers + headers.each do |key, value| + @body << "#{key}: #{value}#{CRLF}" + end + elsif filename + # Default content type for files. + @body << "Content-Type: application/octet-stream#{CRLF}" + end + + @body << CRLF + @body << data.to_s + + nil + end + + # Finalizes the encoder by writing the final boundary. + # + # @return [nil] + def close + raise "Encoder already closed" if @closed + + @body << CRLF + @body << "--#{@boundary}--" + @closed = true + + nil + end + + private + + # Escapes quotes for use in header values and replaces line breaks with spaces. + # + # @param str [String] The string to escape + # @return [String] The escaped string + def escape(str) + str.to_s.gsub('"', "%22").tr("\n", " ").tr("\r", " ") + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb new file mode 100644 index 000000000000..5be1bb25341f --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Multipart + # @api private + class FormData + # @return [Array] The parts in this multipart form data. + attr_reader :parts + + # @return [Encoder] The encoder for this multipart form data. + private attr_reader :encoder + + def initialize + @encoder = Encoder.new + @parts = [] + end + + # Adds a new part to the multipart form data. + # + # @param name [String] The name of the form field + # @param value [String, Integer, Float, Boolean, #read] The value of the field + # @param content_type [String, nil] Optional content type + # @return [self] Returns self for chaining + def add(name:, value:, content_type: nil) + headers = content_type ? { "Content-Type" => content_type } : nil + add_part(FormDataPart.new(name:, value:, headers:)) + end + + # Adds a file to the multipart form data. + # + # @param name [String] The name of the form field + # @param file [#read] The file or readable object + # @param filename [String, nil] Optional filename (defaults to basename of path for File objects) + # @param content_type [String, nil] Optional content type (e.g. "image/png") + # @return [self] Returns self for chaining + def add_file(name:, file:, filename: nil, content_type: nil) + headers = content_type ? { "Content-Type" => content_type } : nil + filename ||= filename_for(file) + add_part(FormDataPart.new(name:, value: file, filename:, headers:)) + end + + # Adds a pre-created part to the multipart form data. + # + # @param part [FormDataPart] The part to add + # @return [self] Returns self for chaining + def add_part(part) + @parts << part + self + end + + # Gets the content type string including the boundary. + # + # @return [String] The content type with boundary. + def content_type + @encoder.content_type + end + + # Encode the multipart form data into a multipart/form-data payload. + # + # @return [String] The encoded body. + def encode + @encoder.encode(self) + end + + private + + def filename_for(file) + if file.is_a?(::File) || file.respond_to?(:path) + ::File.basename(file.path) + elsif file.respond_to?(:name) + file.name + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb new file mode 100644 index 000000000000..de45416ee087 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "securerandom" + +module Seed + module Internal + module Multipart + # @api private + class FormDataPart + attr_reader :name, :contents, :filename, :headers + + # @param name [String] The name of the form field + # @param value [String, Integer, Float, Boolean, File, #read] The value of the field + # @param filename [String, nil] Optional filename for file uploads + # @param headers [Hash, nil] Optional additional headers + def initialize(name:, value:, filename: nil, headers: nil) + @name = name + @contents = convert_to_content(value) + @filename = filename + @headers = headers + end + + # Converts the part to a hash suitable for serialization. + # + # @return [Hash] A hash representation of the part + def to_hash + result = { + name: @name, + contents: @contents + } + result[:filename] = @filename if @filename + result[:headers] = @headers if @headers + result + end + + private + + # Converts various types of values to a content representation + # @param value [String, Integer, Float, Boolean, #read] The value to convert + # @return [String] The string representation of the value + def convert_to_content(value) + if value.respond_to?(:read) + value.read + else + value.to_s + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb new file mode 100644 index 000000000000..9fa80cee01ab --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Multipart + # @api private + class Request < Seed::Internal::Http::BaseRequest + attr_reader :body + + # @param base_url [String] The base URL for the request + # @param path [String] The path for the request + # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) + # @param headers [Hash] Additional headers for the request (optional) + # @param query [Hash] Query parameters for the request (optional) + # @param body [MultipartFormData, nil] The multipart form data for the request (optional) + # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] + def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) + super(base_url:, path:, method:, headers:, query:, request_options:) + + @body = body + end + + # @return [Hash] The encoded HTTP request headers. + # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) + # that must not be overridden by additional_headers from request_options. + def encode_headers(protected_keys: []) + sdk_headers = { + "Content-Type" => @body.content_type + }.merge(@headers) + merge_additional_headers(sdk_headers, protected_keys:) + end + + # @return [String, nil] The encoded HTTP request body. + def encode_body + @body&.encode + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb new file mode 100644 index 000000000000..f3c7c1bd9549 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # An array of a specific type + class Array + include Seed::Internal::Types::Type + + attr_reader :type + + class << self + # Instantiates a new `Array` of a given type + # + # @param type [Object] The member type of this array + # + # @return [Seed::Internal::Types::Array] + def [](type) + new(type) + end + end + + # @api private + def initialize(type) + @type = type + end + + # Coerces a value into this array + # + # @param value [Object] + # @option strict [Boolean] + # @return [::Array] + def coerce(value, strict: strict?) + unless value.is_a?(::Array) + raise Errors::TypeError, "cannot coerce `#{value.class}` to Array<#{type}>" if strict + + return value + end + + value.map do |element| + Utils.coerce(type, element, strict: strict) + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb new file mode 100644 index 000000000000..d4e3277e566f --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + module Boolean + extend Seed::Internal::Types::Union + + member TrueClass + member FalseClass + + # Overrides the base coercion method for enums to allow integer and string values to become booleans + # + # @param value [Object] + # @option strict [Boolean] + # @return [Object] + def self.coerce(value, strict: strict?) + case value + when TrueClass, FalseClass + return value + when Integer + return value == 1 + when String + return %w[1 true].include?(value) + end + + raise Errors::TypeError, "cannot coerce `#{value.class}` to Boolean" if strict + + value + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb new file mode 100644 index 000000000000..72e45e4c1f27 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # Module for defining enums + module Enum + include Type + + # @api private + # + # @return [Array] + def values + @values ||= constants.map { |c| const_get(c) } + end + + # @api private + def finalize! + values + end + + # @api private + def strict? + @strict ||= false + end + + # @api private + def strict! + @strict = true + end + + def coerce(value, strict: strict?) + coerced_value = Utils.coerce(Symbol, value) + + return coerced_value if values.include?(coerced_value) + + raise Errors::TypeError, "`#{value}` not in enum #{self}" if strict + + value + end + + # Parse JSON string and coerce to the enum value + # + # @param str [String] JSON string to parse + # @return [String] The enum value + def load(str) + coerce(::JSON.parse(str)) + end + + def inspect + "#{name}[#{values.join(", ")}]" + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb new file mode 100644 index 000000000000..d8bffa63ac11 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + class Hash + include Type + + attr_reader :key_type, :value_type + + class << self + def [](key_type, value_type) + new(key_type, value_type) + end + end + + def initialize(key_type, value_type) + @key_type = key_type + @value_type = value_type + end + + def coerce(value, strict: strict?) + unless value.is_a?(::Hash) + raise Errors::TypeError, "not hash" if strict + + return value + end + + value.to_h do |k, v| + [Utils.coerce(key_type, k, strict: strict), Utils.coerce(value_type, v, strict: strict)] + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb new file mode 100644 index 000000000000..8caca14ff7ea --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # @abstract + # + # An abstract model that all data objects will inherit from + class Model + include Type + + class << self + # The defined fields for this model + # + # @api private + # + # @return [Hash] + def fields + @fields ||= if self < Seed::Internal::Types::Model + superclass.fields.dup + else + {} + end + end + + # Any extra fields that have been created from instantiation + # + # @api private + # + # @return [Hash] + def extra_fields + @extra_fields ||= {} + end + + # Define a new field on this model + # + # @param name [Symbol] The name of the field + # @param type [Class] Type of the field + # @option optional [Boolean] If it is an optional field + # @option nullable [Boolean] If it is a nullable field + # @option api_name [Symbol, String] Name in the API of this field. When serializing/deserializing, will use + # this field name + # @return [void] + def field(name, type, optional: false, nullable: false, api_name: nil, default: nil) + add_field_definition(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, + default: default) + + define_accessor(name) + define_setter(name) + end + + # Define a new literal for this model + # + # @param name [Symbol] + # @param value [Object] + # @option api_name [Symbol, String] + # @return [void] + def literal(name, value, api_name: nil) + add_field_definition(name: name, type: value.class, optional: false, nullable: false, api_name: api_name, + value: value) + + define_accessor(name) + end + + # Adds a new field definition into the class's fields registry + # + # @api private + # + # @param name [Symbol] + # @param type [Class] + # @option optional [Boolean] + # @return [void] + private def add_field_definition(name:, type:, optional:, nullable:, api_name:, default: nil, value: nil) + fields[name.to_sym] = + Field.new(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, + value: value, default: default) + end + + # Adds a new field definition into the class's extra fields registry + # + # @api private + # + # @param name [Symbol] + # @param type [Class] + # @option required [Boolean] + # @option optional [Boolean] + # @return [void] + def add_extra_field_definition(name:, type:) + return if extra_fields.key?(name.to_sym) + + extra_fields[name.to_sym] = Field.new(name: name, type: type, optional: true, nullable: false) + + define_accessor(name) + define_setter(name) + end + + # @api private + private def define_accessor(name) + method_name = name.to_sym + + define_method(method_name) do + @data[name] + end + end + + # @api private + private def define_setter(name) + method_name = :"#{name}=" + + define_method(method_name) do |val| + @data[name] = val + end + end + + def coerce(value, strict: (respond_to?(:strict?) ? strict? : false)) # rubocop:disable Lint/UnusedMethodArgument + return value if value.is_a?(self) + + return value unless value.is_a?(::Hash) + + new(value) + end + + def load(str) + coerce(::JSON.parse(str, symbolize_names: true)) + end + + def ===(instance) + instance.class.ancestors.include?(self) + end + end + + # Creates a new instance of this model + # TODO: Should all this logic be in `#coerce` instead? + # + # @param values [Hash] + # @option strict [Boolean] + # @return [self] + def initialize(values = {}) + @data = {} + + values = Utils.symbolize_keys(values.dup) + + self.class.fields.each do |field_name, field| + value = values.delete(field.api_name.to_sym) || values.delete(field.api_name) || values.delete(field_name) + + field_value = value || (if field.literal? + field.value + elsif field.default + field.default + end) + + @data[field_name] = Utils.coerce(field.type, field_value) + end + + # Any remaining values in the input become extra fields + values.each do |name, value| + self.class.add_extra_field_definition(name: name, type: value.class) + + @data[name.to_sym] = value + end + end + + def to_h + result = self.class.fields.merge(self.class.extra_fields).each_with_object({}) do |(name, field), acc| + # If there is a value present in the data, use that value + # If there is a `nil` value present in the data, and it is optional but NOT nullable, exclude key altogether + # If there is a `nil` value present in the data, and it is optional and nullable, use the nil value + + value = @data[name] + + next if value.nil? && field.optional && !field.nullable + + if value.is_a?(::Array) + value = value.map { |item| item.respond_to?(:to_h) ? item.to_h : item } + elsif value.respond_to?(:to_h) + value = value.to_h + end + + acc[field.api_name] = value + end + + # Inject union discriminant if this instance was coerced from a discriminated union + # and the discriminant key is not already present in the result + discriminant_key = instance_variable_get(:@_fern_union_discriminant_key) + discriminant_value = instance_variable_get(:@_fern_union_discriminant_value) + result[discriminant_key] = discriminant_value if discriminant_key && discriminant_value && !result.key?(discriminant_key) + + result + end + + def ==(other) + self.class == other.class && to_h == other.to_h + end + + # @return [String] + def inspect + attrs = @data.map do |name, value| + field = self.class.fields[name] || self.class.extra_fields[name] + display_value = field&.sensitive? ? "[REDACTED]" : value.inspect + "#{name}=#{display_value}" + end + + "#<#{self.class.name}:0x#{object_id&.to_s(16)} #{attrs.join(" ")}>" + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb new file mode 100644 index 000000000000..6ce0186f6a5d --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + class Model + # Definition of a field on a model + class Field + SENSITIVE_FIELD_NAMES = %i[ + password secret token api_key apikey access_token refresh_token + client_secret client_id credential bearer authorization + ].freeze + + attr_reader :name, :type, :optional, :nullable, :api_name, :value, :default + + def initialize(name:, type:, optional: false, nullable: false, api_name: nil, value: nil, default: nil) + @name = name.to_sym + @type = type + @optional = optional + @nullable = nullable + @api_name = api_name || name.to_s + @value = value + @default = default + end + + def literal? + !value.nil? + end + + def sensitive? + SENSITIVE_FIELD_NAMES.include?(@name) || + SENSITIVE_FIELD_NAMES.any? { |sensitive| @name.to_s.include?(sensitive.to_s) } + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb new file mode 100644 index 000000000000..5866caf1dbda --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # @abstract + module Type + include Seed::Internal::JSON::Serializable + + # Coerces a value to this type + # + # @param value [unknown] + # @option strict [Boolean] If we should strictly coerce this value + def coerce(value, strict: strict?) + raise NotImplementedError + end + + # Returns if strictness is on for this type, defaults to `false` + # + # @return [Boolean] + def strict? + @strict ||= false + end + + # Enable strictness by default for this type + # + # @return [void] + def strict! + @strict = true + self + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb new file mode 100644 index 000000000000..f3e118a2fa78 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # Define a union between two types + module Union + include Seed::Internal::Types::Type + + def members + @members ||= [] + end + + # Add a member to this union + # + # @param type [Object] + # @option key [Symbol, String] + # @return [void] + def member(type, key: nil) + members.push([key, Utils.wrap_type(type)]) + self + end + + def type_member?(type) + members.any? { |_key, type_fn| type == type_fn.call } + end + + # Set the discriminant for this union + # + # @param key [Symbol, String] + # @return [void] + def discriminant(key) + @discriminant = key + end + + # @api private + private def discriminated? + !@discriminant.nil? + end + + # Check if value matches a type, handling type wrapper instances + # (Internal::Types::Hash and Internal::Types::Array instances) + # + # @param value [Object] + # @param member_type [Object] + # @return [Boolean] + private def type_matches?(value, member_type) + case member_type + when Seed::Internal::Types::Hash + value.is_a?(::Hash) + when Seed::Internal::Types::Array + value.is_a?(::Array) + when Class, Module + value.is_a?(member_type) + else + false + end + end + + # Resolves the type of a value to be one of the members + # + # @param value [Object] + # @return [Class] + private def resolve_member(value) + if discriminated? && value.is_a?(::Hash) + # Try both symbol and string keys for the discriminant + discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) + + return if discriminant_value.nil? + + # Convert to string for consistent comparison + discriminant_str = discriminant_value.to_s + + # First try exact match + members_hash = members.to_h + result = members_hash[discriminant_str]&.call + return result if result + + # Try case-insensitive match as fallback + discriminant_lower = discriminant_str.downcase + matching_keys = members_hash.keys.select { |k| k.to_s.downcase == discriminant_lower } + + # Only use case-insensitive match if exactly one key matches (avoid ambiguity) + return members_hash[matching_keys.first]&.call if matching_keys.length == 1 + + nil + else + # First try exact type matching + result = members.find do |_key, mem| + member_type = Utils.unwrap_type(mem) + type_matches?(value, member_type) + end&.last&.call + + return result if result + + # For Hash values, try to coerce into Model member types + if value.is_a?(::Hash) + members.find do |_key, mem| + member_type = Utils.unwrap_type(mem) + # Check if member_type is a Model class + next unless member_type.is_a?(Class) && member_type <= Model + + # Try to coerce the hash into this model type with strict mode + begin + candidate = Utils.coerce(member_type, value, strict: true) + + # Validate that all required (non-optional) fields are present + # This ensures undiscriminated unions properly distinguish between member types + member_type.fields.each do |field_name, field| + raise Errors::TypeError, "Required field `#{field_name}` missing for union member #{member_type.name}" if candidate.instance_variable_get(:@data)[field_name].nil? && !field.optional + end + + true + rescue Errors::TypeError + false + end + end&.last&.call + end + end + end + + def coerce(value, strict: strict?) + type = resolve_member(value) + + unless type + return value unless strict + + if discriminated? + raise Errors::TypeError, + "value of type `#{value.class}` not member of union #{self}" + end + + raise Errors::TypeError, "could not resolve to member of union #{self}" + end + + coerced = Utils.coerce(type, value, strict: strict) + + # For discriminated unions, store the discriminant info on the coerced instance + # so it can be injected back during serialization (to_h) + if discriminated? && value.is_a?(::Hash) && coerced.is_a?(Model) + discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) + if discriminant_value + coerced.instance_variable_set(:@_fern_union_discriminant_key, @discriminant.to_s) + coerced.instance_variable_set(:@_fern_union_discriminant_value, discriminant_value) + end + end + + coerced + end + + # Parse JSON string and coerce to the correct union member type + # + # @param str [String] JSON string to parse + # @return [Object] Coerced value matching a union member + def load(str) + coerce(::JSON.parse(str, symbolize_names: true)) + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb new file mode 100644 index 000000000000..7b58de956da9 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + module Unknown + include Seed::Internal::Types::Type + + def coerce(value) + value + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb new file mode 100644 index 000000000000..0ac6179e855f --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # Utilities for dealing with and checking types + module Utils + # Wraps a type into a type function + # + # @param type [Proc, Object] + # @return [Proc] + def self.wrap_type(type) + case type + when Proc + type + else + -> { type } + end + end + + # Resolves a type or type function into a type + # + # @param type [Proc, Object] + # @return [Object] + def self.unwrap_type(type) + type.is_a?(Proc) ? type.call : type + end + + def self.coerce(target, value, strict: false) + type = unwrap_type(target) + + case type + in Array + case value + when ::Array + return type.coerce(value, strict: strict) + when Set, ::Hash + return coerce(type, value.to_a) + end + in Hash + case value + when ::Hash + return type.coerce(value, strict: strict) + when ::Array + return coerce(type, value.to_h) + end + in ->(t) { t <= NilClass } + return nil + in ->(t) { t <= String } + case value + when String, Symbol, Numeric, TrueClass, FalseClass + return value.to_s + end + in ->(t) { t <= Symbol } + case value + when Symbol, String + return value.to_sym + end + in ->(t) { t <= Integer } + case value + when Numeric, String, Time + return value.to_i + end + in ->(t) { t <= Float } + case value + when Numeric, Time, String + return value.to_f + end + in ->(t) { t <= Model } + case value + when type + return value + when ::Hash + return type.coerce(value, strict: strict) + end + in Module + case type + in ->(t) { + t.singleton_class.included_modules.include?(Enum) || + t.singleton_class.included_modules.include?(Union) + } + return type.coerce(value, strict: strict) + else + value # rubocop:disable Lint/Void + end + else + value # rubocop:disable Lint/Void + end + + raise Errors::TypeError, "cannot coerce value of type `#{value.class}` to `#{target}`" if strict + + value + end + + def self.symbolize_keys(hash) + hash.transform_keys(&:to_sym) + end + + # Converts camelCase keys to snake_case symbols + # This allows SDK methods to accept both snake_case and camelCase keys + # e.g., { refundMethod: ... } becomes { refund_method: ... } + # + # @param hash [Hash] + # @return [Hash] + def self.normalize_keys(hash) + hash.transform_keys do |key| + key_str = key.to_s + # Convert camelCase to snake_case + snake_case = key_str.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase + snake_case.to_sym + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb new file mode 100644 index 000000000000..2df8e8d92220 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Seed + module Types + # Common audit metadata. + class AuditInfo < Internal::Types::Model + field :created_by, -> { String }, optional: true, nullable: false, api_name: "createdBy" + field :created_date_time, -> { String }, optional: true, nullable: false, api_name: "createdDateTime" + field :modified_by, -> { String }, optional: true, nullable: false, api_name: "modifiedBy" + field :modified_date_time, -> { String }, optional: true, nullable: false, api_name: "modifiedDateTime" + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb new file mode 100644 index 000000000000..8e6b5538aad2 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class BaseOrg < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :metadata, -> { Seed::Types::BaseOrgMetadata }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb new file mode 100644 index 000000000000..4167d5d9464f --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class BaseOrgMetadata < Internal::Types::Model + field :region, -> { String }, optional: false, nullable: false + field :tier, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb new file mode 100644 index 000000000000..8d74cacbb442 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Seed + module Types + class CombinedEntity < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: true, nullable: false + field :summary, -> { String }, optional: true, nullable: false + field :status, -> { Seed::Types::CombinedEntityStatus }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb new file mode 100644 index 000000000000..ce445c2e0f19 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Seed + module Types + module CombinedEntityStatus + extend Seed::Internal::Types::Enum + + ACTIVE = "active" + ARCHIVED = "archived" + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb new file mode 100644 index 000000000000..bf18865e61b5 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class Describable < Internal::Types::Model + field :name, -> { String }, optional: true, nullable: false + field :summary, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb new file mode 100644 index 000000000000..5839792d7c2c --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Seed + module Types + class DetailedOrg < Internal::Types::Model + field :metadata, -> { Seed::Types::DetailedOrgMetadata }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb new file mode 100644 index 000000000000..5c5a311e208c --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class DetailedOrgMetadata < Internal::Types::Model + field :region, -> { String }, optional: false, nullable: false + field :domain, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb new file mode 100644 index 000000000000..420ec1d027dc --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class Identifiable < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb new file mode 100644 index 000000000000..3cf709f96cd0 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Seed + module Types + class Organization < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :metadata, -> { Seed::Types::OrganizationMetadata }, optional: true, nullable: false + field :name, -> { String }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb new file mode 100644 index 000000000000..c25fc51ef2a5 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class OrganizationMetadata < Internal::Types::Model + field :region, -> { String }, optional: false, nullable: false + field :domain, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb new file mode 100644 index 000000000000..10755d50c291 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class PaginatedResult < Internal::Types::Model + field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false + field :results, -> { Internal::Types::Array[Object] }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb new file mode 100644 index 000000000000..925959b21486 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class PagingCursors < Internal::Types::Model + field :next_, -> { String }, optional: false, nullable: false, api_name: "next" + field :previous, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb new file mode 100644 index 000000000000..be802bd7fd55 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Seed + module Types + module RuleExecutionContext + extend Seed::Internal::Types::Enum + + PROD = "prod" + STAGING = "staging" + DEV = "dev" + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb new file mode 100644 index 000000000000..e11c0b53c809 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Seed + module Types + class RuleResponse < Internal::Types::Model + field :created_by, -> { String }, optional: true, nullable: false, api_name: "createdBy" + field :created_date_time, -> { String }, optional: true, nullable: false, api_name: "createdDateTime" + field :modified_by, -> { String }, optional: true, nullable: false, api_name: "modifiedBy" + field :modified_date_time, -> { String }, optional: true, nullable: false, api_name: "modifiedDateTime" + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: false, nullable: false + field :status, -> { Seed::Types::RuleResponseStatus }, optional: false, nullable: false + field :execution_context, -> { Seed::Types::RuleExecutionContext }, optional: true, nullable: false, api_name: "executionContext" + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb new file mode 100644 index 000000000000..609f51bfabb4 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Seed + module Types + module RuleResponseStatus + extend Seed::Internal::Types::Enum + + ACTIVE = "active" + INACTIVE = "inactive" + DRAFT = "draft" + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb new file mode 100644 index 000000000000..798fc21f7888 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Seed + module Types + class RuleType < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: false, nullable: false + field :description, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb new file mode 100644 index 000000000000..1efe97d4ec28 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class RuleTypeSearchResponse < Internal::Types::Model + field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false + field :results, -> { Internal::Types::Array[Seed::Types::RuleType] }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb new file mode 100644 index 000000000000..9d5a150b6803 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class User < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :email, -> { String }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb new file mode 100644 index 000000000000..b08cad04abab --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class UserSearchResponse < Internal::Types::Model + field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false + field :results, -> { Internal::Types::Array[Seed::Types::User] }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb new file mode 100644 index 000000000000..00dd45cdd958 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Seed + VERSION = "0.0.1" +end diff --git a/seed/ruby-sdk-v2/allof-inline/reference.md b/seed/ruby-sdk-v2/allof-inline/reference.md new file mode 100644 index 000000000000..2a0eb7a77a05 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/reference.md @@ -0,0 +1,228 @@ +# Reference +
client.search_rule_types() -> Seed::Types::RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.search_rule_types +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `String` + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.create_rule(request) -> Seed::Types::RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.create_rule( + name: "name", + execution_context: "prod" +) +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `String` + +
+
+ +
+
+ +**execution_context:** `Seed::Types::RuleExecutionContext` + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.list_users() -> Seed::Types::UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.list_users +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.get_entity() -> Seed::Types::CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.get_entity +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.get_organization() -> Seed::Types::Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.get_organization +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ diff --git a/seed/ruby-sdk-v2/allof-inline/seed.gemspec b/seed/ruby-sdk-v2/allof-inline/seed.gemspec new file mode 100644 index 000000000000..f87ecf152d70 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/seed.gemspec @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative "lib/seed/version" +require_relative "custom.gemspec" + +# NOTE: A handful of these fields are required as part of the Ruby specification. +# You can change them here or overwrite them in the custom gemspec file. +Gem::Specification.new do |spec| + spec.name = "fern_allof-inline" + spec.authors = ["Seed"] + spec.version = Seed::VERSION + spec.summary = "Ruby client library for the Seed API" + spec.description = "The Seed Ruby library provides convenient access to the Seed API from Ruby." + spec.required_ruby_version = ">= 3.3.0" + spec.metadata["rubygems_mfa_required"] = "true" + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + gemspec = File.basename(__FILE__) + spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| + ls.readlines("\x0", chomp: true).reject do |f| + (f == gemspec) || + f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile]) + end + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + # For more information and examples about making a new gem, check out our + # guide at: https://bundler.io/guides/creating_gem.html + + # Load custom gemspec configuration if it exists + custom_gemspec_file = File.join(__dir__, "custom.gemspec.rb") + add_custom_gemspec_data(spec) if File.exist?(custom_gemspec_file) +end diff --git a/seed/ruby-sdk-v2/allof-inline/snippet.json b/seed/ruby-sdk-v2/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/ruby-sdk-v2/allof-inline/test/custom.test.rb b/seed/ruby-sdk-v2/allof-inline/test/custom.test.rb new file mode 100644 index 000000000000..4bd57989d43d --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/custom.test.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# This is a custom test file, if you wish to add more tests +# to your SDK. +# Be sure to mark this file in `.fernignore`. +# +# If you include example requests/responses in your fern definition, +# you will have tests automatically generated for you. + +# This test is run via command line: rake customtest +describe "Custom Test" do + it "Default" do + refute false + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/test_helper.rb b/seed/ruby-sdk-v2/allof-inline/test/test_helper.rb new file mode 100644 index 000000000000..b086fe6d76ec --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/test_helper.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative "../lib/seed" diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb new file mode 100644 index 000000000000..5008f6abf69f --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require "stringio" +require "json" +require "test_helper" + +NUMBERS = (1..65).to_a +PageResponse = Struct.new(:cards, :next_cursor, keyword_init: true) + +class CursorItemIteratorTest < Minitest::Test + def make_iterator(initial_cursor:) + @times_called = 0 + + Seed::Internal::CursorItemIterator.new(initial_cursor:, cursor_field: :next_cursor, item_field: :cards) do |cursor| + @times_called += 1 + cursor ||= 0 + next_cursor = cursor + 10 + PageResponse.new( + cards: NUMBERS[cursor...next_cursor], + next_cursor: next_cursor < NUMBERS.length ? next_cursor : nil + ) + end + end + + def test_item_iterator_can_iterate_to_exhaustion + iterator = make_iterator(initial_cursor: 0) + + assert_equal NUMBERS, iterator.to_a + assert_equal 7, @times_called + + iterator = make_iterator(initial_cursor: 10) + + assert_equal (11..65).to_a, iterator.to_a + + iterator = make_iterator(initial_cursor: 5) + + assert_equal (6..65).to_a, iterator.to_a + end + + def test_item_iterator_can_work_without_an_initial_cursor + iterator = make_iterator(initial_cursor: nil) + + assert_equal NUMBERS, iterator.to_a + assert_equal 7, @times_called + end + + def test_items_iterator_iterates_lazily + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + assert_equal 1, iterator.first + assert_equal 1, @times_called + + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + assert_equal (1..15).to_a, iterator.first(15) + assert_equal 2, @times_called + + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + iterator.each do |card| + break if card >= 15 + end + + assert_equal 2, @times_called + end + + def test_items_iterator_implements_enumerable + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + doubled = iterator.map { |card| card * 2 } + + assert_equal 7, @times_called + assert_equal NUMBERS.length, doubled.length + end + + def test_items_iterator_can_be_advanced_manually + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + + items = [] + expected_times_called = 0 + while (item = iterator.next_element) + expected_times_called += 1 if (item % 10) == 1 + + assert_equal expected_times_called, @times_called + assert_equal item != NUMBERS.last, iterator.next?, "#{item} #{iterator}" + items.push(item) + end + + assert_equal 7, @times_called + assert_equal NUMBERS, items + end + + def test_pages_iterator + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal( + [ + (1..10).to_a, + (11..20).to_a, + (21..30).to_a, + (31..40).to_a, + (41..50).to_a, + (51..60).to_a, + (61..65).to_a + ], + iterator.to_a.map(&:cards) + ) + + iterator = make_iterator(initial_cursor: 10).pages + + assert_equal( + [ + (11..20).to_a, + (21..30).to_a, + (31..40).to_a, + (41..50).to_a, + (51..60).to_a, + (61..65).to_a + ], + iterator.to_a.map(&:cards) + ) + end + + def test_pages_iterator_can_work_without_an_initial_cursor + iterator = make_iterator(initial_cursor: nil).pages + + assert_equal 7, iterator.to_a.length + assert_equal 7, @times_called + end + + def test_pages_iterator_iterates_lazily + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + iterator.first + + assert_equal 1, @times_called + + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + assert_equal 2, iterator.first(2).length + assert_equal 2, @times_called + end + + def test_pages_iterator_knows_whether_another_page_is_upcoming + iterator = make_iterator(initial_cursor: 0).pages + + iterator.each_with_index do |_page, index| + assert_equal index + 1, @times_called + assert_equal index < 6, iterator.next? + end + end + + def test_pages_iterator_can_be_advanced_manually + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + + lengths = [] + expected_times_called = 0 + while (page = iterator.next_page) + expected_times_called += 1 + + assert_equal expected_times_called, @times_called + lengths.push(page.cards.length) + end + + assert_equal 7, @times_called + assert_equal [10, 10, 10, 10, 10, 10, 5], lengths + end + + def test_pages_iterator_implements_enumerable + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + lengths = iterator.map { |page| page.cards.length } + + assert_equal 7, @times_called + assert_equal [10, 10, 10, 10, 10, 10, 5], lengths + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb new file mode 100644 index 000000000000..92576b820128 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require "stringio" +require "json" +require "test_helper" + +OffsetPageResponse = Struct.new(:items, :has_next, keyword_init: true) +TestIteratorConfig = Struct.new( + :step, + :has_next_field, + :total_item_count, + :per_page, + :initial_page +) do + def first_item_returned + if step + (initial_page || 0) + 1 + else + (((initial_page || 1) - 1) * per_page) + 1 + end + end +end + +LAZY_TEST_ITERATOR_CONFIG = TestIteratorConfig.new(initial_page: 1, step: false, has_next_field: :has_next, total_item_count: 65, per_page: 10) +ALL_TEST_ITERATOR_CONFIGS = [true, false].map do |step| + [:has_next, nil].map do |has_next_field| + [0, 5, 10, 60, 63].map do |total_item_count| + [5, 10].map do |per_page| + initial_pages = [nil, 3, 100] + initial_pages << (step ? 0 : 1) + + initial_pages.map do |initial_page| + TestIteratorConfig.new( + step: step, + has_next_field: has_next_field, + total_item_count: total_item_count, + per_page: per_page, + initial_page: initial_page + ) + end + end + end + end +end.flatten + +class OffsetItemIteratorTest < Minitest::Test + def make_iterator(config) + @times_called = 0 + + items = (1..config.total_item_count).to_a + + Seed::Internal::OffsetItemIterator.new( + initial_page: config.initial_page, + item_field: :items, + has_next_field: config.has_next_field, + step: config.step + ) do |page| + @times_called += 1 + + slice_start = config.step ? page : (page - 1) * config.per_page + slice_end = slice_start + config.per_page + + output = { + items: items[slice_start...slice_end] + } + output[config.has_next_field] = slice_end < items.length if config.has_next_field + + OffsetPageResponse.new(**output) + end + end + + def test_item_iterator_can_iterate_to_exhaustion + ALL_TEST_ITERATOR_CONFIGS.each do |config| + iterator = make_iterator(config) + + assert_equal (config.first_item_returned..config.total_item_count).to_a, iterator.to_a + end + end + + def test_items_iterator_can_be_advanced_manually_and_has_accurate_has_next + ALL_TEST_ITERATOR_CONFIGS.each do |config| + iterator = make_iterator(config) + items = [] + + while (item = iterator.next_element) + assert_equal(item != config.total_item_count, iterator.next?, "#{item} #{iterator}") + items.push(item) + end + + assert_equal (config.first_item_returned..config.total_item_count).to_a, items + end + end + + def test_pages_iterator_can_be_advanced_manually_and_has_accurate_has_next + ALL_TEST_ITERATOR_CONFIGS.each do |config| + iterator = make_iterator(config).pages + pages = [] + + loop do + has_next_output = iterator.next? + page = iterator.next_page + + assert_equal(has_next_output, !page.nil?, "next? was inaccurate: #{config} #{iterator.inspect}") + break if page.nil? + + pages.push(page) + end + + assert_equal pages, make_iterator(config).pages.to_a + end + end + + def test_items_iterator_iterates_lazily + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) + + assert_equal 0, @times_called + assert_equal 1, iterator.first + assert_equal 1, @times_called + + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) + + assert_equal 0, @times_called + assert_equal (1..15).to_a, iterator.first(15) + assert_equal 2, @times_called + + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) + + assert_equal 0, @times_called + iterator.each do |card| + break if card >= 15 + end + + assert_equal 2, @times_called + end + + def test_pages_iterator_iterates_lazily + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages + + assert_equal 0, @times_called + iterator.first + + assert_equal 1, @times_called + + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages + + assert_equal 0, @times_called + assert_equal 3, iterator.first(3).length + assert_equal 3, @times_called + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb new file mode 100644 index 000000000000..e7e6571f03ee --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Array do + module TestArray + StringArray = Seed::Internal::Types::Array[String] + end + + describe "#initialize" do + it "sets the type" do + assert_equal String, TestArray::StringArray.type + end + end + + describe "#coerce" do + it "does not perform coercion if not an array" do + assert_equal 1, TestArray::StringArray.coerce(1) + end + + it "raises an error if not an array and strictness is on" do + assert_raises Seed::Internal::Errors::TypeError do + TestArray::StringArray.coerce(1, strict: true) + end + end + + it "coerces the elements" do + assert_equal %w[foobar 1 true], TestArray::StringArray.coerce(["foobar", 1, true]) + end + + it "raises an error if element of array is not coercable and strictness is on" do + assert_raises Seed::Internal::Errors::TypeError do + TestArray::StringArray.coerce([Object.new], strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb new file mode 100644 index 000000000000..cba18e48765b --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Boolean do + describe ".coerce" do + it "coerces true/false" do + assert Seed::Internal::Types::Boolean.coerce(true) + refute Seed::Internal::Types::Boolean.coerce(false) + end + + it "coerces an Integer" do + assert Seed::Internal::Types::Boolean.coerce(1) + refute Seed::Internal::Types::Boolean.coerce(0) + end + + it "coerces a String" do + assert Seed::Internal::Types::Boolean.coerce("1") + assert Seed::Internal::Types::Boolean.coerce("true") + refute Seed::Internal::Types::Boolean.coerce("0") + end + + it "passes through other values with strictness off" do + obj = Object.new + + assert_equal obj, Seed::Internal::Types::Boolean.coerce(obj) + end + + it "raises an error with other values with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + Seed::Internal::Types::Boolean.coerce(Object.new, strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb new file mode 100644 index 000000000000..e8d89bce467f --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Enum do + module EnumTest + module ExampleEnum + extend Seed::Internal::Types::Enum + + FOO = :foo + BAR = :bar + + finalize! + end + end + + describe "#values" do + it "defines values" do + assert_equal %i[foo bar].sort, EnumTest::ExampleEnum.values.sort + end + end + + describe "#coerce" do + it "coerces an existing member" do + assert_equal :foo, EnumTest::ExampleEnum.coerce(:foo) + end + + it "coerces a string version of a member" do + assert_equal :foo, EnumTest::ExampleEnum.coerce("foo") + end + + it "returns the value if not a member with strictness off" do + assert_equal 1, EnumTest::ExampleEnum.coerce(1) + end + + it "raises an error if value is not a member with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + EnumTest::ExampleEnum.coerce(1, strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb new file mode 100644 index 000000000000..6c5e58a6a946 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Hash do + module TestHash + SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] + end + + describe ".[]" do + it "defines the key and value type" do + assert_equal Symbol, TestHash::SymbolStringHash.key_type + assert_equal String, TestHash::SymbolStringHash.value_type + end + end + + describe "#coerce" do + it "coerces the keys" do + assert_equal %i[foo bar], TestHash::SymbolStringHash.coerce({ "foo" => "1", :bar => "2" }).keys + end + + it "coerces the values" do + assert_equal %w[foo 1], TestHash::SymbolStringHash.coerce({ foo: :foo, bar: 1 }).values + end + + it "passes through other values with strictness off" do + obj = Object.new + + assert_equal obj, TestHash::SymbolStringHash.coerce(obj) + end + + it "raises an error with other values with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce(Object.new, strict: true) + end + end + + it "raises an error with non-coercable key types with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce({ Object.new => 1 }, strict: true) + end + end + + it "raises an error with non-coercable value types with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce({ "foobar" => Object.new }, strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb new file mode 100644 index 000000000000..3d87b9f5a8c7 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Model do + module StringInteger + extend Seed::Internal::Types::Union + + member String + member Integer + end + + class ExampleModel < Seed::Internal::Types::Model + field :name, String + field :rating, StringInteger, optional: true + field :year, Integer, optional: true, nullable: true, api_name: "yearOfRelease" + end + + class ExampleModelInheritance < ExampleModel + field :director, String + end + + class ExampleWithDefaults < ExampleModel + field :type, String, default: "example" + end + + class ExampleChild < Seed::Internal::Types::Model + field :value, String + end + + class ExampleParent < Seed::Internal::Types::Model + field :child, ExampleChild + end + + describe ".field" do + before do + @example = ExampleModel.new(name: "Inception", rating: 4) + end + + it "defines fields on model" do + assert_equal %i[name rating year], ExampleModel.fields.keys + end + + it "defines fields from parent models" do + assert_equal %i[name rating year director], ExampleModelInheritance.fields.keys + end + + it "sets the field's type" do + assert_equal String, ExampleModel.fields[:name].type + assert_equal StringInteger, ExampleModel.fields[:rating].type + end + + it "sets the `default` option" do + assert_equal "example", ExampleWithDefaults.fields[:type].default + end + + it "defines getters" do + assert_respond_to @example, :name + assert_respond_to @example, :rating + + assert_equal "Inception", @example.name + assert_equal 4, @example.rating + end + + it "defines setters" do + assert_respond_to @example, :name= + assert_respond_to @example, :rating= + + @example.name = "Inception 2" + @example.rating = 5 + + assert_equal "Inception 2", @example.name + assert_equal 5, @example.rating + end + end + + describe "#initialize" do + it "sets the data" do + example = ExampleModel.new(name: "Inception", rating: 4) + + assert_equal "Inception", example.name + assert_equal 4, example.rating + end + + it "allows extra fields to be set" do + example = ExampleModel.new(name: "Inception", rating: 4, director: "Christopher Nolan") + + assert_equal "Christopher Nolan", example.director + end + + it "sets the defaults where applicable" do + example_using_defaults = ExampleWithDefaults.new + + assert_equal "example", example_using_defaults.type + + example_without_defaults = ExampleWithDefaults.new(type: "not example") + + assert_equal "not example", example_without_defaults.type + end + + it "coerces child models" do + parent = ExampleParent.new(child: { value: "foobar" }) + + assert_kind_of ExampleChild, parent.child + end + + it "uses the api_name to pull the value" do + example = ExampleModel.new({ name: "Inception", yearOfRelease: 2014 }) + + assert_equal 2014, example.year + refute_respond_to example, :yearOfRelease + end + end + + describe "#inspect" do + class SensitiveModel < Seed::Internal::Types::Model + field :username, String + field :password, String + field :client_secret, String + field :access_token, String + field :api_key, String + end + + it "redacts sensitive fields" do + model = SensitiveModel.new( + username: "user123", + password: "secret123", + client_secret: "cs_abc", + access_token: "token_xyz", + api_key: "key_123" + ) + + inspect_output = model.inspect + + assert_includes inspect_output, "username=\"user123\"" + assert_includes inspect_output, "password=[REDACTED]" + assert_includes inspect_output, "client_secret=[REDACTED]" + assert_includes inspect_output, "access_token=[REDACTED]" + assert_includes inspect_output, "api_key=[REDACTED]" + refute_includes inspect_output, "secret123" + refute_includes inspect_output, "cs_abc" + refute_includes inspect_output, "token_xyz" + refute_includes inspect_output, "key_123" + end + + it "does not redact non-sensitive fields" do + example = ExampleModel.new(name: "Inception", rating: 4) + inspect_output = example.inspect + + assert_includes inspect_output, "name=\"Inception\"" + assert_includes inspect_output, "rating=4" + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb new file mode 100644 index 000000000000..e4e95c93139f --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Union do + class Rectangle < Seed::Internal::Types::Model + literal :type, "square" + + field :area, Float + end + + class Circle < Seed::Internal::Types::Model + literal :type, "circle" + + field :area, Float + end + + class Pineapple < Seed::Internal::Types::Model + literal :type, "pineapple" + + field :area, Float + end + + module Shape + extend Seed::Internal::Types::Union + + discriminant :type + + member -> { Rectangle }, key: "rect" + member -> { Circle }, key: "circle" + end + + module StringOrInteger + extend Seed::Internal::Types::Union + + member String + member Integer + end + + describe "#coerce" do + it "coerces hashes into member models with discriminated unions" do + circle = Shape.coerce({ type: "circle", area: 4.0 }) + + assert_instance_of Circle, circle + end + end + + describe "#type_member?" do + it "defines Model members" do + assert Shape.type_member?(Rectangle) + assert Shape.type_member?(Circle) + refute Shape.type_member?(Pineapple) + end + + it "defines other members" do + assert StringOrInteger.type_member?(String) + assert StringOrInteger.type_member?(Integer) + refute StringOrInteger.type_member?(Float) + refute StringOrInteger.type_member?(Pineapple) + end + end +end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb new file mode 100644 index 000000000000..29d14621a229 --- /dev/null +++ b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Utils do + Utils = Seed::Internal::Types::Utils + + module TestUtils + class M < Seed::Internal::Types::Model + field :value, String + end + + class UnionMemberA < Seed::Internal::Types::Model + literal :type, "A" + field :only_on_a, String + end + + class UnionMemberB < Seed::Internal::Types::Model + literal :type, "B" + field :only_on_b, String + end + + module U + extend Seed::Internal::Types::Union + + discriminant :type + + member -> { UnionMemberA }, key: "A" + member -> { UnionMemberB }, key: "B" + end + + SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] + SymbolModelHash = -> { Seed::Internal::Types::Hash[Symbol, TestUtils::M] } + end + + describe ".coerce" do + describe "NilClass" do + it "always returns nil" do + assert_nil Utils.coerce(NilClass, "foobar") + assert_nil Utils.coerce(NilClass, 1) + assert_nil Utils.coerce(NilClass, Object.new) + end + end + + describe "String" do + it "coerces from String, Symbol, Numeric, or Boolean" do + assert_equal "foobar", Utils.coerce(String, "foobar") + assert_equal "foobar", Utils.coerce(String, :foobar) + assert_equal "1", Utils.coerce(String, 1) + assert_equal "1.0", Utils.coerce(String, 1.0) + assert_equal "true", Utils.coerce(String, true) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(String, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(String, Object.new, strict: true) + end + end + end + + describe "Symbol" do + it "coerces from Symbol, String" do + assert_equal :foobar, Utils.coerce(Symbol, :foobar) + assert_equal :foobar, Utils.coerce(Symbol, "foobar") + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Symbol, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(Symbol, Object.new, strict: true) + end + end + end + + describe "Integer" do + it "coerces from Numeric, String, Time" do + assert_equal 1, Utils.coerce(Integer, 1) + assert_equal 1, Utils.coerce(Integer, 1.0) + assert_equal 1, Utils.coerce(Integer, Complex.rect(1)) + assert_equal 1, Utils.coerce(Integer, Rational(1)) + assert_equal 1, Utils.coerce(Integer, "1") + assert_equal 1_713_916_800, Utils.coerce(Integer, Time.utc(2024, 4, 24)) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Integer, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(Integer, Object.new, strict: true) + end + end + end + + describe "Float" do + it "coerces from Numeric, Time" do + assert_in_delta(1.0, Utils.coerce(Float, 1.0)) + assert_in_delta(1.0, Utils.coerce(Float, 1)) + assert_in_delta(1.0, Utils.coerce(Float, Complex.rect(1))) + assert_in_delta(1.0, Utils.coerce(Float, Rational(1))) + assert_in_delta(1_713_916_800.0, Utils.coerce(Integer, Time.utc(2024, 4, 24))) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Float, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(Float, Object.new, strict: true) + end + end + end + + describe "Model" do + it "coerces a hash" do + result = Utils.coerce(TestUtils::M, { value: "foobar" }) + + assert_kind_of TestUtils::M, result + assert_equal "foobar", result.value + end + + it "coerces a hash when the target is a type function" do + result = Utils.coerce(-> { TestUtils::M }, { value: "foobar" }) + + assert_kind_of TestUtils::M, result + assert_equal "foobar", result.value + end + + it "will not coerce non-hashes" do + assert_equal "foobar", Utils.coerce(TestUtils::M, "foobar") + end + end + + describe "Enum" do + module ExampleEnum + extend Seed::Internal::Types::Enum + + FOO = :FOO + BAR = :BAR + + finalize! + end + + it "coerces into a Symbol version of the member value" do + assert_equal :FOO, Utils.coerce(ExampleEnum, "FOO") + end + + it "returns given value if not a member" do + assert_equal "NOPE", Utils.coerce(ExampleEnum, "NOPE") + end + end + + describe "Array" do + StringArray = Seed::Internal::Types::Array[String] + ModelArray = -> { Seed::Internal::Types::Array[TestUtils::M] } + UnionArray = -> { Seed::Internal::Types::Array[TestUtils::U] } + + it "coerces an array of literals" do + assert_equal %w[a b c], Utils.coerce(StringArray, %w[a b c]) + assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, [1, 2.0, true]) + assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, Set.new([1, 2.0, true])) + end + + it "coerces an array of Models" do + assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], + Utils.coerce(ModelArray, [{ value: "foobar" }, { value: "bizbaz" }]) + + assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], + Utils.coerce(ModelArray, [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")]) + end + + it "coerces an array of model unions" do + assert_equal [TestUtils::UnionMemberA.new(type: "A", only_on_a: "A"), TestUtils::UnionMemberB.new(type: "B", only_on_b: "B")], + Utils.coerce(UnionArray, [{ type: "A", only_on_a: "A" }, { type: "B", only_on_b: "B" }]) + end + + it "returns given value if not an array" do + assert_equal 1, Utils.coerce(StringArray, 1) + end + end + + describe "Hash" do + it "coerces the keys and values" do + ssh_res = Utils.coerce(TestUtils::SymbolStringHash, { "foo" => "bar", "biz" => "2" }) + + assert_equal "bar", ssh_res[:foo] + assert_equal "2", ssh_res[:biz] + + smh_res = Utils.coerce(TestUtils::SymbolModelHash, { "foo" => { "value" => "foo" } }) + + assert_equal TestUtils::M.new(value: "foo"), smh_res[:foo] + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/.fern/metadata.json b/seed/ruby-sdk-v2/allof/.fern/metadata.json new file mode 100644 index 000000000000..885f09fc37f2 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-ruby-sdk-v2", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof/.github/workflows/ci.yml b/seed/ruby-sdk-v2/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..72178ea4c8f1 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +name: ci + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Install dependencies + run: bundle install + + - name: Run Rubocop + run: bundle exec rubocop + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Install dependencies + run: bundle install + + - name: Run Tests + run: bundle exec rake test + + publish: + name: Publish to RubyGems.org + runs-on: ubuntu-latest + needs: [lint, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + + permissions: + id-token: write + contents: write + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Install dependencies + run: bundle install + + - name: Configure RubyGems credentials + uses: rubygems/configure-rubygems-credentials@v1.0.0 + + - name: Build gem + run: bundle exec rake build + + - name: Push gem to RubyGems + run: gem push pkg/*.gem diff --git a/seed/ruby-sdk-v2/allof/.gitignore b/seed/ruby-sdk-v2/allof/.gitignore new file mode 100644 index 000000000000..c111b331371a --- /dev/null +++ b/seed/ruby-sdk-v2/allof/.gitignore @@ -0,0 +1 @@ +*.gem diff --git a/seed/ruby-sdk-v2/allof/.rubocop.yml b/seed/ruby-sdk-v2/allof/.rubocop.yml new file mode 100644 index 000000000000..75d8f836f2f0 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/.rubocop.yml @@ -0,0 +1,69 @@ +plugins: + - rubocop-minitest + +AllCops: + TargetRubyVersion: 3.3 + NewCops: enable + +Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/AccessModifierDeclarations: + Enabled: false + +Lint/ConstantDefinitionInBlock: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/BlockLength: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/ParameterLists: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Layout/LineLength: + Enabled: false + +Naming/VariableNumber: + EnforcedStyle: normalcase + +Style/Documentation: + Enabled: false + +Style/Lambda: + EnforcedStyle: literal + +Minitest/MultipleAssertions: + Enabled: false + +Minitest/UselessAssertion: + Enabled: false + +# Dynamic snippets are code samples for documentation, not standalone Ruby files. +Style/FrozenStringLiteralComment: + Exclude: + - "dynamic-snippets/**/*" + +Layout/FirstHashElementIndentation: + Exclude: + - "dynamic-snippets/**/*" diff --git a/seed/ruby-sdk-v2/allof/Gemfile b/seed/ruby-sdk-v2/allof/Gemfile new file mode 100644 index 000000000000..16877f89f300 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/Gemfile @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +group :test, :development do + gem "minitest", "~> 5.16" + gem "minitest-rg" + gem "pry" + gem "rake", "~> 13.0" + gem "rubocop", "~> 1.21" + gem "rubocop-minitest" + gem "webmock" +end + +# Load custom Gemfile configuration if it exists +custom_gemfile = File.join(__dir__, "Gemfile.custom") +eval_gemfile(custom_gemfile) if File.exist?(custom_gemfile) diff --git a/seed/ruby-sdk-v2/allof/Gemfile.custom b/seed/ruby-sdk-v2/allof/Gemfile.custom new file mode 100644 index 000000000000..11bdfaf13f2d --- /dev/null +++ b/seed/ruby-sdk-v2/allof/Gemfile.custom @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Custom Gemfile configuration file +# This file is automatically loaded by the main Gemfile. You can add custom gems, +# groups, or other Gemfile configurations here. If you do make changes to this file, +# you will need to add it to the .fernignore file to prevent your changes from being +# overwritten by the generator. + +# Example usage: +# group :test, :development do +# gem 'custom-gem', '~> 2.0' +# end + +# Add your custom gem dependencies here \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof/README.md b/seed/ruby-sdk-v2/allof/README.md new file mode 100644 index 000000000000..8c9724fb7644 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/README.md @@ -0,0 +1,165 @@ +# Seed Ruby Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRuby) + +The Seed Ruby library provides convenient access to the Seed APIs from Ruby. + +## Table of Contents + +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Additional Headers](#additional-headers) + - [Additional Query Parameters](#additional-query-parameters) +- [Contributing](#contributing) + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```ruby +require "seed" + +client = Seed::Client.new + +client.create_rule( + name: "name", + execution_context: "prod" +) +``` + +## Environments + +This SDK allows you to configure different environments or custom URLs for API requests. You can either use the predefined environments or specify your own custom URL. +### Environments +```ruby +require "seed" + +seed = Seed::Client.new( + base_url: Seed::Environment::DEFAULT +) +``` + +### Custom URL +```ruby +require "seed" + +client = Seed::Client.new( + base_url: "https://example.com" +) +``` + +## Errors + +Failed API calls will raise errors that can be rescued from granularly. + +```ruby +require "seed" + +client = Seed::Client.new( + base_url: "https://example.com" +) + +begin + result = client.create_rule +rescue Seed::Errors::TimeoutError + puts "API didn't respond before our timeout elapsed" +rescue Seed::Errors::ServiceUnavailableError + puts "API returned status 503, is probably overloaded, try again later" +rescue Seed::Errors::ServerError + puts "API returned some other 5xx status, this is probably a bug" +rescue Seed::Errors::ResponseError => e + puts "API returned an unexpected status other than 5xx: #{e.code} #{e.message}" +rescue Seed::Errors::ApiError => e + puts "Some other error occurred when calling the API: #{e.message}" +end +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries. A request will be retried as long as the request is deemed +retryable and the number of retry attempts has not grown larger than the configured retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` option to configure this behavior. + +```ruby +require "seed" + +client = Seed::Client.new( + base_url: "https://example.com", + max_retries: 3 # Configure max retries (default is 2) +) +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeout` option to configure this behavior. + +```ruby +require "seed" + +response = client.create_rule( + ..., + timeout: 30 # 30 second timeout +) +``` + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `additional_headers` request option. + +```ruby +require "seed" + +response = client.create_rule( + ..., + request_options: { + additional_headers: { + "X-Custom-Header" => "custom-value" + } + } +) +``` + +### Additional Query Parameters + +If you would like to send additional query parameters as part of the request, use the `additional_query_parameters` request option. + +```ruby +require "seed" + +response = client.create_rule( + ..., + request_options: { + additional_query_parameters: { + "custom_param" => "custom-value" + } + } +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ruby-sdk-v2/allof/Rakefile b/seed/ruby-sdk-v2/allof/Rakefile new file mode 100644 index 000000000000..9bdd4a6ce80b --- /dev/null +++ b/seed/ruby-sdk-v2/allof/Rakefile @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "minitest/test_task" + +Minitest::TestTask.create + +require "rubocop/rake_task" + +RuboCop::RakeTask.new + +task default: %i[test] + +task lint: %i[rubocop] + +# Run only the custom test file +Minitest::TestTask.create(:customtest) do |t| + t.libs << "test" + t.test_globs = ["test/custom.test.rb"] +end diff --git a/seed/ruby-sdk-v2/allof/custom.gemspec.rb b/seed/ruby-sdk-v2/allof/custom.gemspec.rb new file mode 100644 index 000000000000..86d8efd3cd3c --- /dev/null +++ b/seed/ruby-sdk-v2/allof/custom.gemspec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Custom gemspec configuration file +# This file is automatically loaded by the main gemspec file. The 'spec' variable is available +# in this context from the main gemspec file. You can modify this file to add custom metadata, +# dependencies, or other gemspec configurations. If you do make changes to this file, you will +# need to add it to the .fernignore file to prevent your changes from being overwritten. + +def add_custom_gemspec_data(spec) + # Example custom configurations (uncomment and modify as needed) + + # spec.authors = ["Your name"] + # spec.email = ["your.email@example.com"] + # spec.homepage = "https://github.com/your-org/seed-ruby" + # spec.license = "Your license" +end diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb new file mode 100644 index 000000000000..f8b3f168d71b --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.search_rule_types diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb new file mode 100644 index 000000000000..4b8e2cf5a383 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.search_rule_types(query: "query") diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb new file mode 100644 index 000000000000..96d978fcde17 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb @@ -0,0 +1,8 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.create_rule( + name: "name", + execution_context: "prod" +) diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb new file mode 100644 index 000000000000..96d978fcde17 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb @@ -0,0 +1,8 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.create_rule( + name: "name", + execution_context: "prod" +) diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb new file mode 100644 index 000000000000..99237c7c6437 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.list_users diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb new file mode 100644 index 000000000000..99237c7c6437 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.list_users diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb new file mode 100644 index 000000000000..7769816ca1be --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_entity diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb new file mode 100644 index 000000000000..7769816ca1be --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_entity diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb new file mode 100644 index 000000000000..f47ada1c5233 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_organization diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb new file mode 100644 index 000000000000..f47ada1c5233 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb @@ -0,0 +1,5 @@ +require "seed" + +client = Seed::Client.new(base_url: "https://api.fern.com") + +client.get_organization diff --git a/seed/ruby-sdk-v2/allof/lib/seed.rb b/seed/ruby-sdk-v2/allof/lib/seed.rb new file mode 100644 index 000000000000..4a7e567f5fb2 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "json" +require "net/http" +require "securerandom" + +require_relative "seed/internal/json/serializable" +require_relative "seed/internal/types/type" +require_relative "seed/internal/types/utils" +require_relative "seed/internal/types/union" +require_relative "seed/internal/errors/constraint_error" +require_relative "seed/internal/errors/type_error" +require_relative "seed/internal/http/base_request" +require_relative "seed/internal/json/request" +require_relative "seed/internal/http/raw_client" +require_relative "seed/internal/multipart/multipart_encoder" +require_relative "seed/internal/multipart/multipart_form_data_part" +require_relative "seed/internal/multipart/multipart_form_data" +require_relative "seed/internal/multipart/multipart_request" +require_relative "seed/internal/types/model/field" +require_relative "seed/internal/types/model" +require_relative "seed/internal/types/array" +require_relative "seed/internal/types/boolean" +require_relative "seed/internal/types/enum" +require_relative "seed/internal/types/hash" +require_relative "seed/internal/types/unknown" +require_relative "seed/errors/api_error" +require_relative "seed/errors/response_error" +require_relative "seed/errors/client_error" +require_relative "seed/errors/redirect_error" +require_relative "seed/errors/server_error" +require_relative "seed/errors/timeout_error" +require_relative "seed/internal/iterators/item_iterator" +require_relative "seed/internal/iterators/cursor_item_iterator" +require_relative "seed/internal/iterators/offset_item_iterator" +require_relative "seed/internal/iterators/cursor_page_iterator" +require_relative "seed/internal/iterators/offset_page_iterator" +require_relative "seed/types/paging_cursors" +require_relative "seed/types/paginated_result" +require_relative "seed/types/rule_execution_context" +require_relative "seed/types/audit_info" +require_relative "seed/types/rule_type" +require_relative "seed/types/rule_type_search_response" +require_relative "seed/types/user" +require_relative "seed/types/user_search_response" +require_relative "seed/types/rule_response_status" +require_relative "seed/types/rule_response" +require_relative "seed/types/identifiable" +require_relative "seed/types/describable" +require_relative "seed/types/combined_entity_status" +require_relative "seed/types/combined_entity" +require_relative "seed/types/base_org_metadata" +require_relative "seed/types/base_org" +require_relative "seed/types/detailed_org_metadata" +require_relative "seed/types/detailed_org" +require_relative "seed/types/organization" +require_relative "seed/environment" diff --git a/seed/ruby-sdk-v2/allof/lib/seed/environment.rb b/seed/ruby-sdk-v2/allof/lib/seed/environment.rb new file mode 100644 index 000000000000..e994144e9573 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Seed + class Environment + DEFAULT = "https://api.example.com" + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb new file mode 100644 index 000000000000..b8ba53889b36 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ApiError < StandardError + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb new file mode 100644 index 000000000000..c3c6033641e2 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ClientError < ResponseError + end + + class UnauthorizedError < ClientError + end + + class ForbiddenError < ClientError + end + + class NotFoundError < ClientError + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb new file mode 100644 index 000000000000..f663c01e7615 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Seed + module Errors + class RedirectError < ResponseError + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb new file mode 100644 index 000000000000..beb4a1baf959 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ResponseError < ApiError + attr_reader :code + + def initialize(msg, code:) + @code = code + super(msg) + end + + def inspect + "#<#{self.class.name} @code=#{code} @body=#{message}>" + end + + # Returns the most appropriate error class for the given code. + # + # @return [Class] + def self.subclass_for_code(code) + case code + when 300..399 + RedirectError + when 401 + UnauthorizedError + when 403 + ForbiddenError + when 404 + NotFoundError + when 400..499 + ClientError + when 503 + ServiceUnavailableError + when 500..599 + ServerError + else + ResponseError + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb new file mode 100644 index 000000000000..1838027cdeab --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Seed + module Errors + class ServerError < ResponseError + end + + class ServiceUnavailableError < ApiError + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb new file mode 100644 index 000000000000..ec3a24bb7e96 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Seed + module Errors + class TimeoutError < ApiError + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb new file mode 100644 index 000000000000..e2f0bd66ac37 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Errors + class ConstraintError < StandardError + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb new file mode 100644 index 000000000000..6aec80f59f05 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Errors + class TypeError < StandardError + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb new file mode 100644 index 000000000000..d35df463e5b0 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Http + # @api private + class BaseRequest + attr_reader :base_url, :path, :method, :headers, :query, :request_options + + # @param base_url [String] The base URL for the request + # @param path [String] The path for the request + # @param method [String] The HTTP method for the request (:get, :post, etc.) + # @param headers [Hash] Additional headers for the request (optional) + # @param query [Hash] Query parameters for the request (optional) + # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] + def initialize(base_url:, path:, method:, headers: {}, query: {}, request_options: {}) + @base_url = base_url + @path = path + @method = method + @headers = headers + @query = query + @request_options = request_options + end + + # @return [Hash] The query parameters merged with additional query parameters from request options. + def encode_query + additional_query = @request_options&.dig(:additional_query_parameters) || @request_options&.dig("additional_query_parameters") || {} + @query.merge(additional_query) + end + + # Child classes should implement: + # - encode_headers: Returns the encoded HTTP request headers. + # - encode_body: Returns the encoded HTTP request body. + + private + + # Merges additional_headers from request_options into sdk_headers, filtering out + # any keys that collide with SDK-set or client-protected headers (case-insensitive). + # @param sdk_headers [Hash] Headers set by the SDK for this request type. + # @param protected_keys [Array] Additional header keys that must not be overridden. + # @return [Hash] The merged headers. + def merge_additional_headers(sdk_headers, protected_keys: []) + additional_headers = @request_options&.dig(:additional_headers) || @request_options&.dig("additional_headers") || {} + all_protected = (sdk_headers.keys + protected_keys).to_set { |k| k.to_s.downcase } + filtered = additional_headers.reject { |key, _| all_protected.include?(key.to_s.downcase) } + sdk_headers.merge(filtered) + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb new file mode 100644 index 000000000000..482ab9517714 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Http + # @api private + class RawClient + # Default HTTP status codes that trigger a retry + RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504, 521, 522, 524].freeze + # Initial delay between retries in seconds + INITIAL_RETRY_DELAY = 0.5 + # Maximum delay between retries in seconds + MAX_RETRY_DELAY = 60.0 + # Jitter factor for randomizing retry delays (20%) + JITTER_FACTOR = 0.2 + + # @return [String] The base URL for requests + attr_reader :base_url + + # @param base_url [String] The base url for the request. + # @param max_retries [Integer] The number of times to retry a failed request, defaults to 2. + # @param timeout [Float] The timeout for the request, defaults to 60.0 seconds. + # @param headers [Hash] The headers for the request. + def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) + @base_url = base_url + @max_retries = max_retries + @timeout = timeout + @default_headers = { + "X-Fern-Language": "Ruby", + "X-Fern-SDK-Name": "seed", + "X-Fern-SDK-Version": "0.0.1" + }.merge(headers) + end + + # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. + # @return [HTTP::Response] The HTTP response. + def send(request) + url = build_url(request) + attempt = 0 + response = nil + + loop do + http_request = build_http_request( + url:, + method: request.method, + headers: request.encode_headers(protected_keys: @default_headers.keys), + body: request.encode_body + ) + + conn = connect(url) + conn.open_timeout = @timeout + conn.read_timeout = @timeout + conn.write_timeout = @timeout + conn.continue_timeout = @timeout + + response = conn.request(http_request) + + break unless should_retry?(response, attempt) + + delay = retry_delay(response, attempt) + sleep(delay) + attempt += 1 + end + + response + end + + # Determines if a request should be retried based on the response status code. + # @param response [Net::HTTPResponse] The HTTP response. + # @param attempt [Integer] The current retry attempt (0-indexed). + # @return [Boolean] Whether the request should be retried. + def should_retry?(response, attempt) + return false if attempt >= @max_retries + + status = response.code.to_i + RETRYABLE_STATUSES.include?(status) + end + + # Calculates the delay before the next retry attempt using exponential backoff with jitter. + # Respects Retry-After header if present. + # @param response [Net::HTTPResponse] The HTTP response. + # @param attempt [Integer] The current retry attempt (0-indexed). + # @return [Float] The delay in seconds before the next retry. + def retry_delay(response, attempt) + # Check for Retry-After header (can be seconds or HTTP date) + retry_after = response["Retry-After"] + if retry_after + delay = parse_retry_after(retry_after) + return [delay, MAX_RETRY_DELAY].min if delay&.positive? + end + + # Exponential backoff with jitter: base_delay * 2^attempt + base_delay = INITIAL_RETRY_DELAY * (2**attempt) + add_jitter([base_delay, MAX_RETRY_DELAY].min) + end + + # Parses the Retry-After header value. + # @param value [String] The Retry-After header value (seconds or HTTP date). + # @return [Float, nil] The delay in seconds, or nil if parsing fails. + def parse_retry_after(value) + # Try parsing as integer (seconds) + seconds = Integer(value, exception: false) + return seconds.to_f if seconds + + # Try parsing as HTTP date + begin + retry_time = Time.httpdate(value) + delay = retry_time - Time.now + delay.positive? ? delay : nil + rescue ArgumentError + nil + end + end + + # Adds random jitter to a delay value. + # @param delay [Float] The base delay in seconds. + # @return [Float] The delay with jitter applied. + def add_jitter(delay) + jitter = delay * JITTER_FACTOR * (rand - 0.5) * 2 + [delay + jitter, 0].max + end + + LOCALHOST_HOSTS = %w[localhost 127.0.0.1 [::1]].freeze + + # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. + # @return [URI::Generic] The URL. + def build_url(request) + encoded_query = request.encode_query + + # If the path is already an absolute URL, use it directly + if request.path.start_with?("http://", "https://") + url = request.path + url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? + parsed = URI.parse(url) + validate_https!(parsed) + return parsed + end + + path = request.path.start_with?("/") ? request.path[1..] : request.path + base = request.base_url || @base_url + url = "#{base.chomp("/")}/#{path}" + url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? + parsed = URI.parse(url) + validate_https!(parsed) + parsed + end + + # Raises if the URL uses http:// for a non-localhost host, which would + # send authentication credentials in plaintext. + # @param url [URI::Generic] The parsed URL. + def validate_https!(url) + return if url.scheme != "http" + return if LOCALHOST_HOSTS.include?(url.host) + + raise ArgumentError, "Refusing to send request to non-HTTPS URL: #{url}. " \ + "HTTP is only allowed for localhost. Use HTTPS or pass a localhost URL." + end + + # @param url [URI::Generic] The url to the resource. + # @param method [String] The HTTP method to use. + # @param headers [Hash] The headers for the request. + # @param body [String, nil] The body for the request. + # @return [HTTP::Request] The HTTP request. + def build_http_request(url:, method:, headers: {}, body: nil) + request = Net::HTTPGenericRequest.new( + method, + !body.nil?, + method != "HEAD", + url + ) + + request_headers = @default_headers.merge(headers) + request_headers.each { |name, value| request[name] = value } + request.body = body if body + + request + end + + # @param query [Hash] The query for the request. + # @return [String, nil] The encoded query. + def encode_query(query) + query.to_h.empty? ? nil : URI.encode_www_form(query) + end + + # @param url [URI::Generic] The url to connect to. + # @return [Net::HTTP] The HTTP connection. + def connect(url) + is_https = (url.scheme == "https") + + port = if url.port + url.port + elsif is_https + Net::HTTP.https_default_port + else + Net::HTTP.http_default_port + end + + http = Net::HTTP.new(url.host, port) + http.use_ssl = is_https + http.verify_mode = OpenSSL::SSL::VERIFY_PEER if is_https + # NOTE: We handle retries at the application level with HTTP status code awareness, + # so we set max_retries to 0 to disable Net::HTTP's built-in network-level retries. + http.max_retries = 0 + http + end + + # @return [String] + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} @base_url=#{@base_url.inspect}>" + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb new file mode 100644 index 000000000000..ab627ffc7025 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Seed + module Internal + class CursorItemIterator < ItemIterator + # Instantiates a CursorItemIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields individual items from it. + # + # @param initial_cursor [String] The initial cursor to use when iterating, if any. + # @param cursor_field [Symbol] The field in API responses to extract the next cursor from. + # @param item_field [Symbol] The field in API responses to extract the items to iterate over. + # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. + # @return [Seed::Internal::CursorItemIterator] + def initialize(initial_cursor:, cursor_field:, item_field:, &) + super() + @item_field = item_field + @page_iterator = CursorPageIterator.new(initial_cursor:, cursor_field:, &) + @page = nil + end + + # Returns the CursorPageIterator mediating access to the underlying API. + # + # @return [Seed::Internal::CursorPageIterator] + def pages + @page_iterator + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb new file mode 100644 index 000000000000..f479a749fef9 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Seed + module Internal + class CursorPageIterator + include Enumerable + + # Instantiates a CursorPageIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields pages of items. + # + # @param initial_cursor [String] The initial cursor to use when iterating, if any. + # @param cursor_field [Symbol] The name of the field in API responses to extract the next cursor from. + # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. + # @return [Seed::Internal::CursorPageIterator] + def initialize(initial_cursor:, cursor_field:, &block) + @need_initial_load = initial_cursor.nil? + @cursor = initial_cursor + @cursor_field = cursor_field + @get_next_page = block + end + + # Iterates over each page returned by the API. + # + # @param block [Proc] The block which each retrieved page is yielded to. + # @return [NilClass] + def each(&block) + while (page = next_page) + block.call(page) + end + end + + # Whether another page will be available from the API. + # + # @return [Boolean] + def next? + @need_initial_load || !@cursor.nil? + end + + # Retrieves the next page from the API. + # + # @return [Boolean] + def next_page + return if !@need_initial_load && @cursor.nil? + + @need_initial_load = false + fetched_page = @get_next_page.call(@cursor) + @cursor = fetched_page.send(@cursor_field) + fetched_page + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb new file mode 100644 index 000000000000..1284fb0fd367 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Seed + module Internal + class ItemIterator + include Enumerable + + # Iterates over each item returned by the API. + # + # @param block [Proc] The block which each retrieved item is yielded to. + # @return [NilClass] + def each(&block) + while (item = next_element) + block.call(item) + end + end + + # Whether another item will be available from the API. + # + # @return [Boolean] + def next? + load_next_page if @page.nil? + return false if @page.nil? + + return true if any_items_in_cached_page? + + load_next_page + any_items_in_cached_page? + end + + # Retrieves the next item from the API. + def next_element + item = next_item_from_cached_page + return item if item + + load_next_page + next_item_from_cached_page + end + + private + + def next_item_from_cached_page + return unless @page + + @page.send(@item_field).shift + end + + def any_items_in_cached_page? + return false unless @page + + !@page.send(@item_field).empty? + end + + def load_next_page + @page = @page_iterator.next_page + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb new file mode 100644 index 000000000000..f8840246686d --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Seed + module Internal + class OffsetItemIterator < ItemIterator + # Instantiates an OffsetItemIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields the individual items from it. + # + # @param initial_page [Integer] The initial page or offset to start from when iterating. + # @param item_field [Symbol] The name of the field in API responses to extract the items to iterate over. + # @param has_next_field [Symbol] The name of the field in API responses containing a boolean of whether another page exists. + # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) + # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. + # + # @return [Seed::Internal::OffsetItemIterator] + def initialize(initial_page:, item_field:, has_next_field:, step:, &) + super() + @item_field = item_field + @page_iterator = OffsetPageIterator.new(initial_page:, item_field:, has_next_field:, step:, &) + @page = nil + end + + # Returns the OffsetPageIterator that is mediating access to the underlying API. + # + # @return [Seed::Internal::OffsetPageIterator] + def pages + @page_iterator + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb new file mode 100644 index 000000000000..051b65c5774c --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Seed + module Internal + class OffsetPageIterator + include Enumerable + + # Instantiates an OffsetPageIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields pages of items from it. + # + # @param initial_page [Integer] The initial page to use when iterating, if any. + # @param item_field [Symbol] The field to pull the list of items to iterate over. + # @param has_next_field [Symbol] The field to pull the boolean of whether a next page exists from, if any. + # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) + # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. + # @return [Seed::Internal::OffsetPageIterator] + def initialize(initial_page:, item_field:, has_next_field:, step:, &block) + @page_number = initial_page || (step ? 0 : 1) + @item_field = item_field + @has_next_field = has_next_field + @step = step + @get_next_page = block + + # A cache of whether the API has another page, if it gives us that information... + @next_page = nil + # ...or the actual next page, preloaded, if it doesn't. + @has_next_page = nil + end + + # Iterates over each page returned by the API. + # + # @param block [Proc] The block which each retrieved page is yielded to. + # @return [NilClass] + def each(&block) + while (page = next_page) + block.call(page) + end + end + + # Whether another page will be available from the API. + # + # @return [Boolean] + def next? + return @has_next_page unless @has_next_page.nil? + return true if @next_page + + fetched_page = @get_next_page.call(@page_number) + fetched_page_items = fetched_page&.send(@item_field) + if fetched_page_items.nil? || fetched_page_items.empty? + @has_next_page = false + else + @next_page = fetched_page + true + end + end + + # Returns the next page from the API. + def next_page + return nil if @page_number.nil? + + if @next_page + this_page = @next_page + @next_page = nil + else + this_page = @get_next_page.call(@page_number) + end + + @has_next_page = this_page&.send(@has_next_field) if @has_next_field + + items = this_page.send(@item_field) + if items.nil? || items.empty? + @page_number = nil + return nil + elsif @step + @page_number += items.length + else + @page_number += 1 + end + + this_page + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb new file mode 100644 index 000000000000..667ceae8ac59 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Seed + module Internal + module JSON + # @api private + class Request < Seed::Internal::Http::BaseRequest + attr_reader :body + + # @param base_url [String] The base URL for the request + # @param path [String] The path for the request + # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) + # @param headers [Hash] Additional headers for the request (optional) + # @param query [Hash] Query parameters for the request (optional) + # @param body [Object, nil] The JSON request body (optional) + # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] + def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) + super(base_url:, path:, method:, headers:, query:, request_options:) + + @body = body + end + + # @return [Hash] The encoded HTTP request headers. + # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) + # that must not be overridden by additional_headers from request_options. + def encode_headers(protected_keys: []) + sdk_headers = { + "Content-Type" => "application/json", + "Accept" => "application/json" + }.merge(@headers) + merge_additional_headers(sdk_headers, protected_keys:) + end + + # @return [String, nil] The encoded HTTP request body. + def encode_body + @body.nil? ? nil : ::JSON.generate(@body) + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb new file mode 100644 index 000000000000..f80a15fb962c --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Seed + module Internal + module JSON + module Serializable + # Loads data from JSON into its deserialized form + # + # @param str [String] Raw JSON to load into an object + # @return [Object] + def load(str) + raise NotImplementedError + end + + # Dumps data from its deserialized form into JSON + # + # @param value [Object] The deserialized value + # @return [String] + def dump(value) + raise NotImplementedError + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb new file mode 100644 index 000000000000..307ad7436a57 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Multipart + # Encodes parameters into a `multipart/form-data` payload as described by RFC + # 2388: + # + # https://tools.ietf.org/html/rfc2388 + # + # This is most useful for transferring file-like objects. + # + # Parameters should be added with `#encode`. When ready, use `#body` to get + # the encoded result and `#content_type` to get the value that should be + # placed in the `Content-Type` header of a subsequent request (which includes + # a boundary value). + # + # This abstraction is heavily inspired by Stripe's multipart/form-data implementation, + # which can be found here: + # + # https://github.com/stripe/stripe-ruby/blob/ca00b676f04ac421cf5cb5ff0325f243651677b6/lib/stripe/multipart_encoder.rb#L18 + # + # @api private + class Encoder + CONTENT_TYPE = "multipart/form-data" + CRLF = "\r\n" + + attr_reader :boundary, :body + + def initialize + # Chose the same number of random bytes that Go uses in its standard + # library implementation. Easily enough entropy to ensure that it won't + # be present in a file we're sending. + @boundary = SecureRandom.hex(30) + + @body = String.new + @closed = false + @first_field = true + end + + # Gets the content type string including the boundary. + # + # @return [String] The content type with boundary + def content_type + "#{CONTENT_TYPE}; boundary=#{@boundary}" + end + + # Encode the given FormData object into a multipart/form-data payload. + # + # @param form_data [FormData] The form data to encode + # @return [String] The encoded body. + def encode(form_data) + return "" if form_data.parts.empty? + + form_data.parts.each do |part| + write_part(part) + end + close + + @body + end + + # Writes a FormDataPart to the encoder. + # + # @param part [FormDataPart] The part to write + # @return [nil] + def write_part(part) + raise "Cannot write to closed encoder" if @closed + + write_field( + name: part.name, + data: part.contents, + filename: part.filename, + headers: part.headers + ) + + nil + end + + # Writes a field to the encoder. + # + # @param name [String] The field name + # @param data [String] The field data + # @param filename [String, nil] Optional filename + # @param headers [Hash, nil] Optional additional headers + # @return [nil] + def write_field(name:, data:, filename: nil, headers: nil) + raise "Cannot write to closed encoder" if @closed + + if @first_field + @first_field = false + else + @body << CRLF + end + + @body << "--#{@boundary}#{CRLF}" + @body << %(Content-Disposition: form-data; name="#{escape(name.to_s)}") + @body << %(; filename="#{escape(filename)}") if filename + @body << CRLF + + if headers + headers.each do |key, value| + @body << "#{key}: #{value}#{CRLF}" + end + elsif filename + # Default content type for files. + @body << "Content-Type: application/octet-stream#{CRLF}" + end + + @body << CRLF + @body << data.to_s + + nil + end + + # Finalizes the encoder by writing the final boundary. + # + # @return [nil] + def close + raise "Encoder already closed" if @closed + + @body << CRLF + @body << "--#{@boundary}--" + @closed = true + + nil + end + + private + + # Escapes quotes for use in header values and replaces line breaks with spaces. + # + # @param str [String] The string to escape + # @return [String] The escaped string + def escape(str) + str.to_s.gsub('"', "%22").tr("\n", " ").tr("\r", " ") + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb new file mode 100644 index 000000000000..5be1bb25341f --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Multipart + # @api private + class FormData + # @return [Array] The parts in this multipart form data. + attr_reader :parts + + # @return [Encoder] The encoder for this multipart form data. + private attr_reader :encoder + + def initialize + @encoder = Encoder.new + @parts = [] + end + + # Adds a new part to the multipart form data. + # + # @param name [String] The name of the form field + # @param value [String, Integer, Float, Boolean, #read] The value of the field + # @param content_type [String, nil] Optional content type + # @return [self] Returns self for chaining + def add(name:, value:, content_type: nil) + headers = content_type ? { "Content-Type" => content_type } : nil + add_part(FormDataPart.new(name:, value:, headers:)) + end + + # Adds a file to the multipart form data. + # + # @param name [String] The name of the form field + # @param file [#read] The file or readable object + # @param filename [String, nil] Optional filename (defaults to basename of path for File objects) + # @param content_type [String, nil] Optional content type (e.g. "image/png") + # @return [self] Returns self for chaining + def add_file(name:, file:, filename: nil, content_type: nil) + headers = content_type ? { "Content-Type" => content_type } : nil + filename ||= filename_for(file) + add_part(FormDataPart.new(name:, value: file, filename:, headers:)) + end + + # Adds a pre-created part to the multipart form data. + # + # @param part [FormDataPart] The part to add + # @return [self] Returns self for chaining + def add_part(part) + @parts << part + self + end + + # Gets the content type string including the boundary. + # + # @return [String] The content type with boundary. + def content_type + @encoder.content_type + end + + # Encode the multipart form data into a multipart/form-data payload. + # + # @return [String] The encoded body. + def encode + @encoder.encode(self) + end + + private + + def filename_for(file) + if file.is_a?(::File) || file.respond_to?(:path) + ::File.basename(file.path) + elsif file.respond_to?(:name) + file.name + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb new file mode 100644 index 000000000000..de45416ee087 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "securerandom" + +module Seed + module Internal + module Multipart + # @api private + class FormDataPart + attr_reader :name, :contents, :filename, :headers + + # @param name [String] The name of the form field + # @param value [String, Integer, Float, Boolean, File, #read] The value of the field + # @param filename [String, nil] Optional filename for file uploads + # @param headers [Hash, nil] Optional additional headers + def initialize(name:, value:, filename: nil, headers: nil) + @name = name + @contents = convert_to_content(value) + @filename = filename + @headers = headers + end + + # Converts the part to a hash suitable for serialization. + # + # @return [Hash] A hash representation of the part + def to_hash + result = { + name: @name, + contents: @contents + } + result[:filename] = @filename if @filename + result[:headers] = @headers if @headers + result + end + + private + + # Converts various types of values to a content representation + # @param value [String, Integer, Float, Boolean, #read] The value to convert + # @return [String] The string representation of the value + def convert_to_content(value) + if value.respond_to?(:read) + value.read + else + value.to_s + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb new file mode 100644 index 000000000000..9fa80cee01ab --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Multipart + # @api private + class Request < Seed::Internal::Http::BaseRequest + attr_reader :body + + # @param base_url [String] The base URL for the request + # @param path [String] The path for the request + # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) + # @param headers [Hash] Additional headers for the request (optional) + # @param query [Hash] Query parameters for the request (optional) + # @param body [MultipartFormData, nil] The multipart form data for the request (optional) + # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] + def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) + super(base_url:, path:, method:, headers:, query:, request_options:) + + @body = body + end + + # @return [Hash] The encoded HTTP request headers. + # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) + # that must not be overridden by additional_headers from request_options. + def encode_headers(protected_keys: []) + sdk_headers = { + "Content-Type" => @body.content_type + }.merge(@headers) + merge_additional_headers(sdk_headers, protected_keys:) + end + + # @return [String, nil] The encoded HTTP request body. + def encode_body + @body&.encode + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb new file mode 100644 index 000000000000..f3c7c1bd9549 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # An array of a specific type + class Array + include Seed::Internal::Types::Type + + attr_reader :type + + class << self + # Instantiates a new `Array` of a given type + # + # @param type [Object] The member type of this array + # + # @return [Seed::Internal::Types::Array] + def [](type) + new(type) + end + end + + # @api private + def initialize(type) + @type = type + end + + # Coerces a value into this array + # + # @param value [Object] + # @option strict [Boolean] + # @return [::Array] + def coerce(value, strict: strict?) + unless value.is_a?(::Array) + raise Errors::TypeError, "cannot coerce `#{value.class}` to Array<#{type}>" if strict + + return value + end + + value.map do |element| + Utils.coerce(type, element, strict: strict) + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb new file mode 100644 index 000000000000..d4e3277e566f --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + module Boolean + extend Seed::Internal::Types::Union + + member TrueClass + member FalseClass + + # Overrides the base coercion method for enums to allow integer and string values to become booleans + # + # @param value [Object] + # @option strict [Boolean] + # @return [Object] + def self.coerce(value, strict: strict?) + case value + when TrueClass, FalseClass + return value + when Integer + return value == 1 + when String + return %w[1 true].include?(value) + end + + raise Errors::TypeError, "cannot coerce `#{value.class}` to Boolean" if strict + + value + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb new file mode 100644 index 000000000000..72e45e4c1f27 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # Module for defining enums + module Enum + include Type + + # @api private + # + # @return [Array] + def values + @values ||= constants.map { |c| const_get(c) } + end + + # @api private + def finalize! + values + end + + # @api private + def strict? + @strict ||= false + end + + # @api private + def strict! + @strict = true + end + + def coerce(value, strict: strict?) + coerced_value = Utils.coerce(Symbol, value) + + return coerced_value if values.include?(coerced_value) + + raise Errors::TypeError, "`#{value}` not in enum #{self}" if strict + + value + end + + # Parse JSON string and coerce to the enum value + # + # @param str [String] JSON string to parse + # @return [String] The enum value + def load(str) + coerce(::JSON.parse(str)) + end + + def inspect + "#{name}[#{values.join(", ")}]" + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb new file mode 100644 index 000000000000..d8bffa63ac11 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + class Hash + include Type + + attr_reader :key_type, :value_type + + class << self + def [](key_type, value_type) + new(key_type, value_type) + end + end + + def initialize(key_type, value_type) + @key_type = key_type + @value_type = value_type + end + + def coerce(value, strict: strict?) + unless value.is_a?(::Hash) + raise Errors::TypeError, "not hash" if strict + + return value + end + + value.to_h do |k, v| + [Utils.coerce(key_type, k, strict: strict), Utils.coerce(value_type, v, strict: strict)] + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb new file mode 100644 index 000000000000..8caca14ff7ea --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # @abstract + # + # An abstract model that all data objects will inherit from + class Model + include Type + + class << self + # The defined fields for this model + # + # @api private + # + # @return [Hash] + def fields + @fields ||= if self < Seed::Internal::Types::Model + superclass.fields.dup + else + {} + end + end + + # Any extra fields that have been created from instantiation + # + # @api private + # + # @return [Hash] + def extra_fields + @extra_fields ||= {} + end + + # Define a new field on this model + # + # @param name [Symbol] The name of the field + # @param type [Class] Type of the field + # @option optional [Boolean] If it is an optional field + # @option nullable [Boolean] If it is a nullable field + # @option api_name [Symbol, String] Name in the API of this field. When serializing/deserializing, will use + # this field name + # @return [void] + def field(name, type, optional: false, nullable: false, api_name: nil, default: nil) + add_field_definition(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, + default: default) + + define_accessor(name) + define_setter(name) + end + + # Define a new literal for this model + # + # @param name [Symbol] + # @param value [Object] + # @option api_name [Symbol, String] + # @return [void] + def literal(name, value, api_name: nil) + add_field_definition(name: name, type: value.class, optional: false, nullable: false, api_name: api_name, + value: value) + + define_accessor(name) + end + + # Adds a new field definition into the class's fields registry + # + # @api private + # + # @param name [Symbol] + # @param type [Class] + # @option optional [Boolean] + # @return [void] + private def add_field_definition(name:, type:, optional:, nullable:, api_name:, default: nil, value: nil) + fields[name.to_sym] = + Field.new(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, + value: value, default: default) + end + + # Adds a new field definition into the class's extra fields registry + # + # @api private + # + # @param name [Symbol] + # @param type [Class] + # @option required [Boolean] + # @option optional [Boolean] + # @return [void] + def add_extra_field_definition(name:, type:) + return if extra_fields.key?(name.to_sym) + + extra_fields[name.to_sym] = Field.new(name: name, type: type, optional: true, nullable: false) + + define_accessor(name) + define_setter(name) + end + + # @api private + private def define_accessor(name) + method_name = name.to_sym + + define_method(method_name) do + @data[name] + end + end + + # @api private + private def define_setter(name) + method_name = :"#{name}=" + + define_method(method_name) do |val| + @data[name] = val + end + end + + def coerce(value, strict: (respond_to?(:strict?) ? strict? : false)) # rubocop:disable Lint/UnusedMethodArgument + return value if value.is_a?(self) + + return value unless value.is_a?(::Hash) + + new(value) + end + + def load(str) + coerce(::JSON.parse(str, symbolize_names: true)) + end + + def ===(instance) + instance.class.ancestors.include?(self) + end + end + + # Creates a new instance of this model + # TODO: Should all this logic be in `#coerce` instead? + # + # @param values [Hash] + # @option strict [Boolean] + # @return [self] + def initialize(values = {}) + @data = {} + + values = Utils.symbolize_keys(values.dup) + + self.class.fields.each do |field_name, field| + value = values.delete(field.api_name.to_sym) || values.delete(field.api_name) || values.delete(field_name) + + field_value = value || (if field.literal? + field.value + elsif field.default + field.default + end) + + @data[field_name] = Utils.coerce(field.type, field_value) + end + + # Any remaining values in the input become extra fields + values.each do |name, value| + self.class.add_extra_field_definition(name: name, type: value.class) + + @data[name.to_sym] = value + end + end + + def to_h + result = self.class.fields.merge(self.class.extra_fields).each_with_object({}) do |(name, field), acc| + # If there is a value present in the data, use that value + # If there is a `nil` value present in the data, and it is optional but NOT nullable, exclude key altogether + # If there is a `nil` value present in the data, and it is optional and nullable, use the nil value + + value = @data[name] + + next if value.nil? && field.optional && !field.nullable + + if value.is_a?(::Array) + value = value.map { |item| item.respond_to?(:to_h) ? item.to_h : item } + elsif value.respond_to?(:to_h) + value = value.to_h + end + + acc[field.api_name] = value + end + + # Inject union discriminant if this instance was coerced from a discriminated union + # and the discriminant key is not already present in the result + discriminant_key = instance_variable_get(:@_fern_union_discriminant_key) + discriminant_value = instance_variable_get(:@_fern_union_discriminant_value) + result[discriminant_key] = discriminant_value if discriminant_key && discriminant_value && !result.key?(discriminant_key) + + result + end + + def ==(other) + self.class == other.class && to_h == other.to_h + end + + # @return [String] + def inspect + attrs = @data.map do |name, value| + field = self.class.fields[name] || self.class.extra_fields[name] + display_value = field&.sensitive? ? "[REDACTED]" : value.inspect + "#{name}=#{display_value}" + end + + "#<#{self.class.name}:0x#{object_id&.to_s(16)} #{attrs.join(" ")}>" + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb new file mode 100644 index 000000000000..6ce0186f6a5d --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + class Model + # Definition of a field on a model + class Field + SENSITIVE_FIELD_NAMES = %i[ + password secret token api_key apikey access_token refresh_token + client_secret client_id credential bearer authorization + ].freeze + + attr_reader :name, :type, :optional, :nullable, :api_name, :value, :default + + def initialize(name:, type:, optional: false, nullable: false, api_name: nil, value: nil, default: nil) + @name = name.to_sym + @type = type + @optional = optional + @nullable = nullable + @api_name = api_name || name.to_s + @value = value + @default = default + end + + def literal? + !value.nil? + end + + def sensitive? + SENSITIVE_FIELD_NAMES.include?(@name) || + SENSITIVE_FIELD_NAMES.any? { |sensitive| @name.to_s.include?(sensitive.to_s) } + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb new file mode 100644 index 000000000000..5866caf1dbda --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # @abstract + module Type + include Seed::Internal::JSON::Serializable + + # Coerces a value to this type + # + # @param value [unknown] + # @option strict [Boolean] If we should strictly coerce this value + def coerce(value, strict: strict?) + raise NotImplementedError + end + + # Returns if strictness is on for this type, defaults to `false` + # + # @return [Boolean] + def strict? + @strict ||= false + end + + # Enable strictness by default for this type + # + # @return [void] + def strict! + @strict = true + self + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb new file mode 100644 index 000000000000..f3e118a2fa78 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # Define a union between two types + module Union + include Seed::Internal::Types::Type + + def members + @members ||= [] + end + + # Add a member to this union + # + # @param type [Object] + # @option key [Symbol, String] + # @return [void] + def member(type, key: nil) + members.push([key, Utils.wrap_type(type)]) + self + end + + def type_member?(type) + members.any? { |_key, type_fn| type == type_fn.call } + end + + # Set the discriminant for this union + # + # @param key [Symbol, String] + # @return [void] + def discriminant(key) + @discriminant = key + end + + # @api private + private def discriminated? + !@discriminant.nil? + end + + # Check if value matches a type, handling type wrapper instances + # (Internal::Types::Hash and Internal::Types::Array instances) + # + # @param value [Object] + # @param member_type [Object] + # @return [Boolean] + private def type_matches?(value, member_type) + case member_type + when Seed::Internal::Types::Hash + value.is_a?(::Hash) + when Seed::Internal::Types::Array + value.is_a?(::Array) + when Class, Module + value.is_a?(member_type) + else + false + end + end + + # Resolves the type of a value to be one of the members + # + # @param value [Object] + # @return [Class] + private def resolve_member(value) + if discriminated? && value.is_a?(::Hash) + # Try both symbol and string keys for the discriminant + discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) + + return if discriminant_value.nil? + + # Convert to string for consistent comparison + discriminant_str = discriminant_value.to_s + + # First try exact match + members_hash = members.to_h + result = members_hash[discriminant_str]&.call + return result if result + + # Try case-insensitive match as fallback + discriminant_lower = discriminant_str.downcase + matching_keys = members_hash.keys.select { |k| k.to_s.downcase == discriminant_lower } + + # Only use case-insensitive match if exactly one key matches (avoid ambiguity) + return members_hash[matching_keys.first]&.call if matching_keys.length == 1 + + nil + else + # First try exact type matching + result = members.find do |_key, mem| + member_type = Utils.unwrap_type(mem) + type_matches?(value, member_type) + end&.last&.call + + return result if result + + # For Hash values, try to coerce into Model member types + if value.is_a?(::Hash) + members.find do |_key, mem| + member_type = Utils.unwrap_type(mem) + # Check if member_type is a Model class + next unless member_type.is_a?(Class) && member_type <= Model + + # Try to coerce the hash into this model type with strict mode + begin + candidate = Utils.coerce(member_type, value, strict: true) + + # Validate that all required (non-optional) fields are present + # This ensures undiscriminated unions properly distinguish between member types + member_type.fields.each do |field_name, field| + raise Errors::TypeError, "Required field `#{field_name}` missing for union member #{member_type.name}" if candidate.instance_variable_get(:@data)[field_name].nil? && !field.optional + end + + true + rescue Errors::TypeError + false + end + end&.last&.call + end + end + end + + def coerce(value, strict: strict?) + type = resolve_member(value) + + unless type + return value unless strict + + if discriminated? + raise Errors::TypeError, + "value of type `#{value.class}` not member of union #{self}" + end + + raise Errors::TypeError, "could not resolve to member of union #{self}" + end + + coerced = Utils.coerce(type, value, strict: strict) + + # For discriminated unions, store the discriminant info on the coerced instance + # so it can be injected back during serialization (to_h) + if discriminated? && value.is_a?(::Hash) && coerced.is_a?(Model) + discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) + if discriminant_value + coerced.instance_variable_set(:@_fern_union_discriminant_key, @discriminant.to_s) + coerced.instance_variable_set(:@_fern_union_discriminant_value, discriminant_value) + end + end + + coerced + end + + # Parse JSON string and coerce to the correct union member type + # + # @param str [String] JSON string to parse + # @return [Object] Coerced value matching a union member + def load(str) + coerce(::JSON.parse(str, symbolize_names: true)) + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb new file mode 100644 index 000000000000..7b58de956da9 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + module Unknown + include Seed::Internal::Types::Type + + def coerce(value) + value + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb new file mode 100644 index 000000000000..0ac6179e855f --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module Seed + module Internal + module Types + # Utilities for dealing with and checking types + module Utils + # Wraps a type into a type function + # + # @param type [Proc, Object] + # @return [Proc] + def self.wrap_type(type) + case type + when Proc + type + else + -> { type } + end + end + + # Resolves a type or type function into a type + # + # @param type [Proc, Object] + # @return [Object] + def self.unwrap_type(type) + type.is_a?(Proc) ? type.call : type + end + + def self.coerce(target, value, strict: false) + type = unwrap_type(target) + + case type + in Array + case value + when ::Array + return type.coerce(value, strict: strict) + when Set, ::Hash + return coerce(type, value.to_a) + end + in Hash + case value + when ::Hash + return type.coerce(value, strict: strict) + when ::Array + return coerce(type, value.to_h) + end + in ->(t) { t <= NilClass } + return nil + in ->(t) { t <= String } + case value + when String, Symbol, Numeric, TrueClass, FalseClass + return value.to_s + end + in ->(t) { t <= Symbol } + case value + when Symbol, String + return value.to_sym + end + in ->(t) { t <= Integer } + case value + when Numeric, String, Time + return value.to_i + end + in ->(t) { t <= Float } + case value + when Numeric, Time, String + return value.to_f + end + in ->(t) { t <= Model } + case value + when type + return value + when ::Hash + return type.coerce(value, strict: strict) + end + in Module + case type + in ->(t) { + t.singleton_class.included_modules.include?(Enum) || + t.singleton_class.included_modules.include?(Union) + } + return type.coerce(value, strict: strict) + else + value # rubocop:disable Lint/Void + end + else + value # rubocop:disable Lint/Void + end + + raise Errors::TypeError, "cannot coerce value of type `#{value.class}` to `#{target}`" if strict + + value + end + + def self.symbolize_keys(hash) + hash.transform_keys(&:to_sym) + end + + # Converts camelCase keys to snake_case symbols + # This allows SDK methods to accept both snake_case and camelCase keys + # e.g., { refundMethod: ... } becomes { refund_method: ... } + # + # @param hash [Hash] + # @return [Hash] + def self.normalize_keys(hash) + hash.transform_keys do |key| + key_str = key.to_s + # Convert camelCase to snake_case + snake_case = key_str.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase + snake_case.to_sym + end + end + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb new file mode 100644 index 000000000000..2df8e8d92220 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Seed + module Types + # Common audit metadata. + class AuditInfo < Internal::Types::Model + field :created_by, -> { String }, optional: true, nullable: false, api_name: "createdBy" + field :created_date_time, -> { String }, optional: true, nullable: false, api_name: "createdDateTime" + field :modified_by, -> { String }, optional: true, nullable: false, api_name: "modifiedBy" + field :modified_date_time, -> { String }, optional: true, nullable: false, api_name: "modifiedDateTime" + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb new file mode 100644 index 000000000000..8e6b5538aad2 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class BaseOrg < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :metadata, -> { Seed::Types::BaseOrgMetadata }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb new file mode 100644 index 000000000000..4167d5d9464f --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class BaseOrgMetadata < Internal::Types::Model + field :region, -> { String }, optional: false, nullable: false + field :tier, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb new file mode 100644 index 000000000000..3603bc98cbc5 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Seed + module Types + class CombinedEntity < Internal::Types::Model + field :status, -> { Seed::Types::CombinedEntityStatus }, optional: false, nullable: false + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: true, nullable: false + field :summary, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb new file mode 100644 index 000000000000..ce445c2e0f19 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Seed + module Types + module CombinedEntityStatus + extend Seed::Internal::Types::Enum + + ACTIVE = "active" + ARCHIVED = "archived" + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb new file mode 100644 index 000000000000..bf18865e61b5 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class Describable < Internal::Types::Model + field :name, -> { String }, optional: true, nullable: false + field :summary, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb new file mode 100644 index 000000000000..5839792d7c2c --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Seed + module Types + class DetailedOrg < Internal::Types::Model + field :metadata, -> { Seed::Types::DetailedOrgMetadata }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb new file mode 100644 index 000000000000..5c5a311e208c --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class DetailedOrgMetadata < Internal::Types::Model + field :region, -> { String }, optional: false, nullable: false + field :domain, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb new file mode 100644 index 000000000000..420ec1d027dc --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class Identifiable < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb new file mode 100644 index 000000000000..22f80e523436 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Seed + module Types + class Organization < Internal::Types::Model + field :name, -> { String }, optional: false, nullable: false + field :id, -> { String }, optional: false, nullable: false + field :metadata, -> { Seed::Types::BaseOrgMetadata }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb new file mode 100644 index 000000000000..10755d50c291 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class PaginatedResult < Internal::Types::Model + field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false + field :results, -> { Internal::Types::Array[Object] }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb new file mode 100644 index 000000000000..925959b21486 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class PagingCursors < Internal::Types::Model + field :next_, -> { String }, optional: false, nullable: false, api_name: "next" + field :previous, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb new file mode 100644 index 000000000000..be802bd7fd55 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Seed + module Types + module RuleExecutionContext + extend Seed::Internal::Types::Enum + + PROD = "prod" + STAGING = "staging" + DEV = "dev" + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb new file mode 100644 index 000000000000..7f12d62b7420 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Seed + module Types + class RuleResponse < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: false, nullable: false + field :status, -> { Seed::Types::RuleResponseStatus }, optional: false, nullable: false + field :execution_context, -> { Seed::Types::RuleExecutionContext }, optional: true, nullable: false, api_name: "executionContext" + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb new file mode 100644 index 000000000000..609f51bfabb4 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Seed + module Types + module RuleResponseStatus + extend Seed::Internal::Types::Enum + + ACTIVE = "active" + INACTIVE = "inactive" + DRAFT = "draft" + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb new file mode 100644 index 000000000000..798fc21f7888 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Seed + module Types + class RuleType < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :name, -> { String }, optional: false, nullable: false + field :description, -> { String }, optional: true, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb new file mode 100644 index 000000000000..7195d2c81c41 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class RuleTypeSearchResponse < Internal::Types::Model + field :results, -> { Internal::Types::Array[Seed::Types::RuleType] }, optional: true, nullable: false + field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/user.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/user.rb new file mode 100644 index 000000000000..9d5a150b6803 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/user.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class User < Internal::Types::Model + field :id, -> { String }, optional: false, nullable: false + field :email, -> { String }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb new file mode 100644 index 000000000000..b1c494f6bb39 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Seed + module Types + class UserSearchResponse < Internal::Types::Model + field :results, -> { Internal::Types::Array[Seed::Types::User] }, optional: true, nullable: false + field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false + end + end +end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/version.rb b/seed/ruby-sdk-v2/allof/lib/seed/version.rb new file mode 100644 index 000000000000..00dd45cdd958 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/lib/seed/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Seed + VERSION = "0.0.1" +end diff --git a/seed/ruby-sdk-v2/allof/reference.md b/seed/ruby-sdk-v2/allof/reference.md new file mode 100644 index 000000000000..2a0eb7a77a05 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/reference.md @@ -0,0 +1,228 @@ +# Reference +
client.search_rule_types() -> Seed::Types::RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.search_rule_types +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `String` + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.create_rule(request) -> Seed::Types::RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.create_rule( + name: "name", + execution_context: "prod" +) +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `String` + +
+
+ +
+
+ +**execution_context:** `Seed::Types::RuleExecutionContext` + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.list_users() -> Seed::Types::UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.list_users +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.get_entity() -> Seed::Types::CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.get_entity +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.get_organization() -> Seed::Types::Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```ruby +client.get_organization +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `Seed::RequestOptions` + +
+
+
+
+ + +
+
+
+ diff --git a/seed/ruby-sdk-v2/allof/seed.gemspec b/seed/ruby-sdk-v2/allof/seed.gemspec new file mode 100644 index 000000000000..cffe9f840a9b --- /dev/null +++ b/seed/ruby-sdk-v2/allof/seed.gemspec @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative "lib/seed/version" +require_relative "custom.gemspec" + +# NOTE: A handful of these fields are required as part of the Ruby specification. +# You can change them here or overwrite them in the custom gemspec file. +Gem::Specification.new do |spec| + spec.name = "fern_allof" + spec.authors = ["Seed"] + spec.version = Seed::VERSION + spec.summary = "Ruby client library for the Seed API" + spec.description = "The Seed Ruby library provides convenient access to the Seed API from Ruby." + spec.required_ruby_version = ">= 3.3.0" + spec.metadata["rubygems_mfa_required"] = "true" + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + gemspec = File.basename(__FILE__) + spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| + ls.readlines("\x0", chomp: true).reject do |f| + (f == gemspec) || + f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile]) + end + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + # For more information and examples about making a new gem, check out our + # guide at: https://bundler.io/guides/creating_gem.html + + # Load custom gemspec configuration if it exists + custom_gemspec_file = File.join(__dir__, "custom.gemspec.rb") + add_custom_gemspec_data(spec) if File.exist?(custom_gemspec_file) +end diff --git a/seed/ruby-sdk-v2/allof/snippet.json b/seed/ruby-sdk-v2/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/ruby-sdk-v2/allof/test/custom.test.rb b/seed/ruby-sdk-v2/allof/test/custom.test.rb new file mode 100644 index 000000000000..4bd57989d43d --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/custom.test.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# This is a custom test file, if you wish to add more tests +# to your SDK. +# Be sure to mark this file in `.fernignore`. +# +# If you include example requests/responses in your fern definition, +# you will have tests automatically generated for you. + +# This test is run via command line: rake customtest +describe "Custom Test" do + it "Default" do + refute false + end +end diff --git a/seed/ruby-sdk-v2/allof/test/test_helper.rb b/seed/ruby-sdk-v2/allof/test/test_helper.rb new file mode 100644 index 000000000000..b086fe6d76ec --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/test_helper.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative "../lib/seed" diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb new file mode 100644 index 000000000000..5008f6abf69f --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require "stringio" +require "json" +require "test_helper" + +NUMBERS = (1..65).to_a +PageResponse = Struct.new(:cards, :next_cursor, keyword_init: true) + +class CursorItemIteratorTest < Minitest::Test + def make_iterator(initial_cursor:) + @times_called = 0 + + Seed::Internal::CursorItemIterator.new(initial_cursor:, cursor_field: :next_cursor, item_field: :cards) do |cursor| + @times_called += 1 + cursor ||= 0 + next_cursor = cursor + 10 + PageResponse.new( + cards: NUMBERS[cursor...next_cursor], + next_cursor: next_cursor < NUMBERS.length ? next_cursor : nil + ) + end + end + + def test_item_iterator_can_iterate_to_exhaustion + iterator = make_iterator(initial_cursor: 0) + + assert_equal NUMBERS, iterator.to_a + assert_equal 7, @times_called + + iterator = make_iterator(initial_cursor: 10) + + assert_equal (11..65).to_a, iterator.to_a + + iterator = make_iterator(initial_cursor: 5) + + assert_equal (6..65).to_a, iterator.to_a + end + + def test_item_iterator_can_work_without_an_initial_cursor + iterator = make_iterator(initial_cursor: nil) + + assert_equal NUMBERS, iterator.to_a + assert_equal 7, @times_called + end + + def test_items_iterator_iterates_lazily + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + assert_equal 1, iterator.first + assert_equal 1, @times_called + + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + assert_equal (1..15).to_a, iterator.first(15) + assert_equal 2, @times_called + + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + iterator.each do |card| + break if card >= 15 + end + + assert_equal 2, @times_called + end + + def test_items_iterator_implements_enumerable + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + doubled = iterator.map { |card| card * 2 } + + assert_equal 7, @times_called + assert_equal NUMBERS.length, doubled.length + end + + def test_items_iterator_can_be_advanced_manually + iterator = make_iterator(initial_cursor: 0) + + assert_equal 0, @times_called + + items = [] + expected_times_called = 0 + while (item = iterator.next_element) + expected_times_called += 1 if (item % 10) == 1 + + assert_equal expected_times_called, @times_called + assert_equal item != NUMBERS.last, iterator.next?, "#{item} #{iterator}" + items.push(item) + end + + assert_equal 7, @times_called + assert_equal NUMBERS, items + end + + def test_pages_iterator + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal( + [ + (1..10).to_a, + (11..20).to_a, + (21..30).to_a, + (31..40).to_a, + (41..50).to_a, + (51..60).to_a, + (61..65).to_a + ], + iterator.to_a.map(&:cards) + ) + + iterator = make_iterator(initial_cursor: 10).pages + + assert_equal( + [ + (11..20).to_a, + (21..30).to_a, + (31..40).to_a, + (41..50).to_a, + (51..60).to_a, + (61..65).to_a + ], + iterator.to_a.map(&:cards) + ) + end + + def test_pages_iterator_can_work_without_an_initial_cursor + iterator = make_iterator(initial_cursor: nil).pages + + assert_equal 7, iterator.to_a.length + assert_equal 7, @times_called + end + + def test_pages_iterator_iterates_lazily + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + iterator.first + + assert_equal 1, @times_called + + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + assert_equal 2, iterator.first(2).length + assert_equal 2, @times_called + end + + def test_pages_iterator_knows_whether_another_page_is_upcoming + iterator = make_iterator(initial_cursor: 0).pages + + iterator.each_with_index do |_page, index| + assert_equal index + 1, @times_called + assert_equal index < 6, iterator.next? + end + end + + def test_pages_iterator_can_be_advanced_manually + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + + lengths = [] + expected_times_called = 0 + while (page = iterator.next_page) + expected_times_called += 1 + + assert_equal expected_times_called, @times_called + lengths.push(page.cards.length) + end + + assert_equal 7, @times_called + assert_equal [10, 10, 10, 10, 10, 10, 5], lengths + end + + def test_pages_iterator_implements_enumerable + iterator = make_iterator(initial_cursor: 0).pages + + assert_equal 0, @times_called + lengths = iterator.map { |page| page.cards.length } + + assert_equal 7, @times_called + assert_equal [10, 10, 10, 10, 10, 10, 5], lengths + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb new file mode 100644 index 000000000000..92576b820128 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require "stringio" +require "json" +require "test_helper" + +OffsetPageResponse = Struct.new(:items, :has_next, keyword_init: true) +TestIteratorConfig = Struct.new( + :step, + :has_next_field, + :total_item_count, + :per_page, + :initial_page +) do + def first_item_returned + if step + (initial_page || 0) + 1 + else + (((initial_page || 1) - 1) * per_page) + 1 + end + end +end + +LAZY_TEST_ITERATOR_CONFIG = TestIteratorConfig.new(initial_page: 1, step: false, has_next_field: :has_next, total_item_count: 65, per_page: 10) +ALL_TEST_ITERATOR_CONFIGS = [true, false].map do |step| + [:has_next, nil].map do |has_next_field| + [0, 5, 10, 60, 63].map do |total_item_count| + [5, 10].map do |per_page| + initial_pages = [nil, 3, 100] + initial_pages << (step ? 0 : 1) + + initial_pages.map do |initial_page| + TestIteratorConfig.new( + step: step, + has_next_field: has_next_field, + total_item_count: total_item_count, + per_page: per_page, + initial_page: initial_page + ) + end + end + end + end +end.flatten + +class OffsetItemIteratorTest < Minitest::Test + def make_iterator(config) + @times_called = 0 + + items = (1..config.total_item_count).to_a + + Seed::Internal::OffsetItemIterator.new( + initial_page: config.initial_page, + item_field: :items, + has_next_field: config.has_next_field, + step: config.step + ) do |page| + @times_called += 1 + + slice_start = config.step ? page : (page - 1) * config.per_page + slice_end = slice_start + config.per_page + + output = { + items: items[slice_start...slice_end] + } + output[config.has_next_field] = slice_end < items.length if config.has_next_field + + OffsetPageResponse.new(**output) + end + end + + def test_item_iterator_can_iterate_to_exhaustion + ALL_TEST_ITERATOR_CONFIGS.each do |config| + iterator = make_iterator(config) + + assert_equal (config.first_item_returned..config.total_item_count).to_a, iterator.to_a + end + end + + def test_items_iterator_can_be_advanced_manually_and_has_accurate_has_next + ALL_TEST_ITERATOR_CONFIGS.each do |config| + iterator = make_iterator(config) + items = [] + + while (item = iterator.next_element) + assert_equal(item != config.total_item_count, iterator.next?, "#{item} #{iterator}") + items.push(item) + end + + assert_equal (config.first_item_returned..config.total_item_count).to_a, items + end + end + + def test_pages_iterator_can_be_advanced_manually_and_has_accurate_has_next + ALL_TEST_ITERATOR_CONFIGS.each do |config| + iterator = make_iterator(config).pages + pages = [] + + loop do + has_next_output = iterator.next? + page = iterator.next_page + + assert_equal(has_next_output, !page.nil?, "next? was inaccurate: #{config} #{iterator.inspect}") + break if page.nil? + + pages.push(page) + end + + assert_equal pages, make_iterator(config).pages.to_a + end + end + + def test_items_iterator_iterates_lazily + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) + + assert_equal 0, @times_called + assert_equal 1, iterator.first + assert_equal 1, @times_called + + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) + + assert_equal 0, @times_called + assert_equal (1..15).to_a, iterator.first(15) + assert_equal 2, @times_called + + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) + + assert_equal 0, @times_called + iterator.each do |card| + break if card >= 15 + end + + assert_equal 2, @times_called + end + + def test_pages_iterator_iterates_lazily + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages + + assert_equal 0, @times_called + iterator.first + + assert_equal 1, @times_called + + iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages + + assert_equal 0, @times_called + assert_equal 3, iterator.first(3).length + assert_equal 3, @times_called + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb new file mode 100644 index 000000000000..e7e6571f03ee --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Array do + module TestArray + StringArray = Seed::Internal::Types::Array[String] + end + + describe "#initialize" do + it "sets the type" do + assert_equal String, TestArray::StringArray.type + end + end + + describe "#coerce" do + it "does not perform coercion if not an array" do + assert_equal 1, TestArray::StringArray.coerce(1) + end + + it "raises an error if not an array and strictness is on" do + assert_raises Seed::Internal::Errors::TypeError do + TestArray::StringArray.coerce(1, strict: true) + end + end + + it "coerces the elements" do + assert_equal %w[foobar 1 true], TestArray::StringArray.coerce(["foobar", 1, true]) + end + + it "raises an error if element of array is not coercable and strictness is on" do + assert_raises Seed::Internal::Errors::TypeError do + TestArray::StringArray.coerce([Object.new], strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb new file mode 100644 index 000000000000..cba18e48765b --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Boolean do + describe ".coerce" do + it "coerces true/false" do + assert Seed::Internal::Types::Boolean.coerce(true) + refute Seed::Internal::Types::Boolean.coerce(false) + end + + it "coerces an Integer" do + assert Seed::Internal::Types::Boolean.coerce(1) + refute Seed::Internal::Types::Boolean.coerce(0) + end + + it "coerces a String" do + assert Seed::Internal::Types::Boolean.coerce("1") + assert Seed::Internal::Types::Boolean.coerce("true") + refute Seed::Internal::Types::Boolean.coerce("0") + end + + it "passes through other values with strictness off" do + obj = Object.new + + assert_equal obj, Seed::Internal::Types::Boolean.coerce(obj) + end + + it "raises an error with other values with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + Seed::Internal::Types::Boolean.coerce(Object.new, strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb new file mode 100644 index 000000000000..e8d89bce467f --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Enum do + module EnumTest + module ExampleEnum + extend Seed::Internal::Types::Enum + + FOO = :foo + BAR = :bar + + finalize! + end + end + + describe "#values" do + it "defines values" do + assert_equal %i[foo bar].sort, EnumTest::ExampleEnum.values.sort + end + end + + describe "#coerce" do + it "coerces an existing member" do + assert_equal :foo, EnumTest::ExampleEnum.coerce(:foo) + end + + it "coerces a string version of a member" do + assert_equal :foo, EnumTest::ExampleEnum.coerce("foo") + end + + it "returns the value if not a member with strictness off" do + assert_equal 1, EnumTest::ExampleEnum.coerce(1) + end + + it "raises an error if value is not a member with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + EnumTest::ExampleEnum.coerce(1, strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb new file mode 100644 index 000000000000..6c5e58a6a946 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Hash do + module TestHash + SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] + end + + describe ".[]" do + it "defines the key and value type" do + assert_equal Symbol, TestHash::SymbolStringHash.key_type + assert_equal String, TestHash::SymbolStringHash.value_type + end + end + + describe "#coerce" do + it "coerces the keys" do + assert_equal %i[foo bar], TestHash::SymbolStringHash.coerce({ "foo" => "1", :bar => "2" }).keys + end + + it "coerces the values" do + assert_equal %w[foo 1], TestHash::SymbolStringHash.coerce({ foo: :foo, bar: 1 }).values + end + + it "passes through other values with strictness off" do + obj = Object.new + + assert_equal obj, TestHash::SymbolStringHash.coerce(obj) + end + + it "raises an error with other values with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce(Object.new, strict: true) + end + end + + it "raises an error with non-coercable key types with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce({ Object.new => 1 }, strict: true) + end + end + + it "raises an error with non-coercable value types with strictness on" do + assert_raises Seed::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce({ "foobar" => Object.new }, strict: true) + end + end + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb new file mode 100644 index 000000000000..3d87b9f5a8c7 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Model do + module StringInteger + extend Seed::Internal::Types::Union + + member String + member Integer + end + + class ExampleModel < Seed::Internal::Types::Model + field :name, String + field :rating, StringInteger, optional: true + field :year, Integer, optional: true, nullable: true, api_name: "yearOfRelease" + end + + class ExampleModelInheritance < ExampleModel + field :director, String + end + + class ExampleWithDefaults < ExampleModel + field :type, String, default: "example" + end + + class ExampleChild < Seed::Internal::Types::Model + field :value, String + end + + class ExampleParent < Seed::Internal::Types::Model + field :child, ExampleChild + end + + describe ".field" do + before do + @example = ExampleModel.new(name: "Inception", rating: 4) + end + + it "defines fields on model" do + assert_equal %i[name rating year], ExampleModel.fields.keys + end + + it "defines fields from parent models" do + assert_equal %i[name rating year director], ExampleModelInheritance.fields.keys + end + + it "sets the field's type" do + assert_equal String, ExampleModel.fields[:name].type + assert_equal StringInteger, ExampleModel.fields[:rating].type + end + + it "sets the `default` option" do + assert_equal "example", ExampleWithDefaults.fields[:type].default + end + + it "defines getters" do + assert_respond_to @example, :name + assert_respond_to @example, :rating + + assert_equal "Inception", @example.name + assert_equal 4, @example.rating + end + + it "defines setters" do + assert_respond_to @example, :name= + assert_respond_to @example, :rating= + + @example.name = "Inception 2" + @example.rating = 5 + + assert_equal "Inception 2", @example.name + assert_equal 5, @example.rating + end + end + + describe "#initialize" do + it "sets the data" do + example = ExampleModel.new(name: "Inception", rating: 4) + + assert_equal "Inception", example.name + assert_equal 4, example.rating + end + + it "allows extra fields to be set" do + example = ExampleModel.new(name: "Inception", rating: 4, director: "Christopher Nolan") + + assert_equal "Christopher Nolan", example.director + end + + it "sets the defaults where applicable" do + example_using_defaults = ExampleWithDefaults.new + + assert_equal "example", example_using_defaults.type + + example_without_defaults = ExampleWithDefaults.new(type: "not example") + + assert_equal "not example", example_without_defaults.type + end + + it "coerces child models" do + parent = ExampleParent.new(child: { value: "foobar" }) + + assert_kind_of ExampleChild, parent.child + end + + it "uses the api_name to pull the value" do + example = ExampleModel.new({ name: "Inception", yearOfRelease: 2014 }) + + assert_equal 2014, example.year + refute_respond_to example, :yearOfRelease + end + end + + describe "#inspect" do + class SensitiveModel < Seed::Internal::Types::Model + field :username, String + field :password, String + field :client_secret, String + field :access_token, String + field :api_key, String + end + + it "redacts sensitive fields" do + model = SensitiveModel.new( + username: "user123", + password: "secret123", + client_secret: "cs_abc", + access_token: "token_xyz", + api_key: "key_123" + ) + + inspect_output = model.inspect + + assert_includes inspect_output, "username=\"user123\"" + assert_includes inspect_output, "password=[REDACTED]" + assert_includes inspect_output, "client_secret=[REDACTED]" + assert_includes inspect_output, "access_token=[REDACTED]" + assert_includes inspect_output, "api_key=[REDACTED]" + refute_includes inspect_output, "secret123" + refute_includes inspect_output, "cs_abc" + refute_includes inspect_output, "token_xyz" + refute_includes inspect_output, "key_123" + end + + it "does not redact non-sensitive fields" do + example = ExampleModel.new(name: "Inception", rating: 4) + inspect_output = example.inspect + + assert_includes inspect_output, "name=\"Inception\"" + assert_includes inspect_output, "rating=4" + end + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb new file mode 100644 index 000000000000..e4e95c93139f --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Union do + class Rectangle < Seed::Internal::Types::Model + literal :type, "square" + + field :area, Float + end + + class Circle < Seed::Internal::Types::Model + literal :type, "circle" + + field :area, Float + end + + class Pineapple < Seed::Internal::Types::Model + literal :type, "pineapple" + + field :area, Float + end + + module Shape + extend Seed::Internal::Types::Union + + discriminant :type + + member -> { Rectangle }, key: "rect" + member -> { Circle }, key: "circle" + end + + module StringOrInteger + extend Seed::Internal::Types::Union + + member String + member Integer + end + + describe "#coerce" do + it "coerces hashes into member models with discriminated unions" do + circle = Shape.coerce({ type: "circle", area: 4.0 }) + + assert_instance_of Circle, circle + end + end + + describe "#type_member?" do + it "defines Model members" do + assert Shape.type_member?(Rectangle) + assert Shape.type_member?(Circle) + refute Shape.type_member?(Pineapple) + end + + it "defines other members" do + assert StringOrInteger.type_member?(String) + assert StringOrInteger.type_member?(Integer) + refute StringOrInteger.type_member?(Float) + refute StringOrInteger.type_member?(Pineapple) + end + end +end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb new file mode 100644 index 000000000000..29d14621a229 --- /dev/null +++ b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Seed::Internal::Types::Utils do + Utils = Seed::Internal::Types::Utils + + module TestUtils + class M < Seed::Internal::Types::Model + field :value, String + end + + class UnionMemberA < Seed::Internal::Types::Model + literal :type, "A" + field :only_on_a, String + end + + class UnionMemberB < Seed::Internal::Types::Model + literal :type, "B" + field :only_on_b, String + end + + module U + extend Seed::Internal::Types::Union + + discriminant :type + + member -> { UnionMemberA }, key: "A" + member -> { UnionMemberB }, key: "B" + end + + SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] + SymbolModelHash = -> { Seed::Internal::Types::Hash[Symbol, TestUtils::M] } + end + + describe ".coerce" do + describe "NilClass" do + it "always returns nil" do + assert_nil Utils.coerce(NilClass, "foobar") + assert_nil Utils.coerce(NilClass, 1) + assert_nil Utils.coerce(NilClass, Object.new) + end + end + + describe "String" do + it "coerces from String, Symbol, Numeric, or Boolean" do + assert_equal "foobar", Utils.coerce(String, "foobar") + assert_equal "foobar", Utils.coerce(String, :foobar) + assert_equal "1", Utils.coerce(String, 1) + assert_equal "1.0", Utils.coerce(String, 1.0) + assert_equal "true", Utils.coerce(String, true) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(String, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(String, Object.new, strict: true) + end + end + end + + describe "Symbol" do + it "coerces from Symbol, String" do + assert_equal :foobar, Utils.coerce(Symbol, :foobar) + assert_equal :foobar, Utils.coerce(Symbol, "foobar") + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Symbol, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(Symbol, Object.new, strict: true) + end + end + end + + describe "Integer" do + it "coerces from Numeric, String, Time" do + assert_equal 1, Utils.coerce(Integer, 1) + assert_equal 1, Utils.coerce(Integer, 1.0) + assert_equal 1, Utils.coerce(Integer, Complex.rect(1)) + assert_equal 1, Utils.coerce(Integer, Rational(1)) + assert_equal 1, Utils.coerce(Integer, "1") + assert_equal 1_713_916_800, Utils.coerce(Integer, Time.utc(2024, 4, 24)) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Integer, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(Integer, Object.new, strict: true) + end + end + end + + describe "Float" do + it "coerces from Numeric, Time" do + assert_in_delta(1.0, Utils.coerce(Float, 1.0)) + assert_in_delta(1.0, Utils.coerce(Float, 1)) + assert_in_delta(1.0, Utils.coerce(Float, Complex.rect(1))) + assert_in_delta(1.0, Utils.coerce(Float, Rational(1))) + assert_in_delta(1_713_916_800.0, Utils.coerce(Integer, Time.utc(2024, 4, 24))) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Float, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Seed::Internal::Errors::TypeError do + Utils.coerce(Float, Object.new, strict: true) + end + end + end + + describe "Model" do + it "coerces a hash" do + result = Utils.coerce(TestUtils::M, { value: "foobar" }) + + assert_kind_of TestUtils::M, result + assert_equal "foobar", result.value + end + + it "coerces a hash when the target is a type function" do + result = Utils.coerce(-> { TestUtils::M }, { value: "foobar" }) + + assert_kind_of TestUtils::M, result + assert_equal "foobar", result.value + end + + it "will not coerce non-hashes" do + assert_equal "foobar", Utils.coerce(TestUtils::M, "foobar") + end + end + + describe "Enum" do + module ExampleEnum + extend Seed::Internal::Types::Enum + + FOO = :FOO + BAR = :BAR + + finalize! + end + + it "coerces into a Symbol version of the member value" do + assert_equal :FOO, Utils.coerce(ExampleEnum, "FOO") + end + + it "returns given value if not a member" do + assert_equal "NOPE", Utils.coerce(ExampleEnum, "NOPE") + end + end + + describe "Array" do + StringArray = Seed::Internal::Types::Array[String] + ModelArray = -> { Seed::Internal::Types::Array[TestUtils::M] } + UnionArray = -> { Seed::Internal::Types::Array[TestUtils::U] } + + it "coerces an array of literals" do + assert_equal %w[a b c], Utils.coerce(StringArray, %w[a b c]) + assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, [1, 2.0, true]) + assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, Set.new([1, 2.0, true])) + end + + it "coerces an array of Models" do + assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], + Utils.coerce(ModelArray, [{ value: "foobar" }, { value: "bizbaz" }]) + + assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], + Utils.coerce(ModelArray, [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")]) + end + + it "coerces an array of model unions" do + assert_equal [TestUtils::UnionMemberA.new(type: "A", only_on_a: "A"), TestUtils::UnionMemberB.new(type: "B", only_on_b: "B")], + Utils.coerce(UnionArray, [{ type: "A", only_on_a: "A" }, { type: "B", only_on_b: "B" }]) + end + + it "returns given value if not an array" do + assert_equal 1, Utils.coerce(StringArray, 1) + end + end + + describe "Hash" do + it "coerces the keys and values" do + ssh_res = Utils.coerce(TestUtils::SymbolStringHash, { "foo" => "bar", "biz" => "2" }) + + assert_equal "bar", ssh_res[:foo] + assert_equal "2", ssh_res[:biz] + + smh_res = Utils.coerce(TestUtils::SymbolModelHash, { "foo" => { "value" => "foo" } }) + + assert_equal TestUtils::M.new(value: "foo"), smh_res[:foo] + end + end + end +end diff --git a/seed/rust-sdk/allof-inline/.fern/metadata.json b/seed/rust-sdk/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..479cc3770032 --- /dev/null +++ b/seed/rust-sdk/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-rust-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/rust-sdk/allof-inline/.github/workflows/ci.yml b/seed/rust-sdk/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..4e582a9fa4e3 --- /dev/null +++ b/seed/rust-sdk/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + RUSTFLAGS: "-A warnings" + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Check + run: cargo check + + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Compile + run: cargo build + + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Test + run: cargo test + + publish: + needs: [check, compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Publish + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish diff --git a/seed/rust-sdk/allof-inline/.gitignore b/seed/rust-sdk/allof-inline/.gitignore new file mode 100644 index 000000000000..f75d27799da1 --- /dev/null +++ b/seed/rust-sdk/allof-inline/.gitignore @@ -0,0 +1,5 @@ +/target +**/*.rs.bk +Cargo.lock +.DS_Store +*.swp \ No newline at end of file diff --git a/seed/rust-sdk/allof-inline/Cargo.toml b/seed/rust-sdk/allof-inline/Cargo.toml new file mode 100644 index 000000000000..ce5837604a74 --- /dev/null +++ b/seed/rust-sdk/allof-inline/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "seed_api" +version = "0.0.1" +edition = "2021" +description = "Rust SDK for seed_api generated by Fern" +license = "MIT" +repository = "https://github.com/fern-api/fern" +documentation = "https://docs.rs/seed_api" + +[lib] +doctest = false + +[dependencies] +bytes = "1.0" +chrono = { version = "0.4", features = ["serde"] } +futures = "0.3" +reqwest = { version = "0.12", features = ["json", "stream"], default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" +tokio = { version = "1.0", features = ["full"] } + +[dev-dependencies] +tokio-test = "0.4" + diff --git a/seed/rust-sdk/allof-inline/README.md b/seed/rust-sdk/allof-inline/README.md new file mode 100644 index 000000000000..73c2a4769236 --- /dev/null +++ b/seed/rust-sdk/allof-inline/README.md @@ -0,0 +1,181 @@ +# Seed Rust Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRust) +[![crates.io shield](https://img.shields.io/crates/v/seed_api)](https://crates.io/crates/seed_api) + +The Seed Rust library provides convenient access to the Seed APIs from Rust. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Request Types](#request-types) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) +- [Contributing](#contributing) + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +seed_api = "0.0.1" +``` + +Or install via cargo: + +```sh +cargo add seed_api +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```rust +use seed_api::prelude::{*}; + +let config = ClientConfig { + base_url: Environment::Default.url().to_string(), + ..Default::default() +}; +let client = Client::new(config).expect("Failed to build client"); +``` + +## Errors + +When the API returns a non-success status code (4xx or 5xx response), an error will be returned. + +```rust +match client.create_rule(None)?.await { + Ok(response) => { + println!("Success: {:?}", response); + }, + Err(ApiError::HTTP { status, message }) => { + println!("API Error {}: {:?}", status, message); + }, + Err(e) => { + println!("Other error: {:?}", e); + } +} +``` + +## Request Types + +The SDK exports all request types as Rust structs. Simply import them from the crate to access them: + +```rust +use seed_api::prelude::{*}; + +let request = RuleCreateRequest { + ... +}; +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` method to configure this behavior. + +```rust +let response = client.create_rule( + Some(RequestOptions::new().max_retries(3)) +)?.await; +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `timeout` method to configure this behavior. + +```rust +let response = client.create_rule( + Some(RequestOptions::new().timeout_seconds(30)) +)?.await; +``` + +### Additional Headers + +You can add custom headers to requests using `RequestOptions`. + +```rust +let response = client.create_rule( + Some( + RequestOptions::new() + .additional_header("X-Custom-Header", "custom-value") + .additional_header("X-Another-Header", "another-value") + ) +)? +.await; +``` + +### Additional Query String Parameters + +You can add custom query parameters to requests using `RequestOptions`. + +```rust +let response = client.create_rule( + Some( + RequestOptions::new() + .additional_query_param("filter", "active") + .additional_query_param("sort", "desc") + ) +)? +.await; +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs new file mode 100644 index 000000000000..6968951d8813 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs @@ -0,0 +1,18 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .search_rule_types( + &SearchRuleTypesQueryRequest { + ..Default::default() + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs new file mode 100644 index 000000000000..cf7e959c8d85 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs @@ -0,0 +1,19 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .search_rule_types( + &SearchRuleTypesQueryRequest { + query: Some("query".to_string()), + ..Default::default() + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs new file mode 100644 index 000000000000..db52ba2800ca --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs @@ -0,0 +1,19 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs new file mode 100644 index 000000000000..db52ba2800ca --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs @@ -0,0 +1,19 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs new file mode 100644 index 000000000000..5afd9b2dd1e9 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.list_users(None).await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs new file mode 100644 index 000000000000..5afd9b2dd1e9 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.list_users(None).await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs new file mode 100644 index 000000000000..0f9ad4c50ad6 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_entity(None).await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs new file mode 100644 index 000000000000..0f9ad4c50ad6 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_entity(None).await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs new file mode 100644 index 000000000000..9025e9975095 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_organization(None).await; +} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs new file mode 100644 index 000000000000..9025e9975095 --- /dev/null +++ b/seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_organization(None).await; +} diff --git a/seed/rust-sdk/allof-inline/reference.md b/seed/rust-sdk/allof-inline/reference.md new file mode 100644 index 000000000000..482a8295a3fb --- /dev/null +++ b/seed/rust-sdk/allof-inline/reference.md @@ -0,0 +1,224 @@ +# Reference +
client.search_rule_types(query: Option<Option<String>>) -> Result<RuleTypeSearchResponse, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .search_rule_types( + &SearchRuleTypesQueryRequest { + ..Default::default() + }, + None, + ) + .await; +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `Option` + +
+
+
+
+ + +
+
+
+ +
client.create_rule(request: RuleCreateRequest) -> Result<RuleResponse, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `String` + +
+
+ +
+
+ +**execution_context:** `RuleExecutionContext` + +
+
+
+
+ + +
+
+
+ +
client.list_users() -> Result<UserSearchResponse, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.list_users(None).await; +} +``` +
+
+
+
+ + +
+
+
+ +
client.get_entity() -> Result<CombinedEntity, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_entity(None).await; +} +``` +
+
+
+
+ + +
+
+
+ +
client.get_organization() -> Result<Organization, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_organization(None).await; +} +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/rust-sdk/allof-inline/rustfmt.toml b/seed/rust-sdk/allof-inline/rustfmt.toml new file mode 100644 index 000000000000..872221fb31fe --- /dev/null +++ b/seed/rust-sdk/allof-inline/rustfmt.toml @@ -0,0 +1,4 @@ +# Generated by Fern +edition = "2021" +max_width = 100 +use_small_heuristics = "Default" \ No newline at end of file diff --git a/seed/rust-sdk/allof-inline/snippet.json b/seed/rust-sdk/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/rust-sdk/allof-inline/src/api/mod.rs b/seed/rust-sdk/allof-inline/src/api/mod.rs new file mode 100644 index 000000000000..83cb9e2c92d4 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/mod.rs @@ -0,0 +1,15 @@ +//! API client and types for the allOf Composition +//! +//! This module contains all the API definitions including request/response types +//! and client implementations for interacting with the API. +//! +//! ## Modules +//! +//! - [`resources`] - Service clients and endpoints +//! - [`types`] - Request, response, and model types + +pub mod resources; +pub mod types; + +pub use resources::ApiClient; +pub use types::*; diff --git a/seed/rust-sdk/allof-inline/src/api/resources/mod.rs b/seed/rust-sdk/allof-inline/src/api/resources/mod.rs new file mode 100644 index 000000000000..543ca7436f76 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/resources/mod.rs @@ -0,0 +1,82 @@ +//! Service clients and API endpoints +//! +//! This module provides the client implementations for all available services. + +use crate::api::*; +use crate::{ApiError, ClientConfig, HttpClient, RequestOptions}; +use reqwest::Method; + +pub struct ApiClient { + pub config: ClientConfig, + pub http_client: HttpClient, +} + +impl ApiClient { + pub fn new(config: ClientConfig) -> Result { + Ok(Self { + config: config.clone(), + http_client: HttpClient::new(config.clone())?, + }) + } + + pub async fn search_rule_types( + &self, + request: &SearchRuleTypesQueryRequest, + options: Option, + ) -> Result { + self.http_client + .execute_request( + Method::GET, + "rule-types", + None, + QueryBuilder::new() + .structured_query("query", request.query.clone()) + .build(), + options, + ) + .await + } + + pub async fn create_rule( + &self, + request: &RuleCreateRequest, + options: Option, + ) -> Result { + self.http_client + .execute_request( + Method::POST, + "rules", + Some(serde_json::to_value(request).map_err(ApiError::Serialization)?), + None, + options, + ) + .await + } + + pub async fn list_users( + &self, + options: Option, + ) -> Result { + self.http_client + .execute_request(Method::GET, "users", None, None, options) + .await + } + + pub async fn get_entity( + &self, + options: Option, + ) -> Result { + self.http_client + .execute_request(Method::GET, "entities", None, None, options) + .await + } + + pub async fn get_organization( + &self, + options: Option, + ) -> Result { + self.http_client + .execute_request(Method::GET, "organizations", None, None, options) + .await + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/audit_info.rs b/seed/rust-sdk/allof-inline/src/api/types/audit_info.rs new file mode 100644 index 000000000000..a958df434a50 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/audit_info.rs @@ -0,0 +1,73 @@ +pub use crate::prelude::*; + +/// Common audit metadata. +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct AuditInfo { + /// The user who created this resource. + #[serde(rename = "createdBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + /// When this resource was created. + #[serde(rename = "createdDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub created_date_time: Option>, + /// The user who last modified this resource. + #[serde(rename = "modifiedBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_by: Option, + /// When this resource was last modified. + #[serde(rename = "modifiedDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub modified_date_time: Option>, +} + +impl AuditInfo { + pub fn builder() -> AuditInfoBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct AuditInfoBuilder { + created_by: Option, + created_date_time: Option>, + modified_by: Option, + modified_date_time: Option>, +} + +impl AuditInfoBuilder { + pub fn created_by(mut self, value: impl Into) -> Self { + self.created_by = Some(value.into()); + self + } + + pub fn created_date_time(mut self, value: DateTime) -> Self { + self.created_date_time = Some(value); + self + } + + pub fn modified_by(mut self, value: impl Into) -> Self { + self.modified_by = Some(value.into()); + self + } + + pub fn modified_date_time(mut self, value: DateTime) -> Self { + self.modified_date_time = Some(value); + self + } + + /// Consumes the builder and constructs a [`AuditInfo`]. + pub fn build(self) -> Result { + Ok(AuditInfo { + created_by: self.created_by, + created_date_time: self.created_date_time, + modified_by: self.modified_by, + modified_date_time: self.modified_date_time, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/base_org.rs b/seed/rust-sdk/allof-inline/src/api/types/base_org.rs new file mode 100644 index 000000000000..1747ce8ded53 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/base_org.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrg { + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl BaseOrg { + pub fn builder() -> BaseOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgBuilder { + id: Option, + metadata: Option, +} + +impl BaseOrgBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`BaseOrg`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](BaseOrgBuilder::id) + pub fn build(self) -> Result { + Ok(BaseOrg { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs b/seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs new file mode 100644 index 000000000000..d1182fd85504 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs @@ -0,0 +1,48 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrgMetadata { + /// Deployment region from BaseOrg. + #[serde(default)] + pub region: String, + /// Subscription tier. + #[serde(skip_serializing_if = "Option::is_none")] + pub tier: Option, +} + +impl BaseOrgMetadata { + pub fn builder() -> BaseOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgMetadataBuilder { + region: Option, + tier: Option, +} + +impl BaseOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn tier(mut self, value: impl Into) -> Self { + self.tier = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`BaseOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](BaseOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(BaseOrgMetadata { + region: self + .region + .ok_or_else(|| BuildError::missing_field("region"))?, + tier: self.tier, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs b/seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs new file mode 100644 index 000000000000..fb6b31b1293e --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs @@ -0,0 +1,67 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct CombinedEntity { + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Describable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + pub status: CombinedEntityStatus, +} + +impl CombinedEntity { + pub fn builder() -> CombinedEntityBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct CombinedEntityBuilder { + id: Option, + name: Option, + summary: Option, + status: Option, +} + +impl CombinedEntityBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + pub fn status(mut self, value: CombinedEntityStatus) -> Self { + self.status = Some(value); + self + } + + /// Consumes the builder and constructs a [`CombinedEntity`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](CombinedEntityBuilder::id) + /// - [`status`](CombinedEntityBuilder::status) + pub fn build(self) -> Result { + Ok(CombinedEntity { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + summary: self.summary, + status: self + .status + .ok_or_else(|| BuildError::missing_field("status"))?, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs b/seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs new file mode 100644 index 000000000000..f46cb23eecb2 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs @@ -0,0 +1,42 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CombinedEntityStatus { + Active, + Archived, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for CombinedEntityStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Archived => serializer.serialize_str("archived"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for CombinedEntityStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "archived" => Ok(Self::Archived), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for CombinedEntityStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Archived => write!(f, "archived"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/describable.rs b/seed/rust-sdk/allof-inline/src/api/types/describable.rs new file mode 100644 index 000000000000..6a8a527bd844 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/describable.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Describable { + /// Display name from Describable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, +} + +impl Describable { + pub fn builder() -> DescribableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DescribableBuilder { + name: Option, + summary: Option, +} + +impl DescribableBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Describable`]. + pub fn build(self) -> Result { + Ok(Describable { + name: self.name, + summary: self.summary, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs b/seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs new file mode 100644 index 000000000000..fac068d8a2ee --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs @@ -0,0 +1,33 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrg { + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl DetailedOrg { + pub fn builder() -> DetailedOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgBuilder { + metadata: Option, +} + +impl DetailedOrgBuilder { + pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`DetailedOrg`]. + pub fn build(self) -> Result { + Ok(DetailedOrg { + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs b/seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs new file mode 100644 index 000000000000..bbbeda49c417 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs @@ -0,0 +1,48 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrgMetadata { + /// Deployment region from DetailedOrg. + #[serde(default)] + pub region: String, + /// Custom domain name. + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, +} + +impl DetailedOrgMetadata { + pub fn builder() -> DetailedOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgMetadataBuilder { + region: Option, + domain: Option, +} + +impl DetailedOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn domain(mut self, value: impl Into) -> Self { + self.domain = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](DetailedOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(DetailedOrgMetadata { + region: self + .region + .ok_or_else(|| BuildError::missing_field("region"))?, + domain: self.domain, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/identifiable.rs b/seed/rust-sdk/allof-inline/src/api/types/identifiable.rs new file mode 100644 index 000000000000..58fb3eb70734 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/identifiable.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Identifiable { + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Identifiable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +impl Identifiable { + pub fn builder() -> IdentifiableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct IdentifiableBuilder { + id: Option, + name: Option, +} + +impl IdentifiableBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Identifiable`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](IdentifiableBuilder::id) + pub fn build(self) -> Result { + Ok(Identifiable { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/mod.rs b/seed/rust-sdk/allof-inline/src/api/types/mod.rs new file mode 100644 index 000000000000..a61bbc01e128 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/mod.rs @@ -0,0 +1,45 @@ +pub mod audit_info; +pub mod base_org; +pub mod base_org_metadata; +pub mod combined_entity; +pub mod combined_entity_status; +pub mod describable; +pub mod detailed_org; +pub mod detailed_org_metadata; +pub mod identifiable; +pub mod organization; +pub mod organization_metadata; +pub mod paginated_result; +pub mod paging_cursors; +pub mod rule_create_request; +pub mod rule_execution_context; +pub mod rule_response; +pub mod rule_response_status; +pub mod rule_type; +pub mod rule_type_search_response; +pub mod search_rule_types_query_request; +pub mod user; +pub mod user_search_response; + +pub use audit_info::AuditInfo; +pub use base_org::BaseOrg; +pub use base_org_metadata::BaseOrgMetadata; +pub use combined_entity::CombinedEntity; +pub use combined_entity_status::CombinedEntityStatus; +pub use describable::Describable; +pub use detailed_org::DetailedOrg; +pub use detailed_org_metadata::DetailedOrgMetadata; +pub use identifiable::Identifiable; +pub use organization::Organization; +pub use organization_metadata::OrganizationMetadata; +pub use paginated_result::PaginatedResult; +pub use paging_cursors::PagingCursors; +pub use rule_create_request::RuleCreateRequest; +pub use rule_execution_context::RuleExecutionContext; +pub use rule_response::RuleResponse; +pub use rule_response_status::RuleResponseStatus; +pub use rule_type::RuleType; +pub use rule_type_search_response::RuleTypeSearchResponse; +pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; +pub use user::User; +pub use user_search_response::UserSearchResponse; diff --git a/seed/rust-sdk/allof-inline/src/api/types/organization.rs b/seed/rust-sdk/allof-inline/src/api/types/organization.rs new file mode 100644 index 000000000000..3f727a6b8212 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/organization.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Organization { + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + #[serde(default)] + pub name: String, +} + +impl Organization { + pub fn builder() -> OrganizationBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct OrganizationBuilder { + id: Option, + metadata: Option, + name: Option, +} + +impl OrganizationBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: OrganizationMetadata) -> Self { + self.metadata = Some(value); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Organization`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](OrganizationBuilder::id) + /// - [`name`](OrganizationBuilder::name) + pub fn build(self) -> Result { + Ok(Organization { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs b/seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs new file mode 100644 index 000000000000..b73e932e873d --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs @@ -0,0 +1,48 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct OrganizationMetadata { + /// Deployment region from DetailedOrg. + #[serde(default)] + pub region: String, + /// Custom domain name. + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, +} + +impl OrganizationMetadata { + pub fn builder() -> OrganizationMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct OrganizationMetadataBuilder { + region: Option, + domain: Option, +} + +impl OrganizationMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn domain(mut self, value: impl Into) -> Self { + self.domain = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`OrganizationMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](OrganizationMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(OrganizationMetadata { + region: self + .region + .ok_or_else(|| BuildError::missing_field("region"))?, + domain: self.domain, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs b/seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs new file mode 100644 index 000000000000..1f7073c5844e --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs @@ -0,0 +1,50 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct PaginatedResult { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(default)] + pub results: Vec, +} + +impl PaginatedResult { + pub fn builder() -> PaginatedResultBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PaginatedResultBuilder { + paging: Option, + results: Option>, +} + +impl PaginatedResultBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`PaginatedResult`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](PaginatedResultBuilder::paging) + /// - [`results`](PaginatedResultBuilder::results) + pub fn build(self) -> Result { + Ok(PaginatedResult { + paging: self + .paging + .ok_or_else(|| BuildError::missing_field("paging"))?, + results: self + .results + .ok_or_else(|| BuildError::missing_field("results"))?, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs b/seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs new file mode 100644 index 000000000000..a3a785119b57 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct PagingCursors { + /// Cursor for the next page of results. + #[serde(default)] + pub next: String, + /// Cursor for the previous page of results. + #[serde(skip_serializing_if = "Option::is_none")] + pub previous: Option, +} + +impl PagingCursors { + pub fn builder() -> PagingCursorsBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PagingCursorsBuilder { + next: Option, + previous: Option, +} + +impl PagingCursorsBuilder { + pub fn next(mut self, value: impl Into) -> Self { + self.next = Some(value.into()); + self + } + + pub fn previous(mut self, value: impl Into) -> Self { + self.previous = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`PagingCursors`]. + /// This method will fail if any of the following fields are not set: + /// - [`next`](PagingCursorsBuilder::next) + pub fn build(self) -> Result { + Ok(PagingCursors { + next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, + previous: self.previous, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs new file mode 100644 index 000000000000..913f6f08eabd --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleCreateRequest { + #[serde(default)] + pub name: String, + #[serde(rename = "executionContext")] + pub execution_context: RuleExecutionContext, +} + +impl RuleCreateRequest { + pub fn builder() -> RuleCreateRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleCreateRequestBuilder { + name: Option, + execution_context: Option, +} + +impl RuleCreateRequestBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleCreateRequest`]. + /// This method will fail if any of the following fields are not set: + /// - [`name`](RuleCreateRequestBuilder::name) + /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) + pub fn build(self) -> Result { + Ok(RuleCreateRequest { + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + execution_context: self + .execution_context + .ok_or_else(|| BuildError::missing_field("execution_context"))?, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs new file mode 100644 index 000000000000..7f8fa5bd56e1 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +/// Execution environment for a rule. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleExecutionContext { + Prod, + Staging, + Dev, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleExecutionContext { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Prod => serializer.serialize_str("prod"), + Self::Staging => serializer.serialize_str("staging"), + Self::Dev => serializer.serialize_str("dev"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleExecutionContext { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "prod" => Ok(Self::Prod), + "staging" => Ok(Self::Staging), + "dev" => Ok(Self::Dev), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleExecutionContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Prod => write!(f, "prod"), + Self::Staging => write!(f, "staging"), + Self::Dev => write!(f, "dev"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_response.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_response.rs new file mode 100644 index 000000000000..9977ccb1acfa --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/rule_response.rs @@ -0,0 +1,114 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleResponse { + /// The user who created this resource. + #[serde(rename = "createdBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + /// When this resource was created. + #[serde(rename = "createdDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub created_date_time: Option>, + /// The user who last modified this resource. + #[serde(rename = "modifiedBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_by: Option, + /// When this resource was last modified. + #[serde(rename = "modifiedDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub modified_date_time: Option>, + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + pub status: RuleResponseStatus, + #[serde(rename = "executionContext")] + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_context: Option, +} + +impl RuleResponse { + pub fn builder() -> RuleResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleResponseBuilder { + created_by: Option, + created_date_time: Option>, + modified_by: Option, + modified_date_time: Option>, + id: Option, + name: Option, + status: Option, + execution_context: Option, +} + +impl RuleResponseBuilder { + pub fn created_by(mut self, value: impl Into) -> Self { + self.created_by = Some(value.into()); + self + } + + pub fn created_date_time(mut self, value: DateTime) -> Self { + self.created_date_time = Some(value); + self + } + + pub fn modified_by(mut self, value: impl Into) -> Self { + self.modified_by = Some(value.into()); + self + } + + pub fn modified_date_time(mut self, value: DateTime) -> Self { + self.modified_date_time = Some(value); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn status(mut self, value: RuleResponseStatus) -> Self { + self.status = Some(value); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](RuleResponseBuilder::id) + /// - [`name`](RuleResponseBuilder::name) + /// - [`status`](RuleResponseBuilder::status) + pub fn build(self) -> Result { + Ok(RuleResponse { + created_by: self.created_by, + created_date_time: self.created_date_time, + modified_by: self.modified_by, + modified_date_time: self.modified_date_time, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + status: self + .status + .ok_or_else(|| BuildError::missing_field("status"))?, + execution_context: self.execution_context, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs new file mode 100644 index 000000000000..5d2462ecd349 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleResponseStatus { + Active, + Inactive, + Draft, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleResponseStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Inactive => serializer.serialize_str("inactive"), + Self::Draft => serializer.serialize_str("draft"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleResponseStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "inactive" => Ok(Self::Inactive), + "draft" => Ok(Self::Draft), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleResponseStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Inactive => write!(f, "inactive"), + Self::Draft => write!(f, "draft"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_type.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_type.rs new file mode 100644 index 000000000000..69f0317c1923 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/rule_type.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleType { + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +impl RuleType { + pub fn builder() -> RuleTypeBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeBuilder { + id: Option, + name: Option, + description: Option, +} + +impl RuleTypeBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn description(mut self, value: impl Into) -> Self { + self.description = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`RuleType`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](RuleTypeBuilder::id) + /// - [`name`](RuleTypeBuilder::name) + pub fn build(self) -> Result { + Ok(RuleType { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + description: self.description, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs new file mode 100644 index 000000000000..0f1fbcdeb7dc --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleTypeSearchResponse { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, +} + +impl RuleTypeSearchResponse { + pub fn builder() -> RuleTypeSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeSearchResponseBuilder { + paging: Option, + results: Option>, +} + +impl RuleTypeSearchResponseBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](RuleTypeSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(RuleTypeSearchResponse { + paging: self + .paging + .ok_or_else(|| BuildError::missing_field("paging"))?, + results: self.results, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs b/seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs new file mode 100644 index 000000000000..37158173a08c --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs @@ -0,0 +1,32 @@ +pub use crate::prelude::*; + +/// Query parameters for searchRuleTypes +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct SearchRuleTypesQueryRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, +} + +impl SearchRuleTypesQueryRequest { + pub fn builder() -> SearchRuleTypesQueryRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct SearchRuleTypesQueryRequestBuilder { + query: Option, +} + +impl SearchRuleTypesQueryRequestBuilder { + pub fn query(mut self, value: impl Into) -> Self { + self.query = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. + pub fn build(self) -> Result { + Ok(SearchRuleTypesQueryRequest { query: self.query }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/user.rs b/seed/rust-sdk/allof-inline/src/api/types/user.rs new file mode 100644 index 000000000000..cce3febf2dbe --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/user.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct User { + #[serde(default)] + pub id: String, + #[serde(default)] + pub email: String, +} + +impl User { + pub fn builder() -> UserBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserBuilder { + id: Option, + email: Option, +} + +impl UserBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn email(mut self, value: impl Into) -> Self { + self.email = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`User`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](UserBuilder::id) + /// - [`email`](UserBuilder::email) + pub fn build(self) -> Result { + Ok(User { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + email: self + .email + .ok_or_else(|| BuildError::missing_field("email"))?, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs b/seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs new file mode 100644 index 000000000000..08e7814bee1b --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct UserSearchResponse { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, +} + +impl UserSearchResponse { + pub fn builder() -> UserSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserSearchResponseBuilder { + paging: Option, + results: Option>, +} + +impl UserSearchResponseBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`UserSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](UserSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(UserSearchResponse { + paging: self + .paging + .ok_or_else(|| BuildError::missing_field("paging"))?, + results: self.results, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/src/client.rs b/seed/rust-sdk/allof-inline/src/client.rs new file mode 100644 index 000000000000..139cebf898d7 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/client.rs @@ -0,0 +1,247 @@ +use crate::api::resources::ApiClient; +use crate::Environment; +use crate::{ApiError, ClientConfig}; +use std::collections::HashMap; +use std::time::Duration; +/// Builder for creating API clients with custom configuration +pub struct ApiClientBuilder { + config: ClientConfig, +} +impl Default for ApiClientBuilder { + fn default() -> Self { + Self { + config: ClientConfig::default(), + } + } +} +impl ApiClientBuilder { + /// Create a new builder with the specified base URL + pub fn new(base_url: impl Into) -> Self { + let mut config = ClientConfig::default(); + config.base_url = base_url.into(); + Self { config } + } + + /// Set the environment, updating the base URL + pub fn environment(mut self, environment: Environment) -> Self { + self.config.base_url = environment.url().to_string(); + self + } + + /// Set the API key for authentication + pub fn api_key(mut self, key: impl Into) -> Self { + self.config.api_key = Some(key.into()); + self + } + + /// Set the bearer token for authentication + pub fn token(mut self, token: impl Into) -> Self { + self.config.token = Some(token.into()); + self + } + + /// Set the username for basic authentication + pub fn username(mut self, username: impl Into) -> Self { + self.config.username = Some(username.into()); + self + } + + /// Set the password for basic authentication + pub fn password(mut self, password: impl Into) -> Self { + self.config.password = Some(password.into()); + self + } + + /// Set the OAuth client ID for client credentials authentication + pub fn client_id(mut self, client_id: impl Into) -> Self { + self.config.client_id = Some(client_id.into()); + self + } + + /// Set the OAuth client secret for client credentials authentication + pub fn client_secret(mut self, client_secret: impl Into) -> Self { + self.config.client_secret = Some(client_secret.into()); + self + } + + /// Set OAuth credentials (client_id and client_secret) for client credentials authentication + pub fn oauth_credentials( + mut self, + client_id: impl Into, + client_secret: impl Into, + ) -> Self { + self.config.client_id = Some(client_id.into()); + self.config.client_secret = Some(client_secret.into()); + self + } + + /// Set the request timeout + pub fn timeout(mut self, timeout: Duration) -> Self { + self.config.timeout = timeout; + self + } + + /// Set the maximum number of retries + pub fn max_retries(mut self, retries: u32) -> Self { + self.config.max_retries = retries; + self + } + + /// Add a custom header + pub fn custom_header(mut self, key: impl Into, value: impl Into) -> Self { + self.config.custom_headers.insert(key.into(), value.into()); + self + } + + /// Add multiple custom headers + pub fn custom_headers(mut self, headers: HashMap) -> Self { + self.config.custom_headers.extend(headers); + self + } + + /// Set the user agent + pub fn user_agent(mut self, user_agent: impl Into) -> Self { + self.config.user_agent = user_agent.into(); + self + } + + /// Build the client with validation + pub fn build(self) -> Result { + ApiClient::new(self.config) + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_environment() { + let builder = ApiClientBuilder::default().environment(Environment::default()); + assert_eq!( + builder.config.base_url, + Environment::default().url().to_string() + ); + } + + #[test] + fn test_new_sets_base_url() { + let builder = ApiClientBuilder::new("https://api.example.com"); + assert_eq!(builder.config.base_url, "https://api.example.com"); + } + + #[test] + fn test_api_key() { + let builder = ApiClientBuilder::new("https://api.example.com").api_key("my-key"); + assert_eq!(builder.config.api_key, Some("my-key".to_string())); + } + + #[test] + fn test_token() { + let builder = ApiClientBuilder::new("https://api.example.com").token("my-token"); + assert_eq!(builder.config.token, Some("my-token".to_string())); + } + + #[test] + fn test_username() { + let builder = ApiClientBuilder::new("https://api.example.com").username("user"); + assert_eq!(builder.config.username, Some("user".to_string())); + } + + #[test] + fn test_password() { + let builder = ApiClientBuilder::new("https://api.example.com").password("pass"); + assert_eq!(builder.config.password, Some("pass".to_string())); + } + + #[test] + fn test_client_id() { + let builder = ApiClientBuilder::new("https://api.example.com").client_id("cid"); + assert_eq!(builder.config.client_id, Some("cid".to_string())); + } + + #[test] + fn test_client_secret() { + let builder = ApiClientBuilder::new("https://api.example.com").client_secret("secret"); + assert_eq!(builder.config.client_secret, Some("secret".to_string())); + } + + #[test] + fn test_oauth_credentials() { + let builder = + ApiClientBuilder::new("https://api.example.com").oauth_credentials("cid", "secret"); + assert_eq!(builder.config.client_id, Some("cid".to_string())); + assert_eq!(builder.config.client_secret, Some("secret".to_string())); + } + + #[test] + fn test_timeout() { + let builder = + ApiClientBuilder::new("https://api.example.com").timeout(Duration::from_secs(120)); + assert_eq!(builder.config.timeout, Duration::from_secs(120)); + } + + #[test] + fn test_max_retries() { + let builder = ApiClientBuilder::new("https://api.example.com").max_retries(5); + assert_eq!(builder.config.max_retries, 5); + } + + #[test] + fn test_custom_header() { + let builder = + ApiClientBuilder::new("https://api.example.com").custom_header("X-Custom", "value"); + assert_eq!( + builder.config.custom_headers.get("X-Custom"), + Some(&"value".to_string()) + ); + } + + #[test] + fn test_custom_headers_multiple() { + let mut headers = HashMap::new(); + headers.insert("X-One".to_string(), "1".to_string()); + headers.insert("X-Two".to_string(), "2".to_string()); + let builder = ApiClientBuilder::new("https://api.example.com").custom_headers(headers); + assert_eq!( + builder.config.custom_headers.get("X-One"), + Some(&"1".to_string()) + ); + assert_eq!( + builder.config.custom_headers.get("X-Two"), + Some(&"2".to_string()) + ); + } + + #[test] + fn test_user_agent() { + let builder = ApiClientBuilder::new("https://api.example.com").user_agent("my-sdk/1.0"); + assert_eq!(builder.config.user_agent, "my-sdk/1.0"); + } + + #[test] + fn test_full_builder_chain() { + let builder = ApiClientBuilder::new("https://api.example.com") + .api_key("key") + .token("tok") + .username("user") + .password("pass") + .timeout(Duration::from_secs(60)) + .max_retries(3) + .custom_header("X-Foo", "bar") + .user_agent("test/1.0"); + assert_eq!(builder.config.base_url, "https://api.example.com"); + assert_eq!(builder.config.api_key, Some("key".to_string())); + assert_eq!(builder.config.token, Some("tok".to_string())); + assert_eq!(builder.config.username, Some("user".to_string())); + assert_eq!(builder.config.password, Some("pass".to_string())); + assert_eq!(builder.config.timeout, Duration::from_secs(60)); + assert_eq!(builder.config.max_retries, 3); + assert_eq!(builder.config.user_agent, "test/1.0"); + } + + #[test] + fn test_build_succeeds() { + let result = ApiClientBuilder::new("https://api.example.com").build(); + assert!(result.is_ok()); + } +} diff --git a/seed/rust-sdk/allof-inline/src/config.rs b/seed/rust-sdk/allof-inline/src/config.rs new file mode 100644 index 000000000000..466af37454a1 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/config.rs @@ -0,0 +1,39 @@ +use crate::Environment; +use std::collections::HashMap; +use std::time::Duration; + +#[derive(Debug, Clone)] +pub struct ClientConfig { + pub base_url: String, + pub api_key: Option, + pub token: Option, + pub username: Option, + pub password: Option, + pub client_id: Option, + pub client_secret: Option, + pub timeout: Duration, + pub max_retries: u32, + pub custom_headers: HashMap, + pub user_agent: String, +} +impl Default for ClientConfig { + fn default() -> Self { + Self { + base_url: Environment::default().url().to_string(), + api_key: None, + token: None, + username: None, + password: None, + client_id: None, + client_secret: None, + timeout: Duration::from_secs(60), + max_retries: 3, + custom_headers: HashMap::from([ + ("X-Fern-Language".to_string(), "Rust".to_string()), + ("X-Fern-SDK-Name".to_string(), "seed_api".to_string()), + ("X-Fern-SDK-Version".to_string(), "0.0.1".to_string()), + ]), + user_agent: "Api Rust SDK".to_string(), + } + } +} diff --git a/seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs b/seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs new file mode 100644 index 000000000000..e5572d11b299 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs @@ -0,0 +1,270 @@ +//! Flexible datetime parsing module +//! +//! This module provides serde helpers for parsing datetime strings that may or may not +//! include a timezone suffix. It accepts both RFC3339 format (with Z or +00:00 suffix) +//! and ISO 8601 format without timezone (assuming UTC). +//! +//! Supported formats: +//! - `2024-01-15T09:30:00Z` (RFC3339 with Z) +//! - `2024-01-15T09:30:00+00:00` (RFC3339 with offset) +//! - `2024-01-15T09:30:00` (ISO 8601 without timezone, assumes UTC) +//! - `2024-01-15T09:30:00.123Z` (with fractional seconds and Z) +//! - `2024-01-15T09:30:00.123` (with fractional seconds, no timezone) +//! +//! Two submodules are provided: +//! - `utc`: Parses into `DateTime`, converting all datetimes to UTC +//! - `offset`: Parses into `DateTime`, preserving original timezone + +/// Module for DateTime with flexible parsing - converts all datetimes to UTC +pub mod utc { + use chrono::{DateTime, NaiveDateTime, Utc}; + use serde::{self, Deserialize, Deserializer, Serializer}; + + /// Serialize a DateTime to RFC3339 format + pub fn serialize(date: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&date.to_rfc3339()) + } + + /// Deserialize a datetime string that may or may not include a timezone suffix. + /// If no timezone is present, UTC is assumed. All datetimes are converted to UTC. + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + parse_flexible_datetime(&s).map_err(serde::de::Error::custom) + } + + /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. + fn parse_flexible_datetime(s: &str) -> Result, String> { + // First, try parsing as RFC3339 (with timezone) + if let Ok(dt) = DateTime::parse_from_rfc3339(s) { + return Ok(dt.with_timezone(&Utc)); + } + + // Try parsing as NaiveDateTime (without timezone) and assume UTC + // Try with fractional seconds first + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { + return Ok(naive.and_utc()); + } + + // Try without fractional seconds + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { + return Ok(naive.and_utc()); + } + + Err(format!( + "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ + or ISO 8601 format (e.g., '2024-01-15T09:30:00')", + s + )) + } + + /// Module for optional DateTime fields with flexible parsing + pub mod option { + use super::*; + + pub fn serialize(date: &Option>, serializer: S) -> Result + where + S: Serializer, + { + match date { + Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let opt: Option = Option::deserialize(deserializer)?; + match opt { + Some(s) => parse_flexible_datetime(&s) + .map(Some) + .map_err(serde::de::Error::custom), + None => Ok(None), + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_parse_rfc3339_with_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_rfc3339_with_offset() { + let result = parse_flexible_datetime("2024-01-15T09:30:00+00:00"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_without_timezone() { + let result = parse_flexible_datetime("2024-01-15T09:30:00"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_with_fractional_seconds() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_with_fractional_seconds_and_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); + assert!(result.is_ok()); + } + } +} + +/// Module for DateTime with flexible parsing - preserves original timezone +pub mod offset { + use chrono::{DateTime, FixedOffset, NaiveDateTime}; + use serde::{self, Deserialize, Deserializer, Serializer}; + + /// Serialize a DateTime to RFC3339 format + pub fn serialize(date: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&date.to_rfc3339()) + } + + /// Deserialize a datetime string that may or may not include a timezone suffix. + /// If no timezone is present, UTC (+00:00) is assumed. + /// The original timezone offset is preserved when present. + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + parse_flexible_datetime(&s).map_err(serde::de::Error::custom) + } + + /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. + /// Preserves the original timezone offset when present, assumes UTC when not. + fn parse_flexible_datetime(s: &str) -> Result, String> { + // First, try parsing as RFC3339 (with timezone) - this preserves the original offset + if let Ok(dt) = DateTime::parse_from_rfc3339(s) { + return Ok(dt); + } + + // Try parsing as NaiveDateTime (without timezone) and assume UTC (+00:00) + let utc_offset = FixedOffset::east_opt(0).unwrap(); + + // Try with fractional seconds first + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { + return Ok(naive.and_local_timezone(utc_offset).unwrap()); + } + + // Try without fractional seconds + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { + return Ok(naive.and_local_timezone(utc_offset).unwrap()); + } + + Err(format!( + "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ + or ISO 8601 format (e.g., '2024-01-15T09:30:00')", + s + )) + } + + /// Module for optional DateTime fields with flexible parsing + pub mod option { + use super::*; + + pub fn serialize( + date: &Option>, + serializer: S, + ) -> Result + where + S: Serializer, + { + match date { + Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D>( + deserializer: D, + ) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let opt: Option = Option::deserialize(deserializer)?; + match opt { + Some(s) => parse_flexible_datetime(&s) + .map(Some) + .map_err(serde::de::Error::custom), + None => Ok(None), + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_parse_rfc3339_with_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); + assert!(result.is_ok()); + let dt = result.unwrap(); + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_parse_rfc3339_with_offset() { + let result = parse_flexible_datetime("2024-01-15T09:30:00-05:00"); + assert!(result.is_ok()); + let dt = result.unwrap(); + // -05:00 = -5 * 3600 = -18000 seconds + assert_eq!(dt.offset().local_minus_utc(), -18000); + } + + #[test] + fn test_parse_without_timezone() { + let result = parse_flexible_datetime("2024-01-15T09:30:00"); + assert!(result.is_ok()); + let dt = result.unwrap(); + // Should assume UTC (+00:00) + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_parse_with_fractional_seconds() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); + assert!(result.is_ok()); + let dt = result.unwrap(); + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_parse_with_fractional_seconds_and_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); + assert!(result.is_ok()); + let dt = result.unwrap(); + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_preserves_positive_offset() { + let result = parse_flexible_datetime("2024-01-15T09:30:00+09:00"); + assert!(result.is_ok()); + let dt = result.unwrap(); + // +09:00 = 9 * 3600 = 32400 seconds + assert_eq!(dt.offset().local_minus_utc(), 32400); + } + } +} diff --git a/seed/rust-sdk/allof-inline/src/core/http_client.rs b/seed/rust-sdk/allof-inline/src/core/http_client.rs new file mode 100644 index 000000000000..d61f8b46a3a1 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/http_client.rs @@ -0,0 +1,569 @@ +use crate::{join_url, ApiError, ClientConfig, OAuthTokenProvider, RequestOptions}; +use futures::{Stream, StreamExt}; +use reqwest::{ + header::{HeaderName, HeaderValue}, + Client, Method, Request, Response, +}; +use serde::de::DeserializeOwned; + +use std::{ + pin::Pin, + str::FromStr, + sync::Arc, + task::{Context, Poll}, +}; + +/// A streaming byte stream for downloading files efficiently +pub struct ByteStream { + content_length: Option, + inner: Pin> + Send>>, +} + +impl ByteStream { + /// Create a new ByteStream from a Response + pub(crate) fn new(response: Response) -> Self { + let content_length = response.content_length(); + let stream = response.bytes_stream(); + + Self { + content_length, + inner: Box::pin(stream), + } + } + + /// Collect the entire stream into a `Vec` + /// + /// This consumes the stream and buffers all data into memory. + /// For large files, prefer using `try_next()` to process chunks incrementally. + /// + /// # Example + /// ```no_run + /// let stream = client.download_file().await?; + /// let bytes = stream.collect().await?; + /// ``` + pub async fn collect(mut self) -> Result, ApiError> { + let mut result = Vec::new(); + while let Some(chunk) = self.inner.next().await { + result.extend_from_slice(&chunk.map_err(ApiError::Network)?); + } + Ok(result) + } + + /// Get the next chunk from the stream + /// + /// Returns `Ok(Some(bytes))` if a chunk is available, + /// `Ok(None)` if the stream is finished, or an error. + /// + /// # Example + /// ```no_run + /// let mut stream = client.download_file().await?; + /// while let Some(chunk) = stream.try_next().await? { + /// process_chunk(&chunk); + /// } + /// ``` + pub async fn try_next(&mut self) -> Result, ApiError> { + match self.inner.next().await { + Some(Ok(bytes)) => Ok(Some(bytes)), + Some(Err(e)) => Err(ApiError::Network(e)), + None => Ok(None), + } + } + + /// Get the content length from response headers if available + pub fn content_length(&self) -> Option { + self.content_length + } +} + +impl Stream for ByteStream { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.inner.as_mut().poll_next(cx) { + Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))), + Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ApiError::Network(e)))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +/// Configuration for OAuth token fetching. +/// +/// This struct contains all the information needed to automatically fetch +/// and refresh OAuth tokens. +#[derive(Clone)] +pub struct OAuthConfig { + /// The OAuth token provider that manages token caching and refresh + pub token_provider: Arc, + /// The token endpoint path (e.g., "/token") + pub token_endpoint: String, +} + +/// Response from an OAuth token endpoint. +#[derive(Debug, Clone, serde::Deserialize)] +struct OAuthTokenResponse { + access_token: String, + #[serde(default)] + expires_in: Option, +} + +/// Internal HTTP client that handles requests with authentication and retries +#[derive(Clone)] +pub struct HttpClient { + client: Client, + config: ClientConfig, + /// Optional OAuth configuration for automatic token management + oauth_config: Option, +} + +impl HttpClient { + /// Creates a new HttpClient without OAuth support. + pub fn new(config: ClientConfig) -> Result { + Self::new_with_oauth(config, None) + } + + /// Creates a new HttpClient with optional OAuth support. + /// + /// When `oauth_config` is provided, the client will automatically fetch and refresh + /// OAuth tokens before making requests. + pub fn new_with_oauth( + config: ClientConfig, + oauth_config: Option, + ) -> Result { + let client = Client::builder() + .timeout(config.timeout) + .user_agent(&config.user_agent) + .build() + .map_err(ApiError::Network)?; + + Ok(Self { + client, + config, + oauth_config, + }) + } + + /// Returns the configured base URL. + pub fn base_url(&self) -> &str { + &self.config.base_url + } + + /// Returns a reference to the client configuration. + pub fn config(&self) -> &ClientConfig { + &self.config + } + + /// Execute a request with the given method, path, and options + pub async fn execute_request( + &self, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result + where + T: DeserializeOwned, // Generic T: DeserializeOwned means the response will be automatically deserialized into whatever type you specify: + { + let url = join_url(&self.config.base_url, path); + let mut request = self.client.request(method, &url); + + // Apply query parameters if provided + if let Some(params) = query_params { + request = request.query(¶ms); + } + + // Apply additional query parameters from options + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + // Apply body if provided + if let Some(body) = body { + request = request.json(&body); + } + + // Build the request + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + // Apply authentication and headers + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + // Execute with retries + let response = self.execute_with_retries(req, &options).await?; + self.parse_response(response).await + } + + /// Execute a request with an explicit base URL override. + /// + /// Used for multi-URL environments where different endpoints + /// resolve to different base URLs. + pub async fn execute_request_with_base_url( + &self, + base_url: &str, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result + where + T: DeserializeOwned, + { + let url = join_url(base_url, path); + let mut request = self.client.request(method, &url); + + if let Some(params) = query_params { + request = request.query(¶ms); + } + + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + if let Some(body) = body { + request = request.json(&body); + } + + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + let response = self.execute_with_retries(req, &options).await?; + self.parse_response(response).await + } + + async fn apply_auth_headers( + &self, + request: &mut Request, + options: &Option, + ) -> Result<(), ApiError> { + let headers = request.headers_mut(); + + // Apply API key (request options override config) + let api_key = options + .as_ref() + .and_then(|opts| opts.api_key.as_ref()) + .or(self.config.api_key.as_ref()); + + if let Some(key) = api_key { + let header_value = key.to_string(); + headers.insert( + "api_key", + header_value.parse().map_err(|_| ApiError::InvalidHeader)?, + ); + } + + // Apply bearer token - priority: request options > OAuth > config + let token = if let Some(opts) = options.as_ref() { + if opts.token.is_some() { + opts.token.clone() + } else { + None + } + } else { + None + }; + + let token = match token { + Some(t) => Some(t), + None => { + // Try OAuth token provider if configured + if let Some(oauth_config) = &self.oauth_config { + Some(self.get_oauth_token(oauth_config).await?) + } else { + // Fall back to static token from config + self.config.token.clone() + } + } + }; + + if let Some(token) = token { + let auth_value = format!("Bearer {}", token); + headers.insert( + "Authorization", + auth_value.parse().map_err(|_| ApiError::InvalidHeader)?, + ); + } + + Ok(()) + } + + /// Fetches an OAuth token, using the cached token if valid or fetching a new one. + async fn get_oauth_token(&self, oauth_config: &OAuthConfig) -> Result { + let token_provider = &oauth_config.token_provider; + let token_endpoint = &oauth_config.token_endpoint; + let client_id = token_provider.client_id().to_string(); + let client_secret = token_provider.client_secret().to_string(); + let base_url = self.config.base_url.clone(); + + // Use the async get_or_fetch method with a closure that fetches the token + token_provider + .get_or_fetch_async(|| async { + self.fetch_oauth_token(&base_url, token_endpoint, &client_id, &client_secret) + .await + }) + .await + } + + /// Makes an HTTP request to the OAuth token endpoint to fetch a new token. + async fn fetch_oauth_token( + &self, + base_url: &str, + token_endpoint: &str, + client_id: &str, + client_secret: &str, + ) -> Result<(String, u64), ApiError> { + let url = join_url(base_url, token_endpoint); + + // Build the token request body + let body = serde_json::json!({ + "client_id": client_id, + "client_secret": client_secret, + "grant_type": "client_credentials" + }); + + let response = self + .client + .request(Method::POST, &url) + .json(&body) + .send() + .await + .map_err(ApiError::Network)?; + + if !response.status().is_success() { + let status_code = response.status().as_u16(); + let body = response.text().await.ok(); + return Err(ApiError::from_response(status_code, body.as_deref())); + } + + // Parse the token response + let token_response: OAuthTokenResponse = + response.json().await.map_err(ApiError::Network)?; + + let expires_in = token_response.expires_in.unwrap_or(3600) as u64; + Ok((token_response.access_token, expires_in)) + } + + fn apply_custom_headers( + &self, + request: &mut Request, + options: &Option, + ) -> Result<(), ApiError> { + let headers = request.headers_mut(); + + // Apply config-level custom headers + for (key, value) in &self.config.custom_headers { + headers.insert( + HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, + HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, + ); + } + + // Apply request-level custom headers (override config) + if let Some(options) = options { + for (key, value) in &options.additional_headers { + headers.insert( + HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, + HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, + ); + } + } + + Ok(()) + } + + async fn execute_with_retries( + &self, + request: Request, + options: &Option, + ) -> Result { + let max_retries = options + .as_ref() + .and_then(|opts| opts.max_retries) + .unwrap_or(self.config.max_retries); + + let mut last_error = None; + + for attempt in 0..=max_retries { + let cloned_request = request.try_clone().ok_or(ApiError::RequestClone)?; + + match self.client.execute(cloned_request).await { + Ok(response) if response.status().is_success() => return Ok(response), + Ok(response) => { + let status_code = response.status().as_u16(); + let body = response.text().await.ok(); + return Err(ApiError::from_response(status_code, body.as_deref())); + } + Err(e) if attempt < max_retries => { + last_error = Some(e); + // Exponential backoff + let delay = std::time::Duration::from_millis(100 * 2_u64.pow(attempt)); + tokio::time::sleep(delay).await; + } + Err(e) => return Err(ApiError::Network(e)), + } + } + + Err(ApiError::Network(last_error.unwrap())) + } + + async fn parse_response(&self, response: Response) -> Result + where + T: DeserializeOwned, + { + let status = response.status().as_u16(); + let text = response.text().await.map_err(ApiError::Network)?; + + // Handle empty response bodies (e.g., 202 Accepted for deferred requests) + if text.is_empty() { + return Err(ApiError::Http { + status, + message: String::new(), + }); + } + + serde_json::from_str(&text).map_err(ApiError::Serialization) + } + + /// Execute a request and return a streaming response (for large file downloads) + /// + /// This method returns a `ByteStream` that can be used to download large files + /// efficiently without loading the entire content into memory. The stream can be + /// consumed chunk by chunk, written directly to disk, or collected into bytes. + /// + /// # Examples + /// + /// **Option 1: Collect all bytes into memory** + /// ```no_run + /// let stream = client.execute_stream_request( + /// Method::GET, + /// "/file", + /// None, + /// None, + /// None, + /// ).await?; + /// + /// let bytes = stream.collect().await?; + /// ``` + /// + /// **Option 2: Process chunks with try_next()** + /// ```no_run + /// let mut stream = client.execute_stream_request( + /// Method::GET, + /// "/large-file", + /// None, + /// None, + /// None, + /// ).await?; + /// + /// while let Some(chunk) = stream.try_next().await? { + /// process_chunk(&chunk); + /// } + /// ``` + /// + /// **Option 3: Stream with futures::Stream trait** + /// ```no_run + /// use futures::StreamExt; + /// + /// let stream = client.execute_stream_request( + /// Method::GET, + /// "/large-file", + /// None, + /// None, + /// None, + /// ).await?; + /// + /// let mut file = tokio::fs::File::create("output.mp4").await?; + /// let mut stream = std::pin::pin!(stream); + /// while let Some(chunk) = stream.next().await { + /// let chunk = chunk?; + /// tokio::io::AsyncWriteExt::write_all(&mut file, &chunk).await?; + /// } + /// ``` + pub async fn execute_stream_request( + &self, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result { + let url = join_url(&self.config.base_url, path); + let mut request = self.client.request(method, &url); + + // Apply query parameters if provided + if let Some(params) = query_params { + request = request.query(¶ms); + } + + // Apply additional query parameters from options + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + // Apply body if provided + if let Some(body) = body { + request = request.json(&body); + } + + // Build the request + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + // Apply authentication and headers + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + // Execute with retries + let response = self.execute_with_retries(req, &options).await?; + + // Return streaming response + Ok(ByteStream::new(response)) + } + + /// Execute a streaming request with an explicit base URL override. + pub async fn execute_stream_request_with_base_url( + &self, + base_url: &str, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result { + let url = join_url(base_url, path); + let mut request = self.client.request(method, &url); + + if let Some(params) = query_params { + request = request.query(¶ms); + } + + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + if let Some(body) = body { + request = request.json(&body); + } + + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + let response = self.execute_with_retries(req, &options).await?; + + Ok(ByteStream::new(response)) + } +} diff --git a/seed/rust-sdk/allof-inline/src/core/mod.rs b/seed/rust-sdk/allof-inline/src/core/mod.rs new file mode 100644 index 000000000000..71a75dbb141b --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/mod.rs @@ -0,0 +1,14 @@ +//! Core client infrastructure + +pub mod flexible_datetime; +mod http_client; +mod oauth_token_provider; +mod query_parameter_builder; +mod request_options; +mod utils; + +pub use http_client::{ByteStream, HttpClient, OAuthConfig}; +pub use oauth_token_provider::OAuthTokenProvider; +pub use query_parameter_builder::{parse_structured_query, QueryBuilder, QueryBuilderError}; +pub use request_options::RequestOptions; +pub use utils::join_url; diff --git a/seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs b/seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs new file mode 100644 index 000000000000..09222bedd8e0 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs @@ -0,0 +1,363 @@ +use std::future::Future; +use std::sync::Mutex; +use std::time::{Duration, Instant}; +use tokio::sync::Mutex as AsyncMutex; + +/// Buffer time in seconds subtracted from token expiration to ensure +/// we refresh the token before it actually expires. +const EXPIRATION_BUFFER_SECONDS: u64 = 120; // 2 minutes + +/// Default expiry time in seconds used when the OAuth response doesn't include an expires_in value. +const DEFAULT_EXPIRY_SECONDS: u64 = 3600; // 1 hour fallback + +/// Manages OAuth access tokens, including caching and automatic refresh. +/// +/// This provider implements thread-safe token management with automatic expiration +/// handling. It uses a double-checked locking pattern to minimize lock contention +/// while ensuring only one thread fetches a new token at a time. +/// +/// # Example +/// +/// ```rust,ignore +/// use crate::OAuthTokenProvider; +/// +/// let provider = OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); +/// +/// // Get or fetch a token (sync) +/// let token = provider.get_or_fetch(|| { +/// // Your token fetching logic here +/// // Returns (access_token, expires_in_seconds) +/// Ok(("token".to_string(), Some(3600))) +/// })?; +/// +/// // Get or fetch a token (async) +/// let token = provider.get_or_fetch_async(|| async { +/// // Your async token fetching logic here +/// Ok(("token".to_string(), Some(3600))) +/// }).await?; +/// ``` +pub struct OAuthTokenProvider { + client_id: String, + client_secret: String, + inner: Mutex, + /// Separate mutex to ensure only one thread fetches a new token at a time (sync) + fetch_lock: Mutex<()>, + /// Async mutex for async token fetching + async_fetch_lock: AsyncMutex<()>, +} + +struct OAuthTokenProviderInner { + access_token: Option, + expires_at: Option, +} + +impl OAuthTokenProvider { + /// Creates a new OAuthTokenProvider with the given credentials. + pub fn new(client_id: String, client_secret: String) -> Self { + Self { + client_id, + client_secret, + inner: Mutex::new(OAuthTokenProviderInner { + access_token: None, + expires_at: None, + }), + fetch_lock: Mutex::new(()), + async_fetch_lock: AsyncMutex::new(()), + } + } + + /// Returns the client ID. + pub fn client_id(&self) -> &str { + &self.client_id + } + + /// Returns the client secret. + pub fn client_secret(&self) -> &str { + &self.client_secret + } + + /// Sets the cached access token and its expiration time. + /// + /// The `expires_in` parameter is the number of seconds until the token expires. + /// A buffer is applied to refresh before actual expiration. + pub fn set_token(&self, access_token: String, expires_in: u64) { + let mut inner = self.inner.lock().unwrap(); + inner.access_token = Some(access_token); + + if expires_in > 0 { + // Apply buffer to refresh before actual expiration + let effective_expires_in = expires_in.saturating_sub(EXPIRATION_BUFFER_SECONDS); + inner.expires_at = Some(Instant::now() + Duration::from_secs(effective_expires_in)); + } else { + // No expiration info, token won't auto-refresh based on time + inner.expires_at = None; + } + } + + /// Returns the cached access token if it's still valid. + /// + /// Returns `None` if the token is expired or not set. + pub fn get_token(&self) -> Option { + let inner = self.inner.lock().unwrap(); + + if let Some(ref token) = inner.access_token { + // Check if token is still valid + if let Some(expires_at) = inner.expires_at { + if Instant::now() < expires_at { + return Some(token.clone()); + } + } else { + // No expiration set, token is always valid + return Some(token.clone()); + } + } + + None + } + + /// Returns a valid token, fetching a new one if necessary (synchronous version). + /// + /// The `fetch_func` is called at most once even if multiple threads call `get_or_fetch` + /// concurrently when the token is expired. It should return `(access_token, expires_in_seconds)`. + /// + /// # Arguments + /// + /// * `fetch_func` - A function that fetches a new token. Returns `Result<(String, u64), E>` + /// where the tuple contains (access_token, expires_in_seconds). + /// + /// # Example + /// + /// ```rust,ignore + /// let token = provider.get_or_fetch(|| { + /// // Call your OAuth endpoint here (sync) + /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret())?; + /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) + /// })?; + /// ``` + pub fn get_or_fetch(&self, fetch_func: F) -> Result + where + F: FnOnce() -> Result<(String, u64), E>, + { + // Fast path: check if we have a valid token + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Slow path: acquire fetch lock to ensure only one thread fetches + let _fetch_guard = self.fetch_lock.lock().unwrap(); + + // Double-check after acquiring lock (another thread may have fetched) + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Fetch new token + let (access_token, expires_in) = fetch_func()?; + + // Use default expiry if not provided + let effective_expires_in = if expires_in > 0 { + expires_in + } else { + DEFAULT_EXPIRY_SECONDS + }; + + self.set_token(access_token.clone(), effective_expires_in); + Ok(access_token) + } + + /// Returns a valid token, fetching a new one if necessary (async version). + /// + /// This is the async version of `get_or_fetch` for use with async token fetching. + /// The `fetch_func` is called at most once even if multiple tasks call `get_or_fetch_async` + /// concurrently when the token is expired. + /// + /// # Arguments + /// + /// * `fetch_func` - An async function that fetches a new token. Returns `Result<(String, u64), E>` + /// where the tuple contains (access_token, expires_in_seconds). + /// + /// # Example + /// + /// ```rust,ignore + /// let token = provider.get_or_fetch_async(|| async { + /// // Call your OAuth endpoint here (async) + /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret()).await?; + /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) + /// }).await?; + /// ``` + pub async fn get_or_fetch_async(&self, fetch_func: F) -> Result + where + F: FnOnce() -> Fut, + Fut: Future>, + { + // Fast path: check if we have a valid token + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Slow path: acquire async fetch lock to ensure only one task fetches + let _fetch_guard = self.async_fetch_lock.lock().await; + + // Double-check after acquiring lock (another task may have fetched) + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Fetch new token + let (access_token, expires_in) = fetch_func().await?; + + // Use default expiry if not provided + let effective_expires_in = if expires_in > 0 { + expires_in + } else { + DEFAULT_EXPIRY_SECONDS + }; + + self.set_token(access_token.clone(), effective_expires_in); + Ok(access_token) + } + + /// Returns `true` if the token needs to be refreshed. + /// + /// This is useful for proactively refreshing tokens before they expire. + pub fn needs_refresh(&self) -> bool { + let inner = self.inner.lock().unwrap(); + + if inner.access_token.is_none() { + return true; + } + + if let Some(expires_at) = inner.expires_at { + if Instant::now() >= expires_at { + return true; + } + } + + false + } + + /// Clears the cached token. + /// + /// This can be used to force a token refresh on the next request. + pub fn reset(&self) { + let mut inner = self.inner.lock().unwrap(); + inner.access_token = None; + inner.expires_at = None; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + use std::thread; + + #[test] + fn test_new_provider() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + assert_eq!(provider.client_id(), "client_id"); + assert_eq!(provider.client_secret(), "client_secret"); + assert!(provider.get_token().is_none()); + assert!(provider.needs_refresh()); + } + + #[test] + fn test_set_and_get_token() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + provider.set_token("test_token".to_string(), 3600); + + let token = provider.get_token(); + assert!(token.is_some()); + assert_eq!(token.unwrap(), "test_token"); + assert!(!provider.needs_refresh()); + } + + #[test] + fn test_expired_token() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + // Set token with 0 expiry (will be expired immediately due to buffer) + provider.set_token("test_token".to_string(), 1); + + // Token should be expired (1 second - 120 second buffer = expired) + assert!(provider.get_token().is_none()); + assert!(provider.needs_refresh()); + } + + #[test] + fn test_get_or_fetch() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + let result: Result = + provider.get_or_fetch(|| Ok(("fetched_token".to_string(), 3600))); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "fetched_token"); + + // Second call should return cached token + let result2: Result = provider.get_or_fetch(|| { + panic!("Should not be called - token is cached"); + }); + + assert!(result2.is_ok()); + assert_eq!(result2.unwrap(), "fetched_token"); + } + + #[test] + fn test_reset() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + provider.set_token("test_token".to_string(), 3600); + assert!(provider.get_token().is_some()); + + provider.reset(); + assert!(provider.get_token().is_none()); + assert!(provider.needs_refresh()); + } + + #[test] + fn test_concurrent_access() { + let provider = Arc::new(OAuthTokenProvider::new( + "client_id".to_string(), + "client_secret".to_string(), + )); + let fetch_count = Arc::new(AtomicUsize::new(0)); + + let mut handles = vec![]; + + for _ in 0..10 { + let provider_clone = Arc::clone(&provider); + let fetch_count_clone = Arc::clone(&fetch_count); + + let handle = thread::spawn(move || { + let result: Result = provider_clone.get_or_fetch(|| { + fetch_count_clone.fetch_add(1, Ordering::SeqCst); + // Simulate some work + thread::sleep(Duration::from_millis(10)); + Ok(("concurrent_token".to_string(), 3600)) + }); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "concurrent_token"); + }); + + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + // Due to double-checked locking, fetch should only be called once + // (or at most a few times if threads race before the first fetch completes) + let count = fetch_count.load(Ordering::SeqCst); + assert!(count >= 1 && count <= 3, "Fetch was called {} times", count); + } +} diff --git a/seed/rust-sdk/allof-inline/src/core/pagination.rs b/seed/rust-sdk/allof-inline/src/core/pagination.rs new file mode 100644 index 000000000000..6b41f78a4858 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/pagination.rs @@ -0,0 +1,541 @@ +use std::collections::VecDeque; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use futures::Stream; +use serde_json::Value; + +use crate::{ApiError, HttpClient}; + +/// Result of a pagination request +#[derive(Debug)] +pub struct PaginationResult { + pub items: Vec, + pub next_cursor: Option, + pub has_next_page: bool, +} + +/// Async paginator that implements Stream for iterating over paginated results +pub struct AsyncPaginator { + http_client: Arc, + page_loader: Box< + dyn Fn( + Arc, + Option, + ) + -> Pin, ApiError>> + Send>> + + Send + + Sync, + >, + current_page: VecDeque, + current_cursor: Option, + has_next_page: bool, + loading_next: + Option, ApiError>> + Send>>>, +} + +impl AsyncPaginator { + pub fn new( + http_client: Arc, + page_loader: F, + initial_cursor: Option, + ) -> Result + where + F: Fn(Arc, Option) -> Fut + Send + Sync + 'static, + Fut: Future, ApiError>> + Send + 'static, + { + Ok(Self { + http_client, + page_loader: Box::new(move |client, cursor| Box::pin(page_loader(client, cursor))), + current_page: VecDeque::new(), + current_cursor: initial_cursor, + has_next_page: true, // Assume true initially, will be updated after first request + loading_next: None, + }) + } + + /// Check if there are more pages available + pub fn has_next_page(&self) -> bool { + !self.current_page.is_empty() || self.has_next_page + } + + /// Load the next page explicitly + pub async fn next_page(&mut self) -> Result, ApiError> { + if !self.has_next_page { + return Ok(Vec::new()); + } + + let result = + (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()).await?; + + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + + Ok(result.items) + } +} + +impl Stream for AsyncPaginator +where + T: Unpin, +{ + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // If we have items in the current page, return the next one + if let Some(item) = self.current_page.pop_front() { + return Poll::Ready(Some(Ok(item))); + } + + // If we're already loading the next page, poll that future + if let Some(ref mut loading_future) = self.loading_next { + match loading_future.as_mut().poll(cx) { + Poll::Ready(Ok(result)) => { + self.current_page.extend(result.items); + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + self.loading_next = None; + + // Try to get the next item from the newly loaded page + if let Some(item) = self.current_page.pop_front() { + return Poll::Ready(Some(Ok(item))); + } else if !self.has_next_page { + return Poll::Ready(None); + } + // Fall through to start loading next page + } + Poll::Ready(Err(e)) => { + self.loading_next = None; + return Poll::Ready(Some(Err(e))); + } + Poll::Pending => return Poll::Pending, + } + } + + // If we have no more pages to load, we're done + if !self.has_next_page { + return Poll::Ready(None); + } + + // Start loading the next page + let future = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()); + self.loading_next = Some(future); + + // Poll the future immediately + if let Some(ref mut loading_future) = self.loading_next { + match loading_future.as_mut().poll(cx) { + Poll::Ready(Ok(result)) => { + self.current_page.extend(result.items); + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + self.loading_next = None; + + if let Some(item) = self.current_page.pop_front() { + Poll::Ready(Some(Ok(item))) + } else if !self.has_next_page { + Poll::Ready(None) + } else { + // This shouldn't happen, but just in case + cx.waker().wake_by_ref(); + Poll::Pending + } + } + Poll::Ready(Err(e)) => { + self.loading_next = None; + Poll::Ready(Some(Err(e))) + } + Poll::Pending => Poll::Pending, + } + } else { + Poll::Pending + } + } +} + +/// Synchronous paginator for blocking iteration +pub struct SyncPaginator { + http_client: Arc, + page_loader: Box< + dyn Fn(Arc, Option) -> Result, ApiError> + + Send + + Sync, + >, + current_page: VecDeque, + current_cursor: Option, + has_next_page: bool, +} + +impl SyncPaginator { + pub fn new( + http_client: Arc, + page_loader: F, + initial_cursor: Option, + ) -> Result + where + F: Fn(Arc, Option) -> Result, ApiError> + + Send + + Sync + + 'static, + { + Ok(Self { + http_client, + page_loader: Box::new(page_loader), + current_page: VecDeque::new(), + current_cursor: initial_cursor, + has_next_page: true, // Assume true initially + }) + } + + /// Check if there are more pages available + pub fn has_next_page(&self) -> bool { + !self.current_page.is_empty() || self.has_next_page + } + + /// Load the next page explicitly + pub fn next_page(&mut self) -> Result, ApiError> { + if !self.has_next_page { + return Ok(Vec::new()); + } + + let result = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone())?; + + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + + Ok(result.items) + } + + /// Get all remaining items by loading all pages + pub fn collect_all(&mut self) -> Result, ApiError> { + let mut all_items = Vec::new(); + + // Add items from current page + while let Some(item) = self.current_page.pop_front() { + all_items.push(item); + } + + // Load all remaining pages + while self.has_next_page { + let page_items = self.next_page()?; + all_items.extend(page_items); + } + + Ok(all_items) + } +} + +impl Iterator for SyncPaginator { + type Item = Result; + + fn next(&mut self) -> Option { + // If we have items in the current page, return the next one + if let Some(item) = self.current_page.pop_front() { + return Some(Ok(item)); + } + + // If we have no more pages to load, we're done + if !self.has_next_page { + return None; + } + + // Load the next page + match (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()) { + Ok(result) => { + self.current_page.extend(result.items); + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + + // Return the first item from the newly loaded page + self.current_page.pop_front().map(Ok) + } + Err(e) => Some(Err(e)), + } + } +} + +/// Trait for types that can provide pagination metadata +pub trait Paginated { + /// Extract the items from this page + fn items(&self) -> &[T]; + + /// Get the cursor for the next page, if any + fn next_cursor(&self) -> Option<&str>; + + /// Check if there's a next page available + fn has_next_page(&self) -> bool; +} + +/// Trait for types that can provide offset-based pagination metadata +pub trait OffsetPaginated { + /// Extract the items from this page + fn items(&self) -> &[T]; + + /// Check if there's a next page available + fn has_next_page(&self) -> bool; + + /// Get the current page size (for calculating next offset) + fn page_size(&self) -> usize { + self.items().len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ClientConfig; + + fn make_http_client() -> Arc { + Arc::new(HttpClient::new(ClientConfig::default()).expect("Failed to create test HttpClient")) + } + + // =========================== + // SyncPaginator tests + // =========================== + + #[test] + fn test_sync_paginator_has_next_page_initially() { + let client = make_http_client(); + let paginator = SyncPaginator::::new(client, |_client, _cursor| { + Ok(PaginationResult { + items: vec![], + next_cursor: None, + has_next_page: false, + }) + }, None).unwrap(); + assert!(paginator.has_next_page()); + } + + #[test] + fn test_sync_paginator_single_page() { + let client = make_http_client(); + let mut paginator = SyncPaginator::new(client, |_client, _cursor| { + Ok(PaginationResult { + items: vec!["a".to_string(), "b".to_string()], + next_cursor: None, + has_next_page: false, + }) + }, None).unwrap(); + + let page = paginator.next_page().unwrap(); + assert_eq!(page, vec!["a".to_string(), "b".to_string()]); + assert!(!paginator.has_next_page()); + } + + #[test] + fn test_sync_paginator_exhausted_returns_empty() { + let client = make_http_client(); + let mut paginator = SyncPaginator::new(client, |_client, _cursor| { + Ok(PaginationResult { + items: vec!["a".to_string()], + next_cursor: None, + has_next_page: false, + }) + }, None).unwrap(); + + let _ = paginator.next_page().unwrap(); + let empty = paginator.next_page().unwrap(); + assert!(empty.is_empty()); + } + + #[test] + fn test_sync_paginator_multiple_pages() { + let client = make_http_client(); + let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let count = call_count.clone(); + + let mut paginator = SyncPaginator::new(client, move |_client, cursor| { + let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + match call { + 0 => { + assert!(cursor.is_none()); + Ok(PaginationResult { + items: vec![1, 2], + next_cursor: Some("page2".to_string()), + has_next_page: true, + }) + } + 1 => { + assert_eq!(cursor, Some("page2".to_string())); + Ok(PaginationResult { + items: vec![3, 4], + next_cursor: None, + has_next_page: false, + }) + } + _ => panic!("Unexpected call"), + } + }, None).unwrap(); + + let page1 = paginator.next_page().unwrap(); + assert_eq!(page1, vec![1, 2]); + assert!(paginator.has_next_page()); + + let page2 = paginator.next_page().unwrap(); + assert_eq!(page2, vec![3, 4]); + assert!(!paginator.has_next_page()); + } + + #[test] + fn test_sync_paginator_collect_all() { + let client = make_http_client(); + let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let count = call_count.clone(); + + let mut paginator = SyncPaginator::new(client, move |_client, _cursor| { + let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + match call { + 0 => Ok(PaginationResult { + items: vec![1, 2], + next_cursor: Some("next".to_string()), + has_next_page: true, + }), + 1 => Ok(PaginationResult { + items: vec![3], + next_cursor: None, + has_next_page: false, + }), + _ => panic!("Unexpected call"), + } + }, None).unwrap(); + + let all = paginator.collect_all().unwrap(); + assert_eq!(all, vec![1, 2, 3]); + } + + #[test] + fn test_sync_paginator_iterator() { + let client = make_http_client(); + let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let count = call_count.clone(); + + let paginator = SyncPaginator::new(client, move |_client, _cursor| { + let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + match call { + 0 => Ok(PaginationResult { + items: vec![10, 20], + next_cursor: Some("p2".to_string()), + has_next_page: true, + }), + 1 => Ok(PaginationResult { + items: vec![30], + next_cursor: None, + has_next_page: false, + }), + _ => panic!("Unexpected call"), + } + }, None).unwrap(); + + let items: Vec = paginator.map(|r| r.unwrap()).collect(); + assert_eq!(items, vec![10, 20, 30]); + } + + #[test] + fn test_sync_paginator_error_propagation() { + let client = make_http_client(); + let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { + Err(ApiError::Serialization("test error".to_string())) + }, None).unwrap(); + + let result = paginator.next_page(); + assert!(result.is_err()); + } + + #[test] + fn test_sync_paginator_iterator_error() { + let client = make_http_client(); + let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { + Err(ApiError::Serialization("test error".to_string())) + }, None).unwrap(); + + let item = paginator.next(); + assert!(item.is_some()); + assert!(item.unwrap().is_err()); + } + + #[test] + fn test_sync_paginator_with_initial_cursor() { + let client = make_http_client(); + let mut paginator = SyncPaginator::new(client, |_client, cursor| { + assert_eq!(cursor, Some("start_here".to_string())); + Ok(PaginationResult { + items: vec!["item".to_string()], + next_cursor: None, + has_next_page: false, + }) + }, Some("start_here".to_string())).unwrap(); + + let page = paginator.next_page().unwrap(); + assert_eq!(page, vec!["item".to_string()]); + } + + // =========================== + // PaginationResult tests + // =========================== + + #[test] + fn test_pagination_result_fields() { + let result = PaginationResult { + items: vec![1, 2, 3], + next_cursor: Some("abc".to_string()), + has_next_page: true, + }; + assert_eq!(result.items.len(), 3); + assert_eq!(result.next_cursor, Some("abc".to_string())); + assert!(result.has_next_page); + } + + // =========================== + // Trait tests + // =========================== + + struct MockPage { + data: Vec, + cursor: Option, + has_more: bool, + } + + impl Paginated for MockPage { + fn items(&self) -> &[String] { + &self.data + } + fn next_cursor(&self) -> Option<&str> { + self.cursor.as_deref() + } + fn has_next_page(&self) -> bool { + self.has_more + } + } + + impl OffsetPaginated for MockPage { + fn items(&self) -> &[String] { + &self.data + } + fn has_next_page(&self) -> bool { + self.has_more + } + } + + #[test] + fn test_paginated_trait() { + let page = MockPage { + data: vec!["a".to_string(), "b".to_string()], + cursor: Some("next".to_string()), + has_more: true, + }; + assert_eq!(Paginated::items(&page).len(), 2); + assert_eq!(page.next_cursor(), Some("next")); + assert!(Paginated::has_next_page(&page)); + } + + #[test] + fn test_offset_paginated_default_page_size() { + let page = MockPage { + data: vec!["a".to_string(), "b".to_string(), "c".to_string()], + cursor: None, + has_more: false, + }; + assert_eq!(OffsetPaginated::page_size(&page), 3); + } +} diff --git a/seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs b/seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs new file mode 100644 index 000000000000..6f1a6976ec2b --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs @@ -0,0 +1,576 @@ +use chrono::{DateTime, TimeZone}; +use serde::Serialize; + +/// Modern query builder with type-safe method chaining +/// Provides a clean, Swift-like API for building HTTP query parameters +#[derive(Debug, Default)] +pub struct QueryBuilder { + params: Vec<(String, String)>, +} + +impl QueryBuilder { + /// Create a new query parameter builder + pub fn new() -> Self { + Self::default() + } + + /// Add a string parameter (accept both required/optional) + pub fn string(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v)); + } + self + } + + /// Add multiple string parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn string_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v)); + } + } + self + } + + /// Add an integer parameter (accept both required/optional) + pub fn int(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + self + } + + /// Add multiple integer parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn int_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + } + self + } + + /// Add a float parameter + pub fn float(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + self + } + + /// Add multiple float parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn float_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + } + self + } + + /// Add a boolean parameter + pub fn bool(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + self + } + + /// Add multiple boolean parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn bool_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + } + self + } + + /// Add a datetime parameter (any DateTime timezone) + pub fn datetime( + mut self, + key: &str, + value: impl Into>>, + ) -> Self + where + Tz::Offset: std::fmt::Display, + { + if let Some(v) = value.into() { + self.params.push(( + key.to_string(), + v.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), + )); + } + self + } + + /// Add a date parameter (converts NaiveDate to DateTime) + pub fn date(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + // Convert NaiveDate to DateTime at start of day + let datetime = v.and_hms_opt(0, 0, 0).unwrap().and_utc(); + self.params.push(( + key.to_string(), + datetime.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), + )); + } + self + } + + /// Add any serializable parameter (for enums and complex types) + pub fn serialize(mut self, key: &str, value: Option) -> Self { + if let Some(v) = value { + // For enums that implement Display, use the Display implementation + // to avoid JSON quotes in query parameters + if let Ok(serialized) = serde_json::to_string(&v) { + // Remove JSON quotes if the value is a simple string + let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { + serialized.trim_matches('"').to_string() + } else { + serialized + }; + self.params.push((key.to_string(), cleaned)); + } + } + self + } + + /// Add multiple serializable parameters with the same key (for allow-multiple query params with enums) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn serialize_array( + mut self, + key: &str, + values: impl IntoIterator, + ) -> Self { + for value in values { + if let Ok(serialized) = serde_json::to_string(&value) { + // Skip null values (from Option::None) + if serialized == "null" { + continue; + } + // Remove JSON quotes if the value is a simple string + let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { + serialized.trim_matches('"').to_string() + } else { + serialized + }; + self.params.push((key.to_string(), cleaned)); + } + } + self + } + + /// Parse and add a structured query string + /// Handles complex query patterns like: + /// - "key:value" patterns + /// - "key:value1,value2" (comma-separated values) + /// - Quoted values: "key:\"value with spaces\"" + /// - Space-separated terms (treated as AND logic) + pub fn structured_query(mut self, key: &str, value: impl Into>) -> Self { + if let Some(query_str) = value.into() { + if let Ok(parsed_params) = parse_structured_query(&query_str) { + self.params.extend(parsed_params); + } else { + // Fall back to simple query parameter if parsing fails + self.params.push((key.to_string(), query_str)); + } + } + self + } + + /// Build the final query parameters + pub fn build(self) -> Option> { + if self.params.is_empty() { + None + } else { + Some(self.params) + } + } +} + +/// Errors that can occur during structured query parsing +#[derive(Debug)] +pub enum QueryBuilderError { + InvalidQuerySyntax(String), +} + +impl std::fmt::Display for QueryBuilderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + QueryBuilderError::InvalidQuerySyntax(msg) => { + write!(f, "Invalid query syntax: {}", msg) + } + } + } +} + +impl std::error::Error for QueryBuilderError {} + +/// Parse structured query strings like "key:value key2:value1,value2" +/// Used for complex filtering patterns in APIs like Foxglove +/// +/// Supported patterns: +/// - Simple: "status:active" +/// - Multiple values: "type:sensor,camera" +/// - Quoted values: "location:\"New York\"" +/// - Complex: "status:active type:sensor location:\"San Francisco\"" +pub fn parse_structured_query(query: &str) -> Result, QueryBuilderError> { + let mut params = Vec::new(); + let terms = tokenize_query(query); + + for term in terms { + if let Some((key, values)) = term.split_once(':') { + // Handle comma-separated values + for value in values.split(',') { + let clean_value = value.trim_matches('"'); // Remove quotes + params.push((key.to_string(), clean_value.to_string())); + } + } else { + // For terms without colons, return error to be explicit about expected format + return Err(QueryBuilderError::InvalidQuerySyntax(format!( + "Cannot parse term '{}' - expected 'key:value' format for structured queries", + term + ))); + } + } + + Ok(params) +} + +/// Tokenize a query string, properly handling quoted strings +fn tokenize_query(input: &str) -> Vec { + let mut tokens = Vec::new(); + let mut current_token = String::new(); + let mut in_quotes = false; + let mut chars = input.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '"' => { + // Toggle quote state and include the quote in the token + in_quotes = !in_quotes; + current_token.push(c); + } + ' ' if !in_quotes => { + // Space outside quotes - end current token + if !current_token.is_empty() { + tokens.push(current_token.trim().to_string()); + current_token.clear(); + } + } + _ => { + // Any other character (including spaces inside quotes) + current_token.push(c); + } + } + } + + // Add the last token if there is one + if !current_token.is_empty() { + tokens.push(current_token.trim().to_string()); + } + + tokens +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::{NaiveDate, TimeZone, Utc}; + + // =========================== + // QueryBuilder tests + // =========================== + + #[test] + fn test_empty_builder_returns_none() { + let result = QueryBuilder::new().build(); + assert!(result.is_none()); + } + + #[test] + fn test_string_param_some() { + let result = QueryBuilder::new() + .string("name", Some("alice".to_string())) + .build(); + assert_eq!( + result, + Some(vec![("name".to_string(), "alice".to_string())]) + ); + } + + #[test] + fn test_string_param_none_skipped() { + let result = QueryBuilder::new().string("name", None::).build(); + assert!(result.is_none()); + } + + #[test] + fn test_int_param() { + let result = QueryBuilder::new().int("page", Some(42i64)).build(); + assert_eq!(result, Some(vec![("page".to_string(), "42".to_string())])); + } + + #[test] + fn test_int_param_none_skipped() { + let result = QueryBuilder::new().int("page", None::).build(); + assert!(result.is_none()); + } + + #[test] + fn test_float_param() { + let result = QueryBuilder::new().float("score", Some(3.14f64)).build(); + assert_eq!( + result, + Some(vec![("score".to_string(), "3.14".to_string())]) + ); + } + + #[test] + fn test_bool_param() { + let result = QueryBuilder::new().bool("active", Some(true)).build(); + assert_eq!( + result, + Some(vec![("active".to_string(), "true".to_string())]) + ); + } + + #[test] + fn test_datetime_param_formats_rfc3339() { + let dt = Utc.with_ymd_and_hms(2024, 1, 15, 9, 30, 0).unwrap(); + let result = QueryBuilder::new().datetime("since", Some(dt)).build(); + assert_eq!( + result, + Some(vec![( + "since".to_string(), + "2024-01-15T09:30:00Z".to_string() + )]) + ); + } + + #[test] + fn test_date_param_converts_to_midnight_utc() { + let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(); + let result = QueryBuilder::new().date("on", Some(date)).build(); + assert_eq!( + result, + Some(vec![("on".to_string(), "2024-01-15T00:00:00Z".to_string())]) + ); + } + + #[test] + fn test_string_array_multiple_entries() { + let result = QueryBuilder::new() + .string_array( + "tag", + vec!["a".to_string(), "b".to_string(), "c".to_string()], + ) + .build(); + assert_eq!( + result, + Some(vec![ + ("tag".to_string(), "a".to_string()), + ("tag".to_string(), "b".to_string()), + ("tag".to_string(), "c".to_string()), + ]) + ); + } + + #[test] + fn test_int_array() { + let result = QueryBuilder::new() + .int_array("ids", vec![1i64, 2, 3]) + .build(); + assert_eq!( + result, + Some(vec![ + ("ids".to_string(), "1".to_string()), + ("ids".to_string(), "2".to_string()), + ("ids".to_string(), "3".to_string()), + ]) + ); + } + + #[test] + fn test_float_array() { + let result = QueryBuilder::new() + .float_array("scores", vec![1.1f64, 2.2]) + .build(); + assert_eq!( + result, + Some(vec![ + ("scores".to_string(), "1.1".to_string()), + ("scores".to_string(), "2.2".to_string()), + ]) + ); + } + + #[test] + fn test_bool_array() { + let result = QueryBuilder::new() + .bool_array("flags", vec![true, false]) + .build(); + assert_eq!( + result, + Some(vec![ + ("flags".to_string(), "true".to_string()), + ("flags".to_string(), "false".to_string()), + ]) + ); + } + + #[test] + fn test_serialize_strips_json_quotes() { + let result = QueryBuilder::new() + .serialize("status", Some("active")) + .build(); + assert_eq!( + result, + Some(vec![("status".to_string(), "active".to_string())]) + ); + } + + #[test] + fn test_serialize_none_skipped() { + let result = QueryBuilder::new() + .serialize::("status", None) + .build(); + assert!(result.is_none()); + } + + #[test] + fn test_serialize_numeric_no_quotes() { + let result = QueryBuilder::new().serialize("count", Some(42)).build(); + assert_eq!(result, Some(vec![("count".to_string(), "42".to_string())])); + } + + #[test] + fn test_serialize_array_skips_null() { + let values: Vec> = vec![Some("a"), None, Some("b")]; + let result = QueryBuilder::new().serialize_array("items", values).build(); + assert_eq!( + result, + Some(vec![ + ("items".to_string(), "a".to_string()), + ("items".to_string(), "b".to_string()), + ]) + ); + } + + #[test] + fn test_method_chaining() { + let result = QueryBuilder::new() + .string("name", Some("alice".to_string())) + .int("page", Some(1i64)) + .bool("active", Some(true)) + .build(); + assert_eq!( + result, + Some(vec![ + ("name".to_string(), "alice".to_string()), + ("page".to_string(), "1".to_string()), + ("active".to_string(), "true".to_string()), + ]) + ); + } + + // =========================== + // parse_structured_query tests + // =========================== + + #[test] + fn test_parse_simple_key_value() { + let result = parse_structured_query("status:active").unwrap(); + assert_eq!(result, vec![("status".to_string(), "active".to_string())]); + } + + #[test] + fn test_parse_comma_separated_values() { + let result = parse_structured_query("type:sensor,camera").unwrap(); + assert_eq!( + result, + vec![ + ("type".to_string(), "sensor".to_string()), + ("type".to_string(), "camera".to_string()), + ] + ); + } + + #[test] + fn test_parse_multiple_terms() { + let result = parse_structured_query("status:active type:sensor").unwrap(); + assert_eq!( + result, + vec![ + ("status".to_string(), "active".to_string()), + ("type".to_string(), "sensor".to_string()), + ] + ); + } + + #[test] + fn test_parse_quoted_value() { + let result = parse_structured_query("location:\"New York\"").unwrap(); + assert_eq!( + result, + vec![("location".to_string(), "New York".to_string())] + ); + } + + #[test] + fn test_parse_bare_word_returns_error() { + let result = parse_structured_query("bareword"); + assert!(result.is_err()); + } + + #[test] + fn test_structured_query_builder_fallback() { + // When parsing fails, structured_query falls back to simple param + let result = QueryBuilder::new() + .structured_query("q", Some("bareword".to_string())) + .build(); + assert_eq!( + result, + Some(vec![("q".to_string(), "bareword".to_string())]) + ); + } + + #[test] + fn test_structured_query_builder_parses() { + let result = QueryBuilder::new() + .structured_query("q", Some("status:active".to_string())) + .build(); + assert_eq!( + result, + Some(vec![("status".to_string(), "active".to_string())]) + ); + } + + #[test] + fn test_structured_query_none_skipped() { + let result = QueryBuilder::new() + .structured_query("q", None::) + .build(); + assert!(result.is_none()); + } +} diff --git a/seed/rust-sdk/allof-inline/src/core/request_options.rs b/seed/rust-sdk/allof-inline/src/core/request_options.rs new file mode 100644 index 000000000000..80508c9dd6c3 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/request_options.rs @@ -0,0 +1,176 @@ +use std::collections::HashMap; +/// Options for customizing individual requests +#[derive(Debug, Clone, Default)] +pub struct RequestOptions { + /// API key for authentication (overrides client-level API key) + pub api_key: Option, + /// Bearer token for authentication (overrides client-level token) + pub token: Option, + /// Maximum number of retry attempts for failed requests + pub max_retries: Option, + /// Request timeout in seconds (overrides client-level timeout) + pub timeout_seconds: Option, + /// Additional headers to include in the request + pub additional_headers: HashMap, + /// Additional query parameters to include in the request + pub additional_query_params: HashMap, +} + +impl RequestOptions { + pub fn new() -> Self { + Self::default() + } + + pub fn api_key(mut self, key: impl Into) -> Self { + self.api_key = Some(key.into()); + self + } + + pub fn token(mut self, token: impl Into) -> Self { + self.token = Some(token.into()); + self + } + + pub fn max_retries(mut self, retries: u32) -> Self { + self.max_retries = Some(retries); + self + } + + pub fn timeout_seconds(mut self, timeout: u64) -> Self { + self.timeout_seconds = Some(timeout); + self + } + + pub fn additional_header(mut self, key: impl Into, value: impl Into) -> Self { + self.additional_headers.insert(key.into(), value.into()); + self + } + + pub fn additional_query_param( + mut self, + key: impl Into, + value: impl Into, + ) -> Self { + self.additional_query_params + .insert(key.into(), value.into()); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_has_no_values() { + let opts = RequestOptions::default(); + assert!(opts.api_key.is_none()); + assert!(opts.token.is_none()); + assert!(opts.max_retries.is_none()); + assert!(opts.timeout_seconds.is_none()); + assert!(opts.additional_headers.is_empty()); + assert!(opts.additional_query_params.is_empty()); + } + + #[test] + fn test_new_equals_default() { + let opts = RequestOptions::new(); + assert!(opts.api_key.is_none()); + assert!(opts.token.is_none()); + assert!(opts.max_retries.is_none()); + assert!(opts.timeout_seconds.is_none()); + assert!(opts.additional_headers.is_empty()); + assert!(opts.additional_query_params.is_empty()); + } + + #[test] + fn test_api_key() { + let opts = RequestOptions::new().api_key("my-key"); + assert_eq!(opts.api_key, Some("my-key".to_string())); + } + + #[test] + fn test_token() { + let opts = RequestOptions::new().token("my-token"); + assert_eq!(opts.token, Some("my-token".to_string())); + } + + #[test] + fn test_max_retries() { + let opts = RequestOptions::new().max_retries(3); + assert_eq!(opts.max_retries, Some(3)); + } + + #[test] + fn test_timeout_seconds() { + let opts = RequestOptions::new().timeout_seconds(30); + assert_eq!(opts.timeout_seconds, Some(30)); + } + + #[test] + fn test_additional_header() { + let opts = RequestOptions::new().additional_header("X-Custom", "value"); + assert_eq!( + opts.additional_headers.get("X-Custom"), + Some(&"value".to_string()) + ); + } + + #[test] + fn test_additional_headers_accumulate() { + let opts = RequestOptions::new() + .additional_header("X-First", "1") + .additional_header("X-Second", "2"); + assert_eq!(opts.additional_headers.len(), 2); + assert_eq!( + opts.additional_headers.get("X-First"), + Some(&"1".to_string()) + ); + assert_eq!( + opts.additional_headers.get("X-Second"), + Some(&"2".to_string()) + ); + } + + #[test] + fn test_additional_query_param() { + let opts = RequestOptions::new().additional_query_param("page", "1"); + assert_eq!( + opts.additional_query_params.get("page"), + Some(&"1".to_string()) + ); + } + + #[test] + fn test_additional_query_params_accumulate() { + let opts = RequestOptions::new() + .additional_query_param("page", "1") + .additional_query_param("limit", "10"); + assert_eq!(opts.additional_query_params.len(), 2); + assert_eq!( + opts.additional_query_params.get("page"), + Some(&"1".to_string()) + ); + assert_eq!( + opts.additional_query_params.get("limit"), + Some(&"10".to_string()) + ); + } + + #[test] + fn test_full_method_chaining() { + let opts = RequestOptions::new() + .api_key("key") + .token("tok") + .max_retries(5) + .timeout_seconds(60) + .additional_header("X-Foo", "bar") + .additional_query_param("q", "search"); + assert_eq!(opts.api_key, Some("key".to_string())); + assert_eq!(opts.token, Some("tok".to_string())); + assert_eq!(opts.max_retries, Some(5)); + assert_eq!(opts.timeout_seconds, Some(60)); + assert_eq!(opts.additional_headers.len(), 1); + assert_eq!(opts.additional_query_params.len(), 1); + } +} diff --git a/seed/rust-sdk/allof-inline/src/core/utils.rs b/seed/rust-sdk/allof-inline/src/core/utils.rs new file mode 100644 index 000000000000..323676f39ea2 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/core/utils.rs @@ -0,0 +1,77 @@ +/// URL building utilities +/// Safely join a base URL with a path, handling slashes properly +/// +/// # Examples +/// ``` +/// use example_api::utils::url::join_url; +/// +/// assert_eq!(join_url("https://api.example.com", "users"), "https://api.example.com/users"); +/// assert_eq!(join_url("https://api.example.com/", "users"), "https://api.example.com/users"); +/// assert_eq!(join_url("https://api.example.com", "/users"), "https://api.example.com/users"); +/// assert_eq!(join_url("https://api.example.com/", "/users"), "https://api.example.com/users"); +/// ``` +pub fn join_url(base_url: &str, path: &str) -> String { + format!( + "{}/{}", + base_url.trim_end_matches('/'), + path.trim_start_matches('/') + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_join_url_no_slashes() { + assert_eq!( + join_url("https://api.example.com", "users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_trailing_slash_on_base() { + assert_eq!( + join_url("https://api.example.com/", "users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_leading_slash_on_path() { + assert_eq!( + join_url("https://api.example.com", "/users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_both_slashes() { + assert_eq!( + join_url("https://api.example.com/", "/users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_multi_segment_path() { + assert_eq!( + join_url("https://api.example.com", "v1/users/123"), + "https://api.example.com/v1/users/123" + ); + } + + #[test] + fn test_join_url_empty_path() { + assert_eq!( + join_url("https://api.example.com", ""), + "https://api.example.com/" + ); + } + + #[test] + fn test_join_url_empty_base() { + assert_eq!(join_url("", "users"), "/users"); + } +} diff --git a/seed/rust-sdk/allof-inline/src/environment.rs b/seed/rust-sdk/allof-inline/src/environment.rs new file mode 100644 index 000000000000..9034ff803954 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/environment.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Environment { + #[serde(rename = "default")] + Default, +} +impl Environment { + pub fn url(&self) -> &'static str { + match self { + Self::Default => "https://api.example.com", + } + } +} +impl Default for Environment { + fn default() -> Self { + Self::Default + } +} diff --git a/seed/rust-sdk/allof-inline/src/error.rs b/seed/rust-sdk/allof-inline/src/error.rs new file mode 100644 index 000000000000..655d945d71f1 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/error.rs @@ -0,0 +1,54 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ApiError { + #[error("HTTP error {status}: {message}")] + Http { status: u16, message: String }, + #[error("Network error: {0}")] + Network(reqwest::Error), + #[error("Serialization error: {0}")] + Serialization(serde_json::Error), + #[error("Configuration error: {0}")] + Configuration(String), + #[error("Invalid header value")] + InvalidHeader, + #[error("Could not clone request for retry")] + RequestClone, + #[error("SSE stream terminated")] + StreamTerminated, + #[error("SSE stream timed out waiting for next event")] + StreamTimeout, + #[error("SSE parse error: {0}")] + SseParseError(String), +} + +impl ApiError { + pub fn from_response(status_code: u16, body: Option<&str>) -> Self { + match status_code { + _ => Self::Http { + status: status_code, + message: body.unwrap_or("Unknown error").to_string(), + }, + } + } +} + +/// Error returned when a required field was not set on a builder. +#[derive(Debug)] +pub struct BuildError { + field: &'static str, +} + +impl BuildError { + pub fn missing_field(field: &'static str) -> Self { + Self { field } + } +} + +impl std::fmt::Display for BuildError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "`{}` was not set but is required", self.field) + } +} + +impl std::error::Error for BuildError {} diff --git a/seed/rust-sdk/allof-inline/src/lib.rs b/seed/rust-sdk/allof-inline/src/lib.rs new file mode 100644 index 000000000000..626894135f95 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/lib.rs @@ -0,0 +1,49 @@ +//! # allOf Composition SDK +//! +//! The official Rust SDK for the allOf Composition. +//! +//! ## Getting Started +//! +//! ```rust +//! use seed_api::prelude::*; +//! +//! #[tokio::main] +//! async fn main() { +//! let config = ClientConfig { +//! ..Default::default() +//! }; +//! let client = ApiClient::new(config).expect("Failed to build client"); +//! client +//! .search_rule_types( +//! &SearchRuleTypesQueryRequest { +//! ..Default::default() +//! }, +//! None, +//! ) +//! .await; +//! } +//! ``` +//! +//! ## Modules +//! +//! - [`api`] - Core API types and models +//! - [`client`] - Client implementations +//! - [`config`] - Configuration options +//! - [`core`] - Core utilities and infrastructure +//! - [`error`] - Error types and handling +//! - [`prelude`] - Common imports for convenience + +pub mod api; +pub mod client; +pub mod config; +pub mod core; +pub mod environment; +pub mod error; +pub mod prelude; + +pub use api::*; +pub use client::*; +pub use config::*; +pub use core::*; +pub use environment::*; +pub use error::{ApiError, BuildError}; diff --git a/seed/rust-sdk/allof-inline/src/prelude.rs b/seed/rust-sdk/allof-inline/src/prelude.rs new file mode 100644 index 000000000000..5656ffc7e1b7 --- /dev/null +++ b/seed/rust-sdk/allof-inline/src/prelude.rs @@ -0,0 +1,19 @@ +//! Prelude module for convenient imports +//! +//! This module re-exports the most commonly used types and traits. +//! Import it with: `use seed_api::prelude::*;` + +// Client and configuration +pub use crate::config::ClientConfig; +pub use crate::core::{HttpClient, RequestOptions}; +pub use crate::error::{ApiError, BuildError}; + +// Main client and resource clients +pub use crate::api::*; + +// Re-export commonly used external types +pub use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, Utc}; +pub use serde::{Deserialize, Serialize}; +pub use serde_json::{json, Value}; +pub use std::collections::{HashMap, HashSet}; +pub use std::fmt; diff --git a/seed/rust-sdk/allof/.fern/metadata.json b/seed/rust-sdk/allof/.fern/metadata.json new file mode 100644 index 000000000000..479cc3770032 --- /dev/null +++ b/seed/rust-sdk/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-rust-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/rust-sdk/allof/.github/workflows/ci.yml b/seed/rust-sdk/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..4e582a9fa4e3 --- /dev/null +++ b/seed/rust-sdk/allof/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + RUSTFLAGS: "-A warnings" + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Check + run: cargo check + + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Compile + run: cargo build + + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Test + run: cargo test + + publish: + needs: [check, compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Publish + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish diff --git a/seed/rust-sdk/allof/.gitignore b/seed/rust-sdk/allof/.gitignore new file mode 100644 index 000000000000..f75d27799da1 --- /dev/null +++ b/seed/rust-sdk/allof/.gitignore @@ -0,0 +1,5 @@ +/target +**/*.rs.bk +Cargo.lock +.DS_Store +*.swp \ No newline at end of file diff --git a/seed/rust-sdk/allof/Cargo.toml b/seed/rust-sdk/allof/Cargo.toml new file mode 100644 index 000000000000..ce5837604a74 --- /dev/null +++ b/seed/rust-sdk/allof/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "seed_api" +version = "0.0.1" +edition = "2021" +description = "Rust SDK for seed_api generated by Fern" +license = "MIT" +repository = "https://github.com/fern-api/fern" +documentation = "https://docs.rs/seed_api" + +[lib] +doctest = false + +[dependencies] +bytes = "1.0" +chrono = { version = "0.4", features = ["serde"] } +futures = "0.3" +reqwest = { version = "0.12", features = ["json", "stream"], default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" +tokio = { version = "1.0", features = ["full"] } + +[dev-dependencies] +tokio-test = "0.4" + diff --git a/seed/rust-sdk/allof/README.md b/seed/rust-sdk/allof/README.md new file mode 100644 index 000000000000..73c2a4769236 --- /dev/null +++ b/seed/rust-sdk/allof/README.md @@ -0,0 +1,181 @@ +# Seed Rust Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRust) +[![crates.io shield](https://img.shields.io/crates/v/seed_api)](https://crates.io/crates/seed_api) + +The Seed Rust library provides convenient access to the Seed APIs from Rust. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Request Types](#request-types) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) +- [Contributing](#contributing) + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +seed_api = "0.0.1" +``` + +Or install via cargo: + +```sh +cargo add seed_api +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```rust +use seed_api::prelude::{*}; + +let config = ClientConfig { + base_url: Environment::Default.url().to_string(), + ..Default::default() +}; +let client = Client::new(config).expect("Failed to build client"); +``` + +## Errors + +When the API returns a non-success status code (4xx or 5xx response), an error will be returned. + +```rust +match client.create_rule(None)?.await { + Ok(response) => { + println!("Success: {:?}", response); + }, + Err(ApiError::HTTP { status, message }) => { + println!("API Error {}: {:?}", status, message); + }, + Err(e) => { + println!("Other error: {:?}", e); + } +} +``` + +## Request Types + +The SDK exports all request types as Rust structs. Simply import them from the crate to access them: + +```rust +use seed_api::prelude::{*}; + +let request = RuleCreateRequest { + ... +}; +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` method to configure this behavior. + +```rust +let response = client.create_rule( + Some(RequestOptions::new().max_retries(3)) +)?.await; +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `timeout` method to configure this behavior. + +```rust +let response = client.create_rule( + Some(RequestOptions::new().timeout_seconds(30)) +)?.await; +``` + +### Additional Headers + +You can add custom headers to requests using `RequestOptions`. + +```rust +let response = client.create_rule( + Some( + RequestOptions::new() + .additional_header("X-Custom-Header", "custom-value") + .additional_header("X-Another-Header", "another-value") + ) +)? +.await; +``` + +### Additional Query String Parameters + +You can add custom query parameters to requests using `RequestOptions`. + +```rust +let response = client.create_rule( + Some( + RequestOptions::new() + .additional_query_param("filter", "active") + .additional_query_param("sort", "desc") + ) +)? +.await; +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/rust-sdk/allof/dynamic-snippets/example0.rs b/seed/rust-sdk/allof/dynamic-snippets/example0.rs new file mode 100644 index 000000000000..6968951d8813 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example0.rs @@ -0,0 +1,18 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .search_rule_types( + &SearchRuleTypesQueryRequest { + ..Default::default() + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example1.rs b/seed/rust-sdk/allof/dynamic-snippets/example1.rs new file mode 100644 index 000000000000..cf7e959c8d85 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example1.rs @@ -0,0 +1,19 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .search_rule_types( + &SearchRuleTypesQueryRequest { + query: Some("query".to_string()), + ..Default::default() + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example2.rs b/seed/rust-sdk/allof/dynamic-snippets/example2.rs new file mode 100644 index 000000000000..db52ba2800ca --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example2.rs @@ -0,0 +1,19 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example3.rs b/seed/rust-sdk/allof/dynamic-snippets/example3.rs new file mode 100644 index 000000000000..db52ba2800ca --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example3.rs @@ -0,0 +1,19 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example4.rs b/seed/rust-sdk/allof/dynamic-snippets/example4.rs new file mode 100644 index 000000000000..5afd9b2dd1e9 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example4.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.list_users(None).await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example5.rs b/seed/rust-sdk/allof/dynamic-snippets/example5.rs new file mode 100644 index 000000000000..5afd9b2dd1e9 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example5.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.list_users(None).await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example6.rs b/seed/rust-sdk/allof/dynamic-snippets/example6.rs new file mode 100644 index 000000000000..0f9ad4c50ad6 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example6.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_entity(None).await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example7.rs b/seed/rust-sdk/allof/dynamic-snippets/example7.rs new file mode 100644 index 000000000000..0f9ad4c50ad6 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example7.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_entity(None).await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example8.rs b/seed/rust-sdk/allof/dynamic-snippets/example8.rs new file mode 100644 index 000000000000..9025e9975095 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example8.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_organization(None).await; +} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example9.rs b/seed/rust-sdk/allof/dynamic-snippets/example9.rs new file mode 100644 index 000000000000..9025e9975095 --- /dev/null +++ b/seed/rust-sdk/allof/dynamic-snippets/example9.rs @@ -0,0 +1,11 @@ +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + base_url: "https://api.fern.com".to_string(), + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_organization(None).await; +} diff --git a/seed/rust-sdk/allof/reference.md b/seed/rust-sdk/allof/reference.md new file mode 100644 index 000000000000..482a8295a3fb --- /dev/null +++ b/seed/rust-sdk/allof/reference.md @@ -0,0 +1,224 @@ +# Reference +
client.search_rule_types(query: Option<Option<String>>) -> Result<RuleTypeSearchResponse, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .search_rule_types( + &SearchRuleTypesQueryRequest { + ..Default::default() + }, + None, + ) + .await; +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `Option` + +
+
+
+
+ + +
+
+
+ +
client.create_rule(request: RuleCreateRequest) -> Result<RuleResponse, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client + .create_rule( + &RuleCreateRequest { + name: "name".to_string(), + execution_context: RuleExecutionContext::Prod, + }, + None, + ) + .await; +} +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `String` + +
+
+ +
+
+ +**execution_context:** `RuleExecutionContext` + +
+
+
+
+ + +
+
+
+ +
client.list_users() -> Result<UserSearchResponse, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.list_users(None).await; +} +``` +
+
+
+
+ + +
+
+
+ +
client.get_entity() -> Result<CombinedEntity, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_entity(None).await; +} +``` +
+
+
+
+ + +
+
+
+ +
client.get_organization() -> Result<Organization, ApiError> +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```rust +use seed_api::prelude::*; + +#[tokio::main] +async fn main() { + let config = ClientConfig { + ..Default::default() + }; + let client = ApiClient::new(config).expect("Failed to build client"); + client.get_organization(None).await; +} +``` +
+
+
+
+ + +
+
+
+ diff --git a/seed/rust-sdk/allof/rustfmt.toml b/seed/rust-sdk/allof/rustfmt.toml new file mode 100644 index 000000000000..872221fb31fe --- /dev/null +++ b/seed/rust-sdk/allof/rustfmt.toml @@ -0,0 +1,4 @@ +# Generated by Fern +edition = "2021" +max_width = 100 +use_small_heuristics = "Default" \ No newline at end of file diff --git a/seed/rust-sdk/allof/snippet.json b/seed/rust-sdk/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/rust-sdk/allof/src/api/mod.rs b/seed/rust-sdk/allof/src/api/mod.rs new file mode 100644 index 000000000000..83cb9e2c92d4 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/mod.rs @@ -0,0 +1,15 @@ +//! API client and types for the allOf Composition +//! +//! This module contains all the API definitions including request/response types +//! and client implementations for interacting with the API. +//! +//! ## Modules +//! +//! - [`resources`] - Service clients and endpoints +//! - [`types`] - Request, response, and model types + +pub mod resources; +pub mod types; + +pub use resources::ApiClient; +pub use types::*; diff --git a/seed/rust-sdk/allof/src/api/resources/mod.rs b/seed/rust-sdk/allof/src/api/resources/mod.rs new file mode 100644 index 000000000000..543ca7436f76 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/resources/mod.rs @@ -0,0 +1,82 @@ +//! Service clients and API endpoints +//! +//! This module provides the client implementations for all available services. + +use crate::api::*; +use crate::{ApiError, ClientConfig, HttpClient, RequestOptions}; +use reqwest::Method; + +pub struct ApiClient { + pub config: ClientConfig, + pub http_client: HttpClient, +} + +impl ApiClient { + pub fn new(config: ClientConfig) -> Result { + Ok(Self { + config: config.clone(), + http_client: HttpClient::new(config.clone())?, + }) + } + + pub async fn search_rule_types( + &self, + request: &SearchRuleTypesQueryRequest, + options: Option, + ) -> Result { + self.http_client + .execute_request( + Method::GET, + "rule-types", + None, + QueryBuilder::new() + .structured_query("query", request.query.clone()) + .build(), + options, + ) + .await + } + + pub async fn create_rule( + &self, + request: &RuleCreateRequest, + options: Option, + ) -> Result { + self.http_client + .execute_request( + Method::POST, + "rules", + Some(serde_json::to_value(request).map_err(ApiError::Serialization)?), + None, + options, + ) + .await + } + + pub async fn list_users( + &self, + options: Option, + ) -> Result { + self.http_client + .execute_request(Method::GET, "users", None, None, options) + .await + } + + pub async fn get_entity( + &self, + options: Option, + ) -> Result { + self.http_client + .execute_request(Method::GET, "entities", None, None, options) + .await + } + + pub async fn get_organization( + &self, + options: Option, + ) -> Result { + self.http_client + .execute_request(Method::GET, "organizations", None, None, options) + .await + } +} diff --git a/seed/rust-sdk/allof/src/api/types/audit_info.rs b/seed/rust-sdk/allof/src/api/types/audit_info.rs new file mode 100644 index 000000000000..a958df434a50 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/audit_info.rs @@ -0,0 +1,73 @@ +pub use crate::prelude::*; + +/// Common audit metadata. +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct AuditInfo { + /// The user who created this resource. + #[serde(rename = "createdBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + /// When this resource was created. + #[serde(rename = "createdDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub created_date_time: Option>, + /// The user who last modified this resource. + #[serde(rename = "modifiedBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_by: Option, + /// When this resource was last modified. + #[serde(rename = "modifiedDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub modified_date_time: Option>, +} + +impl AuditInfo { + pub fn builder() -> AuditInfoBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct AuditInfoBuilder { + created_by: Option, + created_date_time: Option>, + modified_by: Option, + modified_date_time: Option>, +} + +impl AuditInfoBuilder { + pub fn created_by(mut self, value: impl Into) -> Self { + self.created_by = Some(value.into()); + self + } + + pub fn created_date_time(mut self, value: DateTime) -> Self { + self.created_date_time = Some(value); + self + } + + pub fn modified_by(mut self, value: impl Into) -> Self { + self.modified_by = Some(value.into()); + self + } + + pub fn modified_date_time(mut self, value: DateTime) -> Self { + self.modified_date_time = Some(value); + self + } + + /// Consumes the builder and constructs a [`AuditInfo`]. + pub fn build(self) -> Result { + Ok(AuditInfo { + created_by: self.created_by, + created_date_time: self.created_date_time, + modified_by: self.modified_by, + modified_date_time: self.modified_date_time, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/base_org.rs b/seed/rust-sdk/allof/src/api/types/base_org.rs new file mode 100644 index 000000000000..1747ce8ded53 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/base_org.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrg { + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl BaseOrg { + pub fn builder() -> BaseOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgBuilder { + id: Option, + metadata: Option, +} + +impl BaseOrgBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`BaseOrg`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](BaseOrgBuilder::id) + pub fn build(self) -> Result { + Ok(BaseOrg { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/base_org_metadata.rs b/seed/rust-sdk/allof/src/api/types/base_org_metadata.rs new file mode 100644 index 000000000000..d1182fd85504 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/base_org_metadata.rs @@ -0,0 +1,48 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrgMetadata { + /// Deployment region from BaseOrg. + #[serde(default)] + pub region: String, + /// Subscription tier. + #[serde(skip_serializing_if = "Option::is_none")] + pub tier: Option, +} + +impl BaseOrgMetadata { + pub fn builder() -> BaseOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgMetadataBuilder { + region: Option, + tier: Option, +} + +impl BaseOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn tier(mut self, value: impl Into) -> Self { + self.tier = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`BaseOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](BaseOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(BaseOrgMetadata { + region: self + .region + .ok_or_else(|| BuildError::missing_field("region"))?, + tier: self.tier, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/combined_entity.rs b/seed/rust-sdk/allof/src/api/types/combined_entity.rs new file mode 100644 index 000000000000..9b776e37be14 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/combined_entity.rs @@ -0,0 +1,67 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct CombinedEntity { + pub status: CombinedEntityStatus, + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Identifiable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, +} + +impl CombinedEntity { + pub fn builder() -> CombinedEntityBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct CombinedEntityBuilder { + status: Option, + id: Option, + name: Option, + summary: Option, +} + +impl CombinedEntityBuilder { + pub fn status(mut self, value: CombinedEntityStatus) -> Self { + self.status = Some(value); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`CombinedEntity`]. + /// This method will fail if any of the following fields are not set: + /// - [`status`](CombinedEntityBuilder::status) + /// - [`id`](CombinedEntityBuilder::id) + pub fn build(self) -> Result { + Ok(CombinedEntity { + status: self + .status + .ok_or_else(|| BuildError::missing_field("status"))?, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + summary: self.summary, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/combined_entity_status.rs b/seed/rust-sdk/allof/src/api/types/combined_entity_status.rs new file mode 100644 index 000000000000..f46cb23eecb2 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/combined_entity_status.rs @@ -0,0 +1,42 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CombinedEntityStatus { + Active, + Archived, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for CombinedEntityStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Archived => serializer.serialize_str("archived"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for CombinedEntityStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "archived" => Ok(Self::Archived), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for CombinedEntityStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Archived => write!(f, "archived"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-sdk/allof/src/api/types/describable.rs b/seed/rust-sdk/allof/src/api/types/describable.rs new file mode 100644 index 000000000000..6a8a527bd844 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/describable.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Describable { + /// Display name from Describable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, +} + +impl Describable { + pub fn builder() -> DescribableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DescribableBuilder { + name: Option, + summary: Option, +} + +impl DescribableBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Describable`]. + pub fn build(self) -> Result { + Ok(Describable { + name: self.name, + summary: self.summary, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/detailed_org.rs b/seed/rust-sdk/allof/src/api/types/detailed_org.rs new file mode 100644 index 000000000000..fac068d8a2ee --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/detailed_org.rs @@ -0,0 +1,33 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrg { + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl DetailedOrg { + pub fn builder() -> DetailedOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgBuilder { + metadata: Option, +} + +impl DetailedOrgBuilder { + pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`DetailedOrg`]. + pub fn build(self) -> Result { + Ok(DetailedOrg { + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs b/seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs new file mode 100644 index 000000000000..bbbeda49c417 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs @@ -0,0 +1,48 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrgMetadata { + /// Deployment region from DetailedOrg. + #[serde(default)] + pub region: String, + /// Custom domain name. + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, +} + +impl DetailedOrgMetadata { + pub fn builder() -> DetailedOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgMetadataBuilder { + region: Option, + domain: Option, +} + +impl DetailedOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn domain(mut self, value: impl Into) -> Self { + self.domain = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](DetailedOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(DetailedOrgMetadata { + region: self + .region + .ok_or_else(|| BuildError::missing_field("region"))?, + domain: self.domain, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/identifiable.rs b/seed/rust-sdk/allof/src/api/types/identifiable.rs new file mode 100644 index 000000000000..58fb3eb70734 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/identifiable.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Identifiable { + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Identifiable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +impl Identifiable { + pub fn builder() -> IdentifiableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct IdentifiableBuilder { + id: Option, + name: Option, +} + +impl IdentifiableBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Identifiable`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](IdentifiableBuilder::id) + pub fn build(self) -> Result { + Ok(Identifiable { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/mod.rs b/seed/rust-sdk/allof/src/api/types/mod.rs new file mode 100644 index 000000000000..18089be4e67a --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/mod.rs @@ -0,0 +1,43 @@ +pub mod audit_info; +pub mod base_org; +pub mod base_org_metadata; +pub mod combined_entity; +pub mod combined_entity_status; +pub mod describable; +pub mod detailed_org; +pub mod detailed_org_metadata; +pub mod identifiable; +pub mod organization; +pub mod paginated_result; +pub mod paging_cursors; +pub mod rule_create_request; +pub mod rule_execution_context; +pub mod rule_response; +pub mod rule_response_status; +pub mod rule_type; +pub mod rule_type_search_response; +pub mod search_rule_types_query_request; +pub mod user; +pub mod user_search_response; + +pub use audit_info::AuditInfo; +pub use base_org::BaseOrg; +pub use base_org_metadata::BaseOrgMetadata; +pub use combined_entity::CombinedEntity; +pub use combined_entity_status::CombinedEntityStatus; +pub use describable::Describable; +pub use detailed_org::DetailedOrg; +pub use detailed_org_metadata::DetailedOrgMetadata; +pub use identifiable::Identifiable; +pub use organization::Organization; +pub use paginated_result::PaginatedResult; +pub use paging_cursors::PagingCursors; +pub use rule_create_request::RuleCreateRequest; +pub use rule_execution_context::RuleExecutionContext; +pub use rule_response::RuleResponse; +pub use rule_response_status::RuleResponseStatus; +pub use rule_type::RuleType; +pub use rule_type_search_response::RuleTypeSearchResponse; +pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; +pub use user::User; +pub use user_search_response::UserSearchResponse; diff --git a/seed/rust-sdk/allof/src/api/types/organization.rs b/seed/rust-sdk/allof/src/api/types/organization.rs new file mode 100644 index 000000000000..ec2ed3f22d66 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/organization.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Organization { + #[serde(default)] + pub name: String, + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl Organization { + pub fn builder() -> OrganizationBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct OrganizationBuilder { + name: Option, + id: Option, + metadata: Option, +} + +impl OrganizationBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`Organization`]. + /// This method will fail if any of the following fields are not set: + /// - [`name`](OrganizationBuilder::name) + /// - [`id`](OrganizationBuilder::id) + pub fn build(self) -> Result { + Ok(Organization { + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/paginated_result.rs b/seed/rust-sdk/allof/src/api/types/paginated_result.rs new file mode 100644 index 000000000000..1f7073c5844e --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/paginated_result.rs @@ -0,0 +1,50 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct PaginatedResult { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(default)] + pub results: Vec, +} + +impl PaginatedResult { + pub fn builder() -> PaginatedResultBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PaginatedResultBuilder { + paging: Option, + results: Option>, +} + +impl PaginatedResultBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`PaginatedResult`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](PaginatedResultBuilder::paging) + /// - [`results`](PaginatedResultBuilder::results) + pub fn build(self) -> Result { + Ok(PaginatedResult { + paging: self + .paging + .ok_or_else(|| BuildError::missing_field("paging"))?, + results: self + .results + .ok_or_else(|| BuildError::missing_field("results"))?, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/paging_cursors.rs b/seed/rust-sdk/allof/src/api/types/paging_cursors.rs new file mode 100644 index 000000000000..a3a785119b57 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/paging_cursors.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct PagingCursors { + /// Cursor for the next page of results. + #[serde(default)] + pub next: String, + /// Cursor for the previous page of results. + #[serde(skip_serializing_if = "Option::is_none")] + pub previous: Option, +} + +impl PagingCursors { + pub fn builder() -> PagingCursorsBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PagingCursorsBuilder { + next: Option, + previous: Option, +} + +impl PagingCursorsBuilder { + pub fn next(mut self, value: impl Into) -> Self { + self.next = Some(value.into()); + self + } + + pub fn previous(mut self, value: impl Into) -> Self { + self.previous = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`PagingCursors`]. + /// This method will fail if any of the following fields are not set: + /// - [`next`](PagingCursorsBuilder::next) + pub fn build(self) -> Result { + Ok(PagingCursors { + next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, + previous: self.previous, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/rule_create_request.rs b/seed/rust-sdk/allof/src/api/types/rule_create_request.rs new file mode 100644 index 000000000000..913f6f08eabd --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/rule_create_request.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleCreateRequest { + #[serde(default)] + pub name: String, + #[serde(rename = "executionContext")] + pub execution_context: RuleExecutionContext, +} + +impl RuleCreateRequest { + pub fn builder() -> RuleCreateRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleCreateRequestBuilder { + name: Option, + execution_context: Option, +} + +impl RuleCreateRequestBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleCreateRequest`]. + /// This method will fail if any of the following fields are not set: + /// - [`name`](RuleCreateRequestBuilder::name) + /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) + pub fn build(self) -> Result { + Ok(RuleCreateRequest { + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + execution_context: self + .execution_context + .ok_or_else(|| BuildError::missing_field("execution_context"))?, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/rule_execution_context.rs b/seed/rust-sdk/allof/src/api/types/rule_execution_context.rs new file mode 100644 index 000000000000..7f8fa5bd56e1 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/rule_execution_context.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +/// Execution environment for a rule. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleExecutionContext { + Prod, + Staging, + Dev, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleExecutionContext { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Prod => serializer.serialize_str("prod"), + Self::Staging => serializer.serialize_str("staging"), + Self::Dev => serializer.serialize_str("dev"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleExecutionContext { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "prod" => Ok(Self::Prod), + "staging" => Ok(Self::Staging), + "dev" => Ok(Self::Dev), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleExecutionContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Prod => write!(f, "prod"), + Self::Staging => write!(f, "staging"), + Self::Dev => write!(f, "dev"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-sdk/allof/src/api/types/rule_response.rs b/seed/rust-sdk/allof/src/api/types/rule_response.rs new file mode 100644 index 000000000000..ce3d9919a491 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/rule_response.rs @@ -0,0 +1,78 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleResponse { + #[serde(flatten)] + pub audit_info_fields: AuditInfo, + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + pub status: RuleResponseStatus, + #[serde(rename = "executionContext")] + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_context: Option, +} + +impl RuleResponse { + pub fn builder() -> RuleResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleResponseBuilder { + audit_info_fields: Option, + id: Option, + name: Option, + status: Option, + execution_context: Option, +} + +impl RuleResponseBuilder { + pub fn audit_info_fields(mut self, value: AuditInfo) -> Self { + self.audit_info_fields = Some(value); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn status(mut self, value: RuleResponseStatus) -> Self { + self.status = Some(value); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`audit_info_fields`](RuleResponseBuilder::audit_info_fields) + /// - [`id`](RuleResponseBuilder::id) + /// - [`name`](RuleResponseBuilder::name) + /// - [`status`](RuleResponseBuilder::status) + pub fn build(self) -> Result { + Ok(RuleResponse { + audit_info_fields: self + .audit_info_fields + .ok_or_else(|| BuildError::missing_field("audit_info_fields"))?, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + status: self + .status + .ok_or_else(|| BuildError::missing_field("status"))?, + execution_context: self.execution_context, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/rule_response_status.rs b/seed/rust-sdk/allof/src/api/types/rule_response_status.rs new file mode 100644 index 000000000000..5d2462ecd349 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/rule_response_status.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleResponseStatus { + Active, + Inactive, + Draft, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleResponseStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Inactive => serializer.serialize_str("inactive"), + Self::Draft => serializer.serialize_str("draft"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleResponseStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "inactive" => Ok(Self::Inactive), + "draft" => Ok(Self::Draft), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleResponseStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Inactive => write!(f, "inactive"), + Self::Draft => write!(f, "draft"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-sdk/allof/src/api/types/rule_type.rs b/seed/rust-sdk/allof/src/api/types/rule_type.rs new file mode 100644 index 000000000000..69f0317c1923 --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/rule_type.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleType { + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +impl RuleType { + pub fn builder() -> RuleTypeBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeBuilder { + id: Option, + name: Option, + description: Option, +} + +impl RuleTypeBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn description(mut self, value: impl Into) -> Self { + self.description = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`RuleType`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](RuleTypeBuilder::id) + /// - [`name`](RuleTypeBuilder::name) + pub fn build(self) -> Result { + Ok(RuleType { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + description: self.description, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs b/seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs new file mode 100644 index 000000000000..854fbd7920fc --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleTypeSearchResponse { + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, + #[serde(default)] + pub paging: PagingCursors, +} + +impl RuleTypeSearchResponse { + pub fn builder() -> RuleTypeSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeSearchResponseBuilder { + results: Option>, + paging: Option, +} + +impl RuleTypeSearchResponseBuilder { + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](RuleTypeSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(RuleTypeSearchResponse { + results: self.results, + paging: self + .paging + .ok_or_else(|| BuildError::missing_field("paging"))?, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs b/seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs new file mode 100644 index 000000000000..37158173a08c --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs @@ -0,0 +1,32 @@ +pub use crate::prelude::*; + +/// Query parameters for searchRuleTypes +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct SearchRuleTypesQueryRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, +} + +impl SearchRuleTypesQueryRequest { + pub fn builder() -> SearchRuleTypesQueryRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct SearchRuleTypesQueryRequestBuilder { + query: Option, +} + +impl SearchRuleTypesQueryRequestBuilder { + pub fn query(mut self, value: impl Into) -> Self { + self.query = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. + pub fn build(self) -> Result { + Ok(SearchRuleTypesQueryRequest { query: self.query }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/user.rs b/seed/rust-sdk/allof/src/api/types/user.rs new file mode 100644 index 000000000000..cce3febf2dbe --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/user.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct User { + #[serde(default)] + pub id: String, + #[serde(default)] + pub email: String, +} + +impl User { + pub fn builder() -> UserBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserBuilder { + id: Option, + email: Option, +} + +impl UserBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn email(mut self, value: impl Into) -> Self { + self.email = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`User`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](UserBuilder::id) + /// - [`email`](UserBuilder::email) + pub fn build(self) -> Result { + Ok(User { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + email: self + .email + .ok_or_else(|| BuildError::missing_field("email"))?, + }) + } +} diff --git a/seed/rust-sdk/allof/src/api/types/user_search_response.rs b/seed/rust-sdk/allof/src/api/types/user_search_response.rs new file mode 100644 index 000000000000..bb233525225a --- /dev/null +++ b/seed/rust-sdk/allof/src/api/types/user_search_response.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct UserSearchResponse { + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, + #[serde(default)] + pub paging: PagingCursors, +} + +impl UserSearchResponse { + pub fn builder() -> UserSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserSearchResponseBuilder { + results: Option>, + paging: Option, +} + +impl UserSearchResponseBuilder { + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + /// Consumes the builder and constructs a [`UserSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](UserSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(UserSearchResponse { + results: self.results, + paging: self + .paging + .ok_or_else(|| BuildError::missing_field("paging"))?, + }) + } +} diff --git a/seed/rust-sdk/allof/src/client.rs b/seed/rust-sdk/allof/src/client.rs new file mode 100644 index 000000000000..139cebf898d7 --- /dev/null +++ b/seed/rust-sdk/allof/src/client.rs @@ -0,0 +1,247 @@ +use crate::api::resources::ApiClient; +use crate::Environment; +use crate::{ApiError, ClientConfig}; +use std::collections::HashMap; +use std::time::Duration; +/// Builder for creating API clients with custom configuration +pub struct ApiClientBuilder { + config: ClientConfig, +} +impl Default for ApiClientBuilder { + fn default() -> Self { + Self { + config: ClientConfig::default(), + } + } +} +impl ApiClientBuilder { + /// Create a new builder with the specified base URL + pub fn new(base_url: impl Into) -> Self { + let mut config = ClientConfig::default(); + config.base_url = base_url.into(); + Self { config } + } + + /// Set the environment, updating the base URL + pub fn environment(mut self, environment: Environment) -> Self { + self.config.base_url = environment.url().to_string(); + self + } + + /// Set the API key for authentication + pub fn api_key(mut self, key: impl Into) -> Self { + self.config.api_key = Some(key.into()); + self + } + + /// Set the bearer token for authentication + pub fn token(mut self, token: impl Into) -> Self { + self.config.token = Some(token.into()); + self + } + + /// Set the username for basic authentication + pub fn username(mut self, username: impl Into) -> Self { + self.config.username = Some(username.into()); + self + } + + /// Set the password for basic authentication + pub fn password(mut self, password: impl Into) -> Self { + self.config.password = Some(password.into()); + self + } + + /// Set the OAuth client ID for client credentials authentication + pub fn client_id(mut self, client_id: impl Into) -> Self { + self.config.client_id = Some(client_id.into()); + self + } + + /// Set the OAuth client secret for client credentials authentication + pub fn client_secret(mut self, client_secret: impl Into) -> Self { + self.config.client_secret = Some(client_secret.into()); + self + } + + /// Set OAuth credentials (client_id and client_secret) for client credentials authentication + pub fn oauth_credentials( + mut self, + client_id: impl Into, + client_secret: impl Into, + ) -> Self { + self.config.client_id = Some(client_id.into()); + self.config.client_secret = Some(client_secret.into()); + self + } + + /// Set the request timeout + pub fn timeout(mut self, timeout: Duration) -> Self { + self.config.timeout = timeout; + self + } + + /// Set the maximum number of retries + pub fn max_retries(mut self, retries: u32) -> Self { + self.config.max_retries = retries; + self + } + + /// Add a custom header + pub fn custom_header(mut self, key: impl Into, value: impl Into) -> Self { + self.config.custom_headers.insert(key.into(), value.into()); + self + } + + /// Add multiple custom headers + pub fn custom_headers(mut self, headers: HashMap) -> Self { + self.config.custom_headers.extend(headers); + self + } + + /// Set the user agent + pub fn user_agent(mut self, user_agent: impl Into) -> Self { + self.config.user_agent = user_agent.into(); + self + } + + /// Build the client with validation + pub fn build(self) -> Result { + ApiClient::new(self.config) + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_environment() { + let builder = ApiClientBuilder::default().environment(Environment::default()); + assert_eq!( + builder.config.base_url, + Environment::default().url().to_string() + ); + } + + #[test] + fn test_new_sets_base_url() { + let builder = ApiClientBuilder::new("https://api.example.com"); + assert_eq!(builder.config.base_url, "https://api.example.com"); + } + + #[test] + fn test_api_key() { + let builder = ApiClientBuilder::new("https://api.example.com").api_key("my-key"); + assert_eq!(builder.config.api_key, Some("my-key".to_string())); + } + + #[test] + fn test_token() { + let builder = ApiClientBuilder::new("https://api.example.com").token("my-token"); + assert_eq!(builder.config.token, Some("my-token".to_string())); + } + + #[test] + fn test_username() { + let builder = ApiClientBuilder::new("https://api.example.com").username("user"); + assert_eq!(builder.config.username, Some("user".to_string())); + } + + #[test] + fn test_password() { + let builder = ApiClientBuilder::new("https://api.example.com").password("pass"); + assert_eq!(builder.config.password, Some("pass".to_string())); + } + + #[test] + fn test_client_id() { + let builder = ApiClientBuilder::new("https://api.example.com").client_id("cid"); + assert_eq!(builder.config.client_id, Some("cid".to_string())); + } + + #[test] + fn test_client_secret() { + let builder = ApiClientBuilder::new("https://api.example.com").client_secret("secret"); + assert_eq!(builder.config.client_secret, Some("secret".to_string())); + } + + #[test] + fn test_oauth_credentials() { + let builder = + ApiClientBuilder::new("https://api.example.com").oauth_credentials("cid", "secret"); + assert_eq!(builder.config.client_id, Some("cid".to_string())); + assert_eq!(builder.config.client_secret, Some("secret".to_string())); + } + + #[test] + fn test_timeout() { + let builder = + ApiClientBuilder::new("https://api.example.com").timeout(Duration::from_secs(120)); + assert_eq!(builder.config.timeout, Duration::from_secs(120)); + } + + #[test] + fn test_max_retries() { + let builder = ApiClientBuilder::new("https://api.example.com").max_retries(5); + assert_eq!(builder.config.max_retries, 5); + } + + #[test] + fn test_custom_header() { + let builder = + ApiClientBuilder::new("https://api.example.com").custom_header("X-Custom", "value"); + assert_eq!( + builder.config.custom_headers.get("X-Custom"), + Some(&"value".to_string()) + ); + } + + #[test] + fn test_custom_headers_multiple() { + let mut headers = HashMap::new(); + headers.insert("X-One".to_string(), "1".to_string()); + headers.insert("X-Two".to_string(), "2".to_string()); + let builder = ApiClientBuilder::new("https://api.example.com").custom_headers(headers); + assert_eq!( + builder.config.custom_headers.get("X-One"), + Some(&"1".to_string()) + ); + assert_eq!( + builder.config.custom_headers.get("X-Two"), + Some(&"2".to_string()) + ); + } + + #[test] + fn test_user_agent() { + let builder = ApiClientBuilder::new("https://api.example.com").user_agent("my-sdk/1.0"); + assert_eq!(builder.config.user_agent, "my-sdk/1.0"); + } + + #[test] + fn test_full_builder_chain() { + let builder = ApiClientBuilder::new("https://api.example.com") + .api_key("key") + .token("tok") + .username("user") + .password("pass") + .timeout(Duration::from_secs(60)) + .max_retries(3) + .custom_header("X-Foo", "bar") + .user_agent("test/1.0"); + assert_eq!(builder.config.base_url, "https://api.example.com"); + assert_eq!(builder.config.api_key, Some("key".to_string())); + assert_eq!(builder.config.token, Some("tok".to_string())); + assert_eq!(builder.config.username, Some("user".to_string())); + assert_eq!(builder.config.password, Some("pass".to_string())); + assert_eq!(builder.config.timeout, Duration::from_secs(60)); + assert_eq!(builder.config.max_retries, 3); + assert_eq!(builder.config.user_agent, "test/1.0"); + } + + #[test] + fn test_build_succeeds() { + let result = ApiClientBuilder::new("https://api.example.com").build(); + assert!(result.is_ok()); + } +} diff --git a/seed/rust-sdk/allof/src/config.rs b/seed/rust-sdk/allof/src/config.rs new file mode 100644 index 000000000000..466af37454a1 --- /dev/null +++ b/seed/rust-sdk/allof/src/config.rs @@ -0,0 +1,39 @@ +use crate::Environment; +use std::collections::HashMap; +use std::time::Duration; + +#[derive(Debug, Clone)] +pub struct ClientConfig { + pub base_url: String, + pub api_key: Option, + pub token: Option, + pub username: Option, + pub password: Option, + pub client_id: Option, + pub client_secret: Option, + pub timeout: Duration, + pub max_retries: u32, + pub custom_headers: HashMap, + pub user_agent: String, +} +impl Default for ClientConfig { + fn default() -> Self { + Self { + base_url: Environment::default().url().to_string(), + api_key: None, + token: None, + username: None, + password: None, + client_id: None, + client_secret: None, + timeout: Duration::from_secs(60), + max_retries: 3, + custom_headers: HashMap::from([ + ("X-Fern-Language".to_string(), "Rust".to_string()), + ("X-Fern-SDK-Name".to_string(), "seed_api".to_string()), + ("X-Fern-SDK-Version".to_string(), "0.0.1".to_string()), + ]), + user_agent: "Api Rust SDK".to_string(), + } + } +} diff --git a/seed/rust-sdk/allof/src/core/flexible_datetime.rs b/seed/rust-sdk/allof/src/core/flexible_datetime.rs new file mode 100644 index 000000000000..e5572d11b299 --- /dev/null +++ b/seed/rust-sdk/allof/src/core/flexible_datetime.rs @@ -0,0 +1,270 @@ +//! Flexible datetime parsing module +//! +//! This module provides serde helpers for parsing datetime strings that may or may not +//! include a timezone suffix. It accepts both RFC3339 format (with Z or +00:00 suffix) +//! and ISO 8601 format without timezone (assuming UTC). +//! +//! Supported formats: +//! - `2024-01-15T09:30:00Z` (RFC3339 with Z) +//! - `2024-01-15T09:30:00+00:00` (RFC3339 with offset) +//! - `2024-01-15T09:30:00` (ISO 8601 without timezone, assumes UTC) +//! - `2024-01-15T09:30:00.123Z` (with fractional seconds and Z) +//! - `2024-01-15T09:30:00.123` (with fractional seconds, no timezone) +//! +//! Two submodules are provided: +//! - `utc`: Parses into `DateTime`, converting all datetimes to UTC +//! - `offset`: Parses into `DateTime`, preserving original timezone + +/// Module for DateTime with flexible parsing - converts all datetimes to UTC +pub mod utc { + use chrono::{DateTime, NaiveDateTime, Utc}; + use serde::{self, Deserialize, Deserializer, Serializer}; + + /// Serialize a DateTime to RFC3339 format + pub fn serialize(date: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&date.to_rfc3339()) + } + + /// Deserialize a datetime string that may or may not include a timezone suffix. + /// If no timezone is present, UTC is assumed. All datetimes are converted to UTC. + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + parse_flexible_datetime(&s).map_err(serde::de::Error::custom) + } + + /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. + fn parse_flexible_datetime(s: &str) -> Result, String> { + // First, try parsing as RFC3339 (with timezone) + if let Ok(dt) = DateTime::parse_from_rfc3339(s) { + return Ok(dt.with_timezone(&Utc)); + } + + // Try parsing as NaiveDateTime (without timezone) and assume UTC + // Try with fractional seconds first + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { + return Ok(naive.and_utc()); + } + + // Try without fractional seconds + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { + return Ok(naive.and_utc()); + } + + Err(format!( + "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ + or ISO 8601 format (e.g., '2024-01-15T09:30:00')", + s + )) + } + + /// Module for optional DateTime fields with flexible parsing + pub mod option { + use super::*; + + pub fn serialize(date: &Option>, serializer: S) -> Result + where + S: Serializer, + { + match date { + Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let opt: Option = Option::deserialize(deserializer)?; + match opt { + Some(s) => parse_flexible_datetime(&s) + .map(Some) + .map_err(serde::de::Error::custom), + None => Ok(None), + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_parse_rfc3339_with_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_rfc3339_with_offset() { + let result = parse_flexible_datetime("2024-01-15T09:30:00+00:00"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_without_timezone() { + let result = parse_flexible_datetime("2024-01-15T09:30:00"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_with_fractional_seconds() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_with_fractional_seconds_and_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); + assert!(result.is_ok()); + } + } +} + +/// Module for DateTime with flexible parsing - preserves original timezone +pub mod offset { + use chrono::{DateTime, FixedOffset, NaiveDateTime}; + use serde::{self, Deserialize, Deserializer, Serializer}; + + /// Serialize a DateTime to RFC3339 format + pub fn serialize(date: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&date.to_rfc3339()) + } + + /// Deserialize a datetime string that may or may not include a timezone suffix. + /// If no timezone is present, UTC (+00:00) is assumed. + /// The original timezone offset is preserved when present. + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + parse_flexible_datetime(&s).map_err(serde::de::Error::custom) + } + + /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. + /// Preserves the original timezone offset when present, assumes UTC when not. + fn parse_flexible_datetime(s: &str) -> Result, String> { + // First, try parsing as RFC3339 (with timezone) - this preserves the original offset + if let Ok(dt) = DateTime::parse_from_rfc3339(s) { + return Ok(dt); + } + + // Try parsing as NaiveDateTime (without timezone) and assume UTC (+00:00) + let utc_offset = FixedOffset::east_opt(0).unwrap(); + + // Try with fractional seconds first + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { + return Ok(naive.and_local_timezone(utc_offset).unwrap()); + } + + // Try without fractional seconds + if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { + return Ok(naive.and_local_timezone(utc_offset).unwrap()); + } + + Err(format!( + "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ + or ISO 8601 format (e.g., '2024-01-15T09:30:00')", + s + )) + } + + /// Module for optional DateTime fields with flexible parsing + pub mod option { + use super::*; + + pub fn serialize( + date: &Option>, + serializer: S, + ) -> Result + where + S: Serializer, + { + match date { + Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D>( + deserializer: D, + ) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let opt: Option = Option::deserialize(deserializer)?; + match opt { + Some(s) => parse_flexible_datetime(&s) + .map(Some) + .map_err(serde::de::Error::custom), + None => Ok(None), + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_parse_rfc3339_with_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); + assert!(result.is_ok()); + let dt = result.unwrap(); + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_parse_rfc3339_with_offset() { + let result = parse_flexible_datetime("2024-01-15T09:30:00-05:00"); + assert!(result.is_ok()); + let dt = result.unwrap(); + // -05:00 = -5 * 3600 = -18000 seconds + assert_eq!(dt.offset().local_minus_utc(), -18000); + } + + #[test] + fn test_parse_without_timezone() { + let result = parse_flexible_datetime("2024-01-15T09:30:00"); + assert!(result.is_ok()); + let dt = result.unwrap(); + // Should assume UTC (+00:00) + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_parse_with_fractional_seconds() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); + assert!(result.is_ok()); + let dt = result.unwrap(); + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_parse_with_fractional_seconds_and_z() { + let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); + assert!(result.is_ok()); + let dt = result.unwrap(); + assert_eq!(dt.offset().local_minus_utc(), 0); + } + + #[test] + fn test_preserves_positive_offset() { + let result = parse_flexible_datetime("2024-01-15T09:30:00+09:00"); + assert!(result.is_ok()); + let dt = result.unwrap(); + // +09:00 = 9 * 3600 = 32400 seconds + assert_eq!(dt.offset().local_minus_utc(), 32400); + } + } +} diff --git a/seed/rust-sdk/allof/src/core/http_client.rs b/seed/rust-sdk/allof/src/core/http_client.rs new file mode 100644 index 000000000000..d61f8b46a3a1 --- /dev/null +++ b/seed/rust-sdk/allof/src/core/http_client.rs @@ -0,0 +1,569 @@ +use crate::{join_url, ApiError, ClientConfig, OAuthTokenProvider, RequestOptions}; +use futures::{Stream, StreamExt}; +use reqwest::{ + header::{HeaderName, HeaderValue}, + Client, Method, Request, Response, +}; +use serde::de::DeserializeOwned; + +use std::{ + pin::Pin, + str::FromStr, + sync::Arc, + task::{Context, Poll}, +}; + +/// A streaming byte stream for downloading files efficiently +pub struct ByteStream { + content_length: Option, + inner: Pin> + Send>>, +} + +impl ByteStream { + /// Create a new ByteStream from a Response + pub(crate) fn new(response: Response) -> Self { + let content_length = response.content_length(); + let stream = response.bytes_stream(); + + Self { + content_length, + inner: Box::pin(stream), + } + } + + /// Collect the entire stream into a `Vec` + /// + /// This consumes the stream and buffers all data into memory. + /// For large files, prefer using `try_next()` to process chunks incrementally. + /// + /// # Example + /// ```no_run + /// let stream = client.download_file().await?; + /// let bytes = stream.collect().await?; + /// ``` + pub async fn collect(mut self) -> Result, ApiError> { + let mut result = Vec::new(); + while let Some(chunk) = self.inner.next().await { + result.extend_from_slice(&chunk.map_err(ApiError::Network)?); + } + Ok(result) + } + + /// Get the next chunk from the stream + /// + /// Returns `Ok(Some(bytes))` if a chunk is available, + /// `Ok(None)` if the stream is finished, or an error. + /// + /// # Example + /// ```no_run + /// let mut stream = client.download_file().await?; + /// while let Some(chunk) = stream.try_next().await? { + /// process_chunk(&chunk); + /// } + /// ``` + pub async fn try_next(&mut self) -> Result, ApiError> { + match self.inner.next().await { + Some(Ok(bytes)) => Ok(Some(bytes)), + Some(Err(e)) => Err(ApiError::Network(e)), + None => Ok(None), + } + } + + /// Get the content length from response headers if available + pub fn content_length(&self) -> Option { + self.content_length + } +} + +impl Stream for ByteStream { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.inner.as_mut().poll_next(cx) { + Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))), + Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ApiError::Network(e)))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +/// Configuration for OAuth token fetching. +/// +/// This struct contains all the information needed to automatically fetch +/// and refresh OAuth tokens. +#[derive(Clone)] +pub struct OAuthConfig { + /// The OAuth token provider that manages token caching and refresh + pub token_provider: Arc, + /// The token endpoint path (e.g., "/token") + pub token_endpoint: String, +} + +/// Response from an OAuth token endpoint. +#[derive(Debug, Clone, serde::Deserialize)] +struct OAuthTokenResponse { + access_token: String, + #[serde(default)] + expires_in: Option, +} + +/// Internal HTTP client that handles requests with authentication and retries +#[derive(Clone)] +pub struct HttpClient { + client: Client, + config: ClientConfig, + /// Optional OAuth configuration for automatic token management + oauth_config: Option, +} + +impl HttpClient { + /// Creates a new HttpClient without OAuth support. + pub fn new(config: ClientConfig) -> Result { + Self::new_with_oauth(config, None) + } + + /// Creates a new HttpClient with optional OAuth support. + /// + /// When `oauth_config` is provided, the client will automatically fetch and refresh + /// OAuth tokens before making requests. + pub fn new_with_oauth( + config: ClientConfig, + oauth_config: Option, + ) -> Result { + let client = Client::builder() + .timeout(config.timeout) + .user_agent(&config.user_agent) + .build() + .map_err(ApiError::Network)?; + + Ok(Self { + client, + config, + oauth_config, + }) + } + + /// Returns the configured base URL. + pub fn base_url(&self) -> &str { + &self.config.base_url + } + + /// Returns a reference to the client configuration. + pub fn config(&self) -> &ClientConfig { + &self.config + } + + /// Execute a request with the given method, path, and options + pub async fn execute_request( + &self, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result + where + T: DeserializeOwned, // Generic T: DeserializeOwned means the response will be automatically deserialized into whatever type you specify: + { + let url = join_url(&self.config.base_url, path); + let mut request = self.client.request(method, &url); + + // Apply query parameters if provided + if let Some(params) = query_params { + request = request.query(¶ms); + } + + // Apply additional query parameters from options + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + // Apply body if provided + if let Some(body) = body { + request = request.json(&body); + } + + // Build the request + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + // Apply authentication and headers + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + // Execute with retries + let response = self.execute_with_retries(req, &options).await?; + self.parse_response(response).await + } + + /// Execute a request with an explicit base URL override. + /// + /// Used for multi-URL environments where different endpoints + /// resolve to different base URLs. + pub async fn execute_request_with_base_url( + &self, + base_url: &str, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result + where + T: DeserializeOwned, + { + let url = join_url(base_url, path); + let mut request = self.client.request(method, &url); + + if let Some(params) = query_params { + request = request.query(¶ms); + } + + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + if let Some(body) = body { + request = request.json(&body); + } + + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + let response = self.execute_with_retries(req, &options).await?; + self.parse_response(response).await + } + + async fn apply_auth_headers( + &self, + request: &mut Request, + options: &Option, + ) -> Result<(), ApiError> { + let headers = request.headers_mut(); + + // Apply API key (request options override config) + let api_key = options + .as_ref() + .and_then(|opts| opts.api_key.as_ref()) + .or(self.config.api_key.as_ref()); + + if let Some(key) = api_key { + let header_value = key.to_string(); + headers.insert( + "api_key", + header_value.parse().map_err(|_| ApiError::InvalidHeader)?, + ); + } + + // Apply bearer token - priority: request options > OAuth > config + let token = if let Some(opts) = options.as_ref() { + if opts.token.is_some() { + opts.token.clone() + } else { + None + } + } else { + None + }; + + let token = match token { + Some(t) => Some(t), + None => { + // Try OAuth token provider if configured + if let Some(oauth_config) = &self.oauth_config { + Some(self.get_oauth_token(oauth_config).await?) + } else { + // Fall back to static token from config + self.config.token.clone() + } + } + }; + + if let Some(token) = token { + let auth_value = format!("Bearer {}", token); + headers.insert( + "Authorization", + auth_value.parse().map_err(|_| ApiError::InvalidHeader)?, + ); + } + + Ok(()) + } + + /// Fetches an OAuth token, using the cached token if valid or fetching a new one. + async fn get_oauth_token(&self, oauth_config: &OAuthConfig) -> Result { + let token_provider = &oauth_config.token_provider; + let token_endpoint = &oauth_config.token_endpoint; + let client_id = token_provider.client_id().to_string(); + let client_secret = token_provider.client_secret().to_string(); + let base_url = self.config.base_url.clone(); + + // Use the async get_or_fetch method with a closure that fetches the token + token_provider + .get_or_fetch_async(|| async { + self.fetch_oauth_token(&base_url, token_endpoint, &client_id, &client_secret) + .await + }) + .await + } + + /// Makes an HTTP request to the OAuth token endpoint to fetch a new token. + async fn fetch_oauth_token( + &self, + base_url: &str, + token_endpoint: &str, + client_id: &str, + client_secret: &str, + ) -> Result<(String, u64), ApiError> { + let url = join_url(base_url, token_endpoint); + + // Build the token request body + let body = serde_json::json!({ + "client_id": client_id, + "client_secret": client_secret, + "grant_type": "client_credentials" + }); + + let response = self + .client + .request(Method::POST, &url) + .json(&body) + .send() + .await + .map_err(ApiError::Network)?; + + if !response.status().is_success() { + let status_code = response.status().as_u16(); + let body = response.text().await.ok(); + return Err(ApiError::from_response(status_code, body.as_deref())); + } + + // Parse the token response + let token_response: OAuthTokenResponse = + response.json().await.map_err(ApiError::Network)?; + + let expires_in = token_response.expires_in.unwrap_or(3600) as u64; + Ok((token_response.access_token, expires_in)) + } + + fn apply_custom_headers( + &self, + request: &mut Request, + options: &Option, + ) -> Result<(), ApiError> { + let headers = request.headers_mut(); + + // Apply config-level custom headers + for (key, value) in &self.config.custom_headers { + headers.insert( + HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, + HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, + ); + } + + // Apply request-level custom headers (override config) + if let Some(options) = options { + for (key, value) in &options.additional_headers { + headers.insert( + HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, + HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, + ); + } + } + + Ok(()) + } + + async fn execute_with_retries( + &self, + request: Request, + options: &Option, + ) -> Result { + let max_retries = options + .as_ref() + .and_then(|opts| opts.max_retries) + .unwrap_or(self.config.max_retries); + + let mut last_error = None; + + for attempt in 0..=max_retries { + let cloned_request = request.try_clone().ok_or(ApiError::RequestClone)?; + + match self.client.execute(cloned_request).await { + Ok(response) if response.status().is_success() => return Ok(response), + Ok(response) => { + let status_code = response.status().as_u16(); + let body = response.text().await.ok(); + return Err(ApiError::from_response(status_code, body.as_deref())); + } + Err(e) if attempt < max_retries => { + last_error = Some(e); + // Exponential backoff + let delay = std::time::Duration::from_millis(100 * 2_u64.pow(attempt)); + tokio::time::sleep(delay).await; + } + Err(e) => return Err(ApiError::Network(e)), + } + } + + Err(ApiError::Network(last_error.unwrap())) + } + + async fn parse_response(&self, response: Response) -> Result + where + T: DeserializeOwned, + { + let status = response.status().as_u16(); + let text = response.text().await.map_err(ApiError::Network)?; + + // Handle empty response bodies (e.g., 202 Accepted for deferred requests) + if text.is_empty() { + return Err(ApiError::Http { + status, + message: String::new(), + }); + } + + serde_json::from_str(&text).map_err(ApiError::Serialization) + } + + /// Execute a request and return a streaming response (for large file downloads) + /// + /// This method returns a `ByteStream` that can be used to download large files + /// efficiently without loading the entire content into memory. The stream can be + /// consumed chunk by chunk, written directly to disk, or collected into bytes. + /// + /// # Examples + /// + /// **Option 1: Collect all bytes into memory** + /// ```no_run + /// let stream = client.execute_stream_request( + /// Method::GET, + /// "/file", + /// None, + /// None, + /// None, + /// ).await?; + /// + /// let bytes = stream.collect().await?; + /// ``` + /// + /// **Option 2: Process chunks with try_next()** + /// ```no_run + /// let mut stream = client.execute_stream_request( + /// Method::GET, + /// "/large-file", + /// None, + /// None, + /// None, + /// ).await?; + /// + /// while let Some(chunk) = stream.try_next().await? { + /// process_chunk(&chunk); + /// } + /// ``` + /// + /// **Option 3: Stream with futures::Stream trait** + /// ```no_run + /// use futures::StreamExt; + /// + /// let stream = client.execute_stream_request( + /// Method::GET, + /// "/large-file", + /// None, + /// None, + /// None, + /// ).await?; + /// + /// let mut file = tokio::fs::File::create("output.mp4").await?; + /// let mut stream = std::pin::pin!(stream); + /// while let Some(chunk) = stream.next().await { + /// let chunk = chunk?; + /// tokio::io::AsyncWriteExt::write_all(&mut file, &chunk).await?; + /// } + /// ``` + pub async fn execute_stream_request( + &self, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result { + let url = join_url(&self.config.base_url, path); + let mut request = self.client.request(method, &url); + + // Apply query parameters if provided + if let Some(params) = query_params { + request = request.query(¶ms); + } + + // Apply additional query parameters from options + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + // Apply body if provided + if let Some(body) = body { + request = request.json(&body); + } + + // Build the request + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + // Apply authentication and headers + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + // Execute with retries + let response = self.execute_with_retries(req, &options).await?; + + // Return streaming response + Ok(ByteStream::new(response)) + } + + /// Execute a streaming request with an explicit base URL override. + pub async fn execute_stream_request_with_base_url( + &self, + base_url: &str, + method: Method, + path: &str, + body: Option, + query_params: Option>, + options: Option, + ) -> Result { + let url = join_url(base_url, path); + let mut request = self.client.request(method, &url); + + if let Some(params) = query_params { + request = request.query(¶ms); + } + + if let Some(opts) = &options { + if !opts.additional_query_params.is_empty() { + request = request.query(&opts.additional_query_params); + } + } + + if let Some(body) = body { + request = request.json(&body); + } + + let mut req = request.build().map_err(|e| ApiError::Network(e))?; + + self.apply_auth_headers(&mut req, &options).await?; + self.apply_custom_headers(&mut req, &options)?; + + let response = self.execute_with_retries(req, &options).await?; + + Ok(ByteStream::new(response)) + } +} diff --git a/seed/rust-sdk/allof/src/core/mod.rs b/seed/rust-sdk/allof/src/core/mod.rs new file mode 100644 index 000000000000..71a75dbb141b --- /dev/null +++ b/seed/rust-sdk/allof/src/core/mod.rs @@ -0,0 +1,14 @@ +//! Core client infrastructure + +pub mod flexible_datetime; +mod http_client; +mod oauth_token_provider; +mod query_parameter_builder; +mod request_options; +mod utils; + +pub use http_client::{ByteStream, HttpClient, OAuthConfig}; +pub use oauth_token_provider::OAuthTokenProvider; +pub use query_parameter_builder::{parse_structured_query, QueryBuilder, QueryBuilderError}; +pub use request_options::RequestOptions; +pub use utils::join_url; diff --git a/seed/rust-sdk/allof/src/core/oauth_token_provider.rs b/seed/rust-sdk/allof/src/core/oauth_token_provider.rs new file mode 100644 index 000000000000..09222bedd8e0 --- /dev/null +++ b/seed/rust-sdk/allof/src/core/oauth_token_provider.rs @@ -0,0 +1,363 @@ +use std::future::Future; +use std::sync::Mutex; +use std::time::{Duration, Instant}; +use tokio::sync::Mutex as AsyncMutex; + +/// Buffer time in seconds subtracted from token expiration to ensure +/// we refresh the token before it actually expires. +const EXPIRATION_BUFFER_SECONDS: u64 = 120; // 2 minutes + +/// Default expiry time in seconds used when the OAuth response doesn't include an expires_in value. +const DEFAULT_EXPIRY_SECONDS: u64 = 3600; // 1 hour fallback + +/// Manages OAuth access tokens, including caching and automatic refresh. +/// +/// This provider implements thread-safe token management with automatic expiration +/// handling. It uses a double-checked locking pattern to minimize lock contention +/// while ensuring only one thread fetches a new token at a time. +/// +/// # Example +/// +/// ```rust,ignore +/// use crate::OAuthTokenProvider; +/// +/// let provider = OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); +/// +/// // Get or fetch a token (sync) +/// let token = provider.get_or_fetch(|| { +/// // Your token fetching logic here +/// // Returns (access_token, expires_in_seconds) +/// Ok(("token".to_string(), Some(3600))) +/// })?; +/// +/// // Get or fetch a token (async) +/// let token = provider.get_or_fetch_async(|| async { +/// // Your async token fetching logic here +/// Ok(("token".to_string(), Some(3600))) +/// }).await?; +/// ``` +pub struct OAuthTokenProvider { + client_id: String, + client_secret: String, + inner: Mutex, + /// Separate mutex to ensure only one thread fetches a new token at a time (sync) + fetch_lock: Mutex<()>, + /// Async mutex for async token fetching + async_fetch_lock: AsyncMutex<()>, +} + +struct OAuthTokenProviderInner { + access_token: Option, + expires_at: Option, +} + +impl OAuthTokenProvider { + /// Creates a new OAuthTokenProvider with the given credentials. + pub fn new(client_id: String, client_secret: String) -> Self { + Self { + client_id, + client_secret, + inner: Mutex::new(OAuthTokenProviderInner { + access_token: None, + expires_at: None, + }), + fetch_lock: Mutex::new(()), + async_fetch_lock: AsyncMutex::new(()), + } + } + + /// Returns the client ID. + pub fn client_id(&self) -> &str { + &self.client_id + } + + /// Returns the client secret. + pub fn client_secret(&self) -> &str { + &self.client_secret + } + + /// Sets the cached access token and its expiration time. + /// + /// The `expires_in` parameter is the number of seconds until the token expires. + /// A buffer is applied to refresh before actual expiration. + pub fn set_token(&self, access_token: String, expires_in: u64) { + let mut inner = self.inner.lock().unwrap(); + inner.access_token = Some(access_token); + + if expires_in > 0 { + // Apply buffer to refresh before actual expiration + let effective_expires_in = expires_in.saturating_sub(EXPIRATION_BUFFER_SECONDS); + inner.expires_at = Some(Instant::now() + Duration::from_secs(effective_expires_in)); + } else { + // No expiration info, token won't auto-refresh based on time + inner.expires_at = None; + } + } + + /// Returns the cached access token if it's still valid. + /// + /// Returns `None` if the token is expired or not set. + pub fn get_token(&self) -> Option { + let inner = self.inner.lock().unwrap(); + + if let Some(ref token) = inner.access_token { + // Check if token is still valid + if let Some(expires_at) = inner.expires_at { + if Instant::now() < expires_at { + return Some(token.clone()); + } + } else { + // No expiration set, token is always valid + return Some(token.clone()); + } + } + + None + } + + /// Returns a valid token, fetching a new one if necessary (synchronous version). + /// + /// The `fetch_func` is called at most once even if multiple threads call `get_or_fetch` + /// concurrently when the token is expired. It should return `(access_token, expires_in_seconds)`. + /// + /// # Arguments + /// + /// * `fetch_func` - A function that fetches a new token. Returns `Result<(String, u64), E>` + /// where the tuple contains (access_token, expires_in_seconds). + /// + /// # Example + /// + /// ```rust,ignore + /// let token = provider.get_or_fetch(|| { + /// // Call your OAuth endpoint here (sync) + /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret())?; + /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) + /// })?; + /// ``` + pub fn get_or_fetch(&self, fetch_func: F) -> Result + where + F: FnOnce() -> Result<(String, u64), E>, + { + // Fast path: check if we have a valid token + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Slow path: acquire fetch lock to ensure only one thread fetches + let _fetch_guard = self.fetch_lock.lock().unwrap(); + + // Double-check after acquiring lock (another thread may have fetched) + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Fetch new token + let (access_token, expires_in) = fetch_func()?; + + // Use default expiry if not provided + let effective_expires_in = if expires_in > 0 { + expires_in + } else { + DEFAULT_EXPIRY_SECONDS + }; + + self.set_token(access_token.clone(), effective_expires_in); + Ok(access_token) + } + + /// Returns a valid token, fetching a new one if necessary (async version). + /// + /// This is the async version of `get_or_fetch` for use with async token fetching. + /// The `fetch_func` is called at most once even if multiple tasks call `get_or_fetch_async` + /// concurrently when the token is expired. + /// + /// # Arguments + /// + /// * `fetch_func` - An async function that fetches a new token. Returns `Result<(String, u64), E>` + /// where the tuple contains (access_token, expires_in_seconds). + /// + /// # Example + /// + /// ```rust,ignore + /// let token = provider.get_or_fetch_async(|| async { + /// // Call your OAuth endpoint here (async) + /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret()).await?; + /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) + /// }).await?; + /// ``` + pub async fn get_or_fetch_async(&self, fetch_func: F) -> Result + where + F: FnOnce() -> Fut, + Fut: Future>, + { + // Fast path: check if we have a valid token + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Slow path: acquire async fetch lock to ensure only one task fetches + let _fetch_guard = self.async_fetch_lock.lock().await; + + // Double-check after acquiring lock (another task may have fetched) + if let Some(token) = self.get_token() { + return Ok(token); + } + + // Fetch new token + let (access_token, expires_in) = fetch_func().await?; + + // Use default expiry if not provided + let effective_expires_in = if expires_in > 0 { + expires_in + } else { + DEFAULT_EXPIRY_SECONDS + }; + + self.set_token(access_token.clone(), effective_expires_in); + Ok(access_token) + } + + /// Returns `true` if the token needs to be refreshed. + /// + /// This is useful for proactively refreshing tokens before they expire. + pub fn needs_refresh(&self) -> bool { + let inner = self.inner.lock().unwrap(); + + if inner.access_token.is_none() { + return true; + } + + if let Some(expires_at) = inner.expires_at { + if Instant::now() >= expires_at { + return true; + } + } + + false + } + + /// Clears the cached token. + /// + /// This can be used to force a token refresh on the next request. + pub fn reset(&self) { + let mut inner = self.inner.lock().unwrap(); + inner.access_token = None; + inner.expires_at = None; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + use std::thread; + + #[test] + fn test_new_provider() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + assert_eq!(provider.client_id(), "client_id"); + assert_eq!(provider.client_secret(), "client_secret"); + assert!(provider.get_token().is_none()); + assert!(provider.needs_refresh()); + } + + #[test] + fn test_set_and_get_token() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + provider.set_token("test_token".to_string(), 3600); + + let token = provider.get_token(); + assert!(token.is_some()); + assert_eq!(token.unwrap(), "test_token"); + assert!(!provider.needs_refresh()); + } + + #[test] + fn test_expired_token() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + // Set token with 0 expiry (will be expired immediately due to buffer) + provider.set_token("test_token".to_string(), 1); + + // Token should be expired (1 second - 120 second buffer = expired) + assert!(provider.get_token().is_none()); + assert!(provider.needs_refresh()); + } + + #[test] + fn test_get_or_fetch() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + let result: Result = + provider.get_or_fetch(|| Ok(("fetched_token".to_string(), 3600))); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "fetched_token"); + + // Second call should return cached token + let result2: Result = provider.get_or_fetch(|| { + panic!("Should not be called - token is cached"); + }); + + assert!(result2.is_ok()); + assert_eq!(result2.unwrap(), "fetched_token"); + } + + #[test] + fn test_reset() { + let provider = + OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); + + provider.set_token("test_token".to_string(), 3600); + assert!(provider.get_token().is_some()); + + provider.reset(); + assert!(provider.get_token().is_none()); + assert!(provider.needs_refresh()); + } + + #[test] + fn test_concurrent_access() { + let provider = Arc::new(OAuthTokenProvider::new( + "client_id".to_string(), + "client_secret".to_string(), + )); + let fetch_count = Arc::new(AtomicUsize::new(0)); + + let mut handles = vec![]; + + for _ in 0..10 { + let provider_clone = Arc::clone(&provider); + let fetch_count_clone = Arc::clone(&fetch_count); + + let handle = thread::spawn(move || { + let result: Result = provider_clone.get_or_fetch(|| { + fetch_count_clone.fetch_add(1, Ordering::SeqCst); + // Simulate some work + thread::sleep(Duration::from_millis(10)); + Ok(("concurrent_token".to_string(), 3600)) + }); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "concurrent_token"); + }); + + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + // Due to double-checked locking, fetch should only be called once + // (or at most a few times if threads race before the first fetch completes) + let count = fetch_count.load(Ordering::SeqCst); + assert!(count >= 1 && count <= 3, "Fetch was called {} times", count); + } +} diff --git a/seed/rust-sdk/allof/src/core/pagination.rs b/seed/rust-sdk/allof/src/core/pagination.rs new file mode 100644 index 000000000000..6b41f78a4858 --- /dev/null +++ b/seed/rust-sdk/allof/src/core/pagination.rs @@ -0,0 +1,541 @@ +use std::collections::VecDeque; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use futures::Stream; +use serde_json::Value; + +use crate::{ApiError, HttpClient}; + +/// Result of a pagination request +#[derive(Debug)] +pub struct PaginationResult { + pub items: Vec, + pub next_cursor: Option, + pub has_next_page: bool, +} + +/// Async paginator that implements Stream for iterating over paginated results +pub struct AsyncPaginator { + http_client: Arc, + page_loader: Box< + dyn Fn( + Arc, + Option, + ) + -> Pin, ApiError>> + Send>> + + Send + + Sync, + >, + current_page: VecDeque, + current_cursor: Option, + has_next_page: bool, + loading_next: + Option, ApiError>> + Send>>>, +} + +impl AsyncPaginator { + pub fn new( + http_client: Arc, + page_loader: F, + initial_cursor: Option, + ) -> Result + where + F: Fn(Arc, Option) -> Fut + Send + Sync + 'static, + Fut: Future, ApiError>> + Send + 'static, + { + Ok(Self { + http_client, + page_loader: Box::new(move |client, cursor| Box::pin(page_loader(client, cursor))), + current_page: VecDeque::new(), + current_cursor: initial_cursor, + has_next_page: true, // Assume true initially, will be updated after first request + loading_next: None, + }) + } + + /// Check if there are more pages available + pub fn has_next_page(&self) -> bool { + !self.current_page.is_empty() || self.has_next_page + } + + /// Load the next page explicitly + pub async fn next_page(&mut self) -> Result, ApiError> { + if !self.has_next_page { + return Ok(Vec::new()); + } + + let result = + (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()).await?; + + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + + Ok(result.items) + } +} + +impl Stream for AsyncPaginator +where + T: Unpin, +{ + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // If we have items in the current page, return the next one + if let Some(item) = self.current_page.pop_front() { + return Poll::Ready(Some(Ok(item))); + } + + // If we're already loading the next page, poll that future + if let Some(ref mut loading_future) = self.loading_next { + match loading_future.as_mut().poll(cx) { + Poll::Ready(Ok(result)) => { + self.current_page.extend(result.items); + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + self.loading_next = None; + + // Try to get the next item from the newly loaded page + if let Some(item) = self.current_page.pop_front() { + return Poll::Ready(Some(Ok(item))); + } else if !self.has_next_page { + return Poll::Ready(None); + } + // Fall through to start loading next page + } + Poll::Ready(Err(e)) => { + self.loading_next = None; + return Poll::Ready(Some(Err(e))); + } + Poll::Pending => return Poll::Pending, + } + } + + // If we have no more pages to load, we're done + if !self.has_next_page { + return Poll::Ready(None); + } + + // Start loading the next page + let future = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()); + self.loading_next = Some(future); + + // Poll the future immediately + if let Some(ref mut loading_future) = self.loading_next { + match loading_future.as_mut().poll(cx) { + Poll::Ready(Ok(result)) => { + self.current_page.extend(result.items); + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + self.loading_next = None; + + if let Some(item) = self.current_page.pop_front() { + Poll::Ready(Some(Ok(item))) + } else if !self.has_next_page { + Poll::Ready(None) + } else { + // This shouldn't happen, but just in case + cx.waker().wake_by_ref(); + Poll::Pending + } + } + Poll::Ready(Err(e)) => { + self.loading_next = None; + Poll::Ready(Some(Err(e))) + } + Poll::Pending => Poll::Pending, + } + } else { + Poll::Pending + } + } +} + +/// Synchronous paginator for blocking iteration +pub struct SyncPaginator { + http_client: Arc, + page_loader: Box< + dyn Fn(Arc, Option) -> Result, ApiError> + + Send + + Sync, + >, + current_page: VecDeque, + current_cursor: Option, + has_next_page: bool, +} + +impl SyncPaginator { + pub fn new( + http_client: Arc, + page_loader: F, + initial_cursor: Option, + ) -> Result + where + F: Fn(Arc, Option) -> Result, ApiError> + + Send + + Sync + + 'static, + { + Ok(Self { + http_client, + page_loader: Box::new(page_loader), + current_page: VecDeque::new(), + current_cursor: initial_cursor, + has_next_page: true, // Assume true initially + }) + } + + /// Check if there are more pages available + pub fn has_next_page(&self) -> bool { + !self.current_page.is_empty() || self.has_next_page + } + + /// Load the next page explicitly + pub fn next_page(&mut self) -> Result, ApiError> { + if !self.has_next_page { + return Ok(Vec::new()); + } + + let result = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone())?; + + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + + Ok(result.items) + } + + /// Get all remaining items by loading all pages + pub fn collect_all(&mut self) -> Result, ApiError> { + let mut all_items = Vec::new(); + + // Add items from current page + while let Some(item) = self.current_page.pop_front() { + all_items.push(item); + } + + // Load all remaining pages + while self.has_next_page { + let page_items = self.next_page()?; + all_items.extend(page_items); + } + + Ok(all_items) + } +} + +impl Iterator for SyncPaginator { + type Item = Result; + + fn next(&mut self) -> Option { + // If we have items in the current page, return the next one + if let Some(item) = self.current_page.pop_front() { + return Some(Ok(item)); + } + + // If we have no more pages to load, we're done + if !self.has_next_page { + return None; + } + + // Load the next page + match (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()) { + Ok(result) => { + self.current_page.extend(result.items); + self.current_cursor = result.next_cursor; + self.has_next_page = result.has_next_page; + + // Return the first item from the newly loaded page + self.current_page.pop_front().map(Ok) + } + Err(e) => Some(Err(e)), + } + } +} + +/// Trait for types that can provide pagination metadata +pub trait Paginated { + /// Extract the items from this page + fn items(&self) -> &[T]; + + /// Get the cursor for the next page, if any + fn next_cursor(&self) -> Option<&str>; + + /// Check if there's a next page available + fn has_next_page(&self) -> bool; +} + +/// Trait for types that can provide offset-based pagination metadata +pub trait OffsetPaginated { + /// Extract the items from this page + fn items(&self) -> &[T]; + + /// Check if there's a next page available + fn has_next_page(&self) -> bool; + + /// Get the current page size (for calculating next offset) + fn page_size(&self) -> usize { + self.items().len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ClientConfig; + + fn make_http_client() -> Arc { + Arc::new(HttpClient::new(ClientConfig::default()).expect("Failed to create test HttpClient")) + } + + // =========================== + // SyncPaginator tests + // =========================== + + #[test] + fn test_sync_paginator_has_next_page_initially() { + let client = make_http_client(); + let paginator = SyncPaginator::::new(client, |_client, _cursor| { + Ok(PaginationResult { + items: vec![], + next_cursor: None, + has_next_page: false, + }) + }, None).unwrap(); + assert!(paginator.has_next_page()); + } + + #[test] + fn test_sync_paginator_single_page() { + let client = make_http_client(); + let mut paginator = SyncPaginator::new(client, |_client, _cursor| { + Ok(PaginationResult { + items: vec!["a".to_string(), "b".to_string()], + next_cursor: None, + has_next_page: false, + }) + }, None).unwrap(); + + let page = paginator.next_page().unwrap(); + assert_eq!(page, vec!["a".to_string(), "b".to_string()]); + assert!(!paginator.has_next_page()); + } + + #[test] + fn test_sync_paginator_exhausted_returns_empty() { + let client = make_http_client(); + let mut paginator = SyncPaginator::new(client, |_client, _cursor| { + Ok(PaginationResult { + items: vec!["a".to_string()], + next_cursor: None, + has_next_page: false, + }) + }, None).unwrap(); + + let _ = paginator.next_page().unwrap(); + let empty = paginator.next_page().unwrap(); + assert!(empty.is_empty()); + } + + #[test] + fn test_sync_paginator_multiple_pages() { + let client = make_http_client(); + let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let count = call_count.clone(); + + let mut paginator = SyncPaginator::new(client, move |_client, cursor| { + let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + match call { + 0 => { + assert!(cursor.is_none()); + Ok(PaginationResult { + items: vec![1, 2], + next_cursor: Some("page2".to_string()), + has_next_page: true, + }) + } + 1 => { + assert_eq!(cursor, Some("page2".to_string())); + Ok(PaginationResult { + items: vec![3, 4], + next_cursor: None, + has_next_page: false, + }) + } + _ => panic!("Unexpected call"), + } + }, None).unwrap(); + + let page1 = paginator.next_page().unwrap(); + assert_eq!(page1, vec![1, 2]); + assert!(paginator.has_next_page()); + + let page2 = paginator.next_page().unwrap(); + assert_eq!(page2, vec![3, 4]); + assert!(!paginator.has_next_page()); + } + + #[test] + fn test_sync_paginator_collect_all() { + let client = make_http_client(); + let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let count = call_count.clone(); + + let mut paginator = SyncPaginator::new(client, move |_client, _cursor| { + let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + match call { + 0 => Ok(PaginationResult { + items: vec![1, 2], + next_cursor: Some("next".to_string()), + has_next_page: true, + }), + 1 => Ok(PaginationResult { + items: vec![3], + next_cursor: None, + has_next_page: false, + }), + _ => panic!("Unexpected call"), + } + }, None).unwrap(); + + let all = paginator.collect_all().unwrap(); + assert_eq!(all, vec![1, 2, 3]); + } + + #[test] + fn test_sync_paginator_iterator() { + let client = make_http_client(); + let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let count = call_count.clone(); + + let paginator = SyncPaginator::new(client, move |_client, _cursor| { + let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + match call { + 0 => Ok(PaginationResult { + items: vec![10, 20], + next_cursor: Some("p2".to_string()), + has_next_page: true, + }), + 1 => Ok(PaginationResult { + items: vec![30], + next_cursor: None, + has_next_page: false, + }), + _ => panic!("Unexpected call"), + } + }, None).unwrap(); + + let items: Vec = paginator.map(|r| r.unwrap()).collect(); + assert_eq!(items, vec![10, 20, 30]); + } + + #[test] + fn test_sync_paginator_error_propagation() { + let client = make_http_client(); + let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { + Err(ApiError::Serialization("test error".to_string())) + }, None).unwrap(); + + let result = paginator.next_page(); + assert!(result.is_err()); + } + + #[test] + fn test_sync_paginator_iterator_error() { + let client = make_http_client(); + let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { + Err(ApiError::Serialization("test error".to_string())) + }, None).unwrap(); + + let item = paginator.next(); + assert!(item.is_some()); + assert!(item.unwrap().is_err()); + } + + #[test] + fn test_sync_paginator_with_initial_cursor() { + let client = make_http_client(); + let mut paginator = SyncPaginator::new(client, |_client, cursor| { + assert_eq!(cursor, Some("start_here".to_string())); + Ok(PaginationResult { + items: vec!["item".to_string()], + next_cursor: None, + has_next_page: false, + }) + }, Some("start_here".to_string())).unwrap(); + + let page = paginator.next_page().unwrap(); + assert_eq!(page, vec!["item".to_string()]); + } + + // =========================== + // PaginationResult tests + // =========================== + + #[test] + fn test_pagination_result_fields() { + let result = PaginationResult { + items: vec![1, 2, 3], + next_cursor: Some("abc".to_string()), + has_next_page: true, + }; + assert_eq!(result.items.len(), 3); + assert_eq!(result.next_cursor, Some("abc".to_string())); + assert!(result.has_next_page); + } + + // =========================== + // Trait tests + // =========================== + + struct MockPage { + data: Vec, + cursor: Option, + has_more: bool, + } + + impl Paginated for MockPage { + fn items(&self) -> &[String] { + &self.data + } + fn next_cursor(&self) -> Option<&str> { + self.cursor.as_deref() + } + fn has_next_page(&self) -> bool { + self.has_more + } + } + + impl OffsetPaginated for MockPage { + fn items(&self) -> &[String] { + &self.data + } + fn has_next_page(&self) -> bool { + self.has_more + } + } + + #[test] + fn test_paginated_trait() { + let page = MockPage { + data: vec!["a".to_string(), "b".to_string()], + cursor: Some("next".to_string()), + has_more: true, + }; + assert_eq!(Paginated::items(&page).len(), 2); + assert_eq!(page.next_cursor(), Some("next")); + assert!(Paginated::has_next_page(&page)); + } + + #[test] + fn test_offset_paginated_default_page_size() { + let page = MockPage { + data: vec!["a".to_string(), "b".to_string(), "c".to_string()], + cursor: None, + has_more: false, + }; + assert_eq!(OffsetPaginated::page_size(&page), 3); + } +} diff --git a/seed/rust-sdk/allof/src/core/query_parameter_builder.rs b/seed/rust-sdk/allof/src/core/query_parameter_builder.rs new file mode 100644 index 000000000000..6f1a6976ec2b --- /dev/null +++ b/seed/rust-sdk/allof/src/core/query_parameter_builder.rs @@ -0,0 +1,576 @@ +use chrono::{DateTime, TimeZone}; +use serde::Serialize; + +/// Modern query builder with type-safe method chaining +/// Provides a clean, Swift-like API for building HTTP query parameters +#[derive(Debug, Default)] +pub struct QueryBuilder { + params: Vec<(String, String)>, +} + +impl QueryBuilder { + /// Create a new query parameter builder + pub fn new() -> Self { + Self::default() + } + + /// Add a string parameter (accept both required/optional) + pub fn string(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v)); + } + self + } + + /// Add multiple string parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn string_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v)); + } + } + self + } + + /// Add an integer parameter (accept both required/optional) + pub fn int(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + self + } + + /// Add multiple integer parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn int_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + } + self + } + + /// Add a float parameter + pub fn float(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + self + } + + /// Add multiple float parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn float_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + } + self + } + + /// Add a boolean parameter + pub fn bool(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + self + } + + /// Add multiple boolean parameters with the same key (for allow-multiple query params) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn bool_array(mut self, key: &str, values: I) -> Self + where + I: IntoIterator, + T: Into>, + { + for value in values { + if let Some(v) = value.into() { + self.params.push((key.to_string(), v.to_string())); + } + } + self + } + + /// Add a datetime parameter (any DateTime timezone) + pub fn datetime( + mut self, + key: &str, + value: impl Into>>, + ) -> Self + where + Tz::Offset: std::fmt::Display, + { + if let Some(v) = value.into() { + self.params.push(( + key.to_string(), + v.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), + )); + } + self + } + + /// Add a date parameter (converts NaiveDate to DateTime) + pub fn date(mut self, key: &str, value: impl Into>) -> Self { + if let Some(v) = value.into() { + // Convert NaiveDate to DateTime at start of day + let datetime = v.and_hms_opt(0, 0, 0).unwrap().and_utc(); + self.params.push(( + key.to_string(), + datetime.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), + )); + } + self + } + + /// Add any serializable parameter (for enums and complex types) + pub fn serialize(mut self, key: &str, value: Option) -> Self { + if let Some(v) = value { + // For enums that implement Display, use the Display implementation + // to avoid JSON quotes in query parameters + if let Ok(serialized) = serde_json::to_string(&v) { + // Remove JSON quotes if the value is a simple string + let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { + serialized.trim_matches('"').to_string() + } else { + serialized + }; + self.params.push((key.to_string(), cleaned)); + } + } + self + } + + /// Add multiple serializable parameters with the same key (for allow-multiple query params with enums) + /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter + pub fn serialize_array( + mut self, + key: &str, + values: impl IntoIterator, + ) -> Self { + for value in values { + if let Ok(serialized) = serde_json::to_string(&value) { + // Skip null values (from Option::None) + if serialized == "null" { + continue; + } + // Remove JSON quotes if the value is a simple string + let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { + serialized.trim_matches('"').to_string() + } else { + serialized + }; + self.params.push((key.to_string(), cleaned)); + } + } + self + } + + /// Parse and add a structured query string + /// Handles complex query patterns like: + /// - "key:value" patterns + /// - "key:value1,value2" (comma-separated values) + /// - Quoted values: "key:\"value with spaces\"" + /// - Space-separated terms (treated as AND logic) + pub fn structured_query(mut self, key: &str, value: impl Into>) -> Self { + if let Some(query_str) = value.into() { + if let Ok(parsed_params) = parse_structured_query(&query_str) { + self.params.extend(parsed_params); + } else { + // Fall back to simple query parameter if parsing fails + self.params.push((key.to_string(), query_str)); + } + } + self + } + + /// Build the final query parameters + pub fn build(self) -> Option> { + if self.params.is_empty() { + None + } else { + Some(self.params) + } + } +} + +/// Errors that can occur during structured query parsing +#[derive(Debug)] +pub enum QueryBuilderError { + InvalidQuerySyntax(String), +} + +impl std::fmt::Display for QueryBuilderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + QueryBuilderError::InvalidQuerySyntax(msg) => { + write!(f, "Invalid query syntax: {}", msg) + } + } + } +} + +impl std::error::Error for QueryBuilderError {} + +/// Parse structured query strings like "key:value key2:value1,value2" +/// Used for complex filtering patterns in APIs like Foxglove +/// +/// Supported patterns: +/// - Simple: "status:active" +/// - Multiple values: "type:sensor,camera" +/// - Quoted values: "location:\"New York\"" +/// - Complex: "status:active type:sensor location:\"San Francisco\"" +pub fn parse_structured_query(query: &str) -> Result, QueryBuilderError> { + let mut params = Vec::new(); + let terms = tokenize_query(query); + + for term in terms { + if let Some((key, values)) = term.split_once(':') { + // Handle comma-separated values + for value in values.split(',') { + let clean_value = value.trim_matches('"'); // Remove quotes + params.push((key.to_string(), clean_value.to_string())); + } + } else { + // For terms without colons, return error to be explicit about expected format + return Err(QueryBuilderError::InvalidQuerySyntax(format!( + "Cannot parse term '{}' - expected 'key:value' format for structured queries", + term + ))); + } + } + + Ok(params) +} + +/// Tokenize a query string, properly handling quoted strings +fn tokenize_query(input: &str) -> Vec { + let mut tokens = Vec::new(); + let mut current_token = String::new(); + let mut in_quotes = false; + let mut chars = input.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '"' => { + // Toggle quote state and include the quote in the token + in_quotes = !in_quotes; + current_token.push(c); + } + ' ' if !in_quotes => { + // Space outside quotes - end current token + if !current_token.is_empty() { + tokens.push(current_token.trim().to_string()); + current_token.clear(); + } + } + _ => { + // Any other character (including spaces inside quotes) + current_token.push(c); + } + } + } + + // Add the last token if there is one + if !current_token.is_empty() { + tokens.push(current_token.trim().to_string()); + } + + tokens +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::{NaiveDate, TimeZone, Utc}; + + // =========================== + // QueryBuilder tests + // =========================== + + #[test] + fn test_empty_builder_returns_none() { + let result = QueryBuilder::new().build(); + assert!(result.is_none()); + } + + #[test] + fn test_string_param_some() { + let result = QueryBuilder::new() + .string("name", Some("alice".to_string())) + .build(); + assert_eq!( + result, + Some(vec![("name".to_string(), "alice".to_string())]) + ); + } + + #[test] + fn test_string_param_none_skipped() { + let result = QueryBuilder::new().string("name", None::).build(); + assert!(result.is_none()); + } + + #[test] + fn test_int_param() { + let result = QueryBuilder::new().int("page", Some(42i64)).build(); + assert_eq!(result, Some(vec![("page".to_string(), "42".to_string())])); + } + + #[test] + fn test_int_param_none_skipped() { + let result = QueryBuilder::new().int("page", None::).build(); + assert!(result.is_none()); + } + + #[test] + fn test_float_param() { + let result = QueryBuilder::new().float("score", Some(3.14f64)).build(); + assert_eq!( + result, + Some(vec![("score".to_string(), "3.14".to_string())]) + ); + } + + #[test] + fn test_bool_param() { + let result = QueryBuilder::new().bool("active", Some(true)).build(); + assert_eq!( + result, + Some(vec![("active".to_string(), "true".to_string())]) + ); + } + + #[test] + fn test_datetime_param_formats_rfc3339() { + let dt = Utc.with_ymd_and_hms(2024, 1, 15, 9, 30, 0).unwrap(); + let result = QueryBuilder::new().datetime("since", Some(dt)).build(); + assert_eq!( + result, + Some(vec![( + "since".to_string(), + "2024-01-15T09:30:00Z".to_string() + )]) + ); + } + + #[test] + fn test_date_param_converts_to_midnight_utc() { + let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(); + let result = QueryBuilder::new().date("on", Some(date)).build(); + assert_eq!( + result, + Some(vec![("on".to_string(), "2024-01-15T00:00:00Z".to_string())]) + ); + } + + #[test] + fn test_string_array_multiple_entries() { + let result = QueryBuilder::new() + .string_array( + "tag", + vec!["a".to_string(), "b".to_string(), "c".to_string()], + ) + .build(); + assert_eq!( + result, + Some(vec![ + ("tag".to_string(), "a".to_string()), + ("tag".to_string(), "b".to_string()), + ("tag".to_string(), "c".to_string()), + ]) + ); + } + + #[test] + fn test_int_array() { + let result = QueryBuilder::new() + .int_array("ids", vec![1i64, 2, 3]) + .build(); + assert_eq!( + result, + Some(vec![ + ("ids".to_string(), "1".to_string()), + ("ids".to_string(), "2".to_string()), + ("ids".to_string(), "3".to_string()), + ]) + ); + } + + #[test] + fn test_float_array() { + let result = QueryBuilder::new() + .float_array("scores", vec![1.1f64, 2.2]) + .build(); + assert_eq!( + result, + Some(vec![ + ("scores".to_string(), "1.1".to_string()), + ("scores".to_string(), "2.2".to_string()), + ]) + ); + } + + #[test] + fn test_bool_array() { + let result = QueryBuilder::new() + .bool_array("flags", vec![true, false]) + .build(); + assert_eq!( + result, + Some(vec![ + ("flags".to_string(), "true".to_string()), + ("flags".to_string(), "false".to_string()), + ]) + ); + } + + #[test] + fn test_serialize_strips_json_quotes() { + let result = QueryBuilder::new() + .serialize("status", Some("active")) + .build(); + assert_eq!( + result, + Some(vec![("status".to_string(), "active".to_string())]) + ); + } + + #[test] + fn test_serialize_none_skipped() { + let result = QueryBuilder::new() + .serialize::("status", None) + .build(); + assert!(result.is_none()); + } + + #[test] + fn test_serialize_numeric_no_quotes() { + let result = QueryBuilder::new().serialize("count", Some(42)).build(); + assert_eq!(result, Some(vec![("count".to_string(), "42".to_string())])); + } + + #[test] + fn test_serialize_array_skips_null() { + let values: Vec> = vec![Some("a"), None, Some("b")]; + let result = QueryBuilder::new().serialize_array("items", values).build(); + assert_eq!( + result, + Some(vec![ + ("items".to_string(), "a".to_string()), + ("items".to_string(), "b".to_string()), + ]) + ); + } + + #[test] + fn test_method_chaining() { + let result = QueryBuilder::new() + .string("name", Some("alice".to_string())) + .int("page", Some(1i64)) + .bool("active", Some(true)) + .build(); + assert_eq!( + result, + Some(vec![ + ("name".to_string(), "alice".to_string()), + ("page".to_string(), "1".to_string()), + ("active".to_string(), "true".to_string()), + ]) + ); + } + + // =========================== + // parse_structured_query tests + // =========================== + + #[test] + fn test_parse_simple_key_value() { + let result = parse_structured_query("status:active").unwrap(); + assert_eq!(result, vec![("status".to_string(), "active".to_string())]); + } + + #[test] + fn test_parse_comma_separated_values() { + let result = parse_structured_query("type:sensor,camera").unwrap(); + assert_eq!( + result, + vec![ + ("type".to_string(), "sensor".to_string()), + ("type".to_string(), "camera".to_string()), + ] + ); + } + + #[test] + fn test_parse_multiple_terms() { + let result = parse_structured_query("status:active type:sensor").unwrap(); + assert_eq!( + result, + vec![ + ("status".to_string(), "active".to_string()), + ("type".to_string(), "sensor".to_string()), + ] + ); + } + + #[test] + fn test_parse_quoted_value() { + let result = parse_structured_query("location:\"New York\"").unwrap(); + assert_eq!( + result, + vec![("location".to_string(), "New York".to_string())] + ); + } + + #[test] + fn test_parse_bare_word_returns_error() { + let result = parse_structured_query("bareword"); + assert!(result.is_err()); + } + + #[test] + fn test_structured_query_builder_fallback() { + // When parsing fails, structured_query falls back to simple param + let result = QueryBuilder::new() + .structured_query("q", Some("bareword".to_string())) + .build(); + assert_eq!( + result, + Some(vec![("q".to_string(), "bareword".to_string())]) + ); + } + + #[test] + fn test_structured_query_builder_parses() { + let result = QueryBuilder::new() + .structured_query("q", Some("status:active".to_string())) + .build(); + assert_eq!( + result, + Some(vec![("status".to_string(), "active".to_string())]) + ); + } + + #[test] + fn test_structured_query_none_skipped() { + let result = QueryBuilder::new() + .structured_query("q", None::) + .build(); + assert!(result.is_none()); + } +} diff --git a/seed/rust-sdk/allof/src/core/request_options.rs b/seed/rust-sdk/allof/src/core/request_options.rs new file mode 100644 index 000000000000..80508c9dd6c3 --- /dev/null +++ b/seed/rust-sdk/allof/src/core/request_options.rs @@ -0,0 +1,176 @@ +use std::collections::HashMap; +/// Options for customizing individual requests +#[derive(Debug, Clone, Default)] +pub struct RequestOptions { + /// API key for authentication (overrides client-level API key) + pub api_key: Option, + /// Bearer token for authentication (overrides client-level token) + pub token: Option, + /// Maximum number of retry attempts for failed requests + pub max_retries: Option, + /// Request timeout in seconds (overrides client-level timeout) + pub timeout_seconds: Option, + /// Additional headers to include in the request + pub additional_headers: HashMap, + /// Additional query parameters to include in the request + pub additional_query_params: HashMap, +} + +impl RequestOptions { + pub fn new() -> Self { + Self::default() + } + + pub fn api_key(mut self, key: impl Into) -> Self { + self.api_key = Some(key.into()); + self + } + + pub fn token(mut self, token: impl Into) -> Self { + self.token = Some(token.into()); + self + } + + pub fn max_retries(mut self, retries: u32) -> Self { + self.max_retries = Some(retries); + self + } + + pub fn timeout_seconds(mut self, timeout: u64) -> Self { + self.timeout_seconds = Some(timeout); + self + } + + pub fn additional_header(mut self, key: impl Into, value: impl Into) -> Self { + self.additional_headers.insert(key.into(), value.into()); + self + } + + pub fn additional_query_param( + mut self, + key: impl Into, + value: impl Into, + ) -> Self { + self.additional_query_params + .insert(key.into(), value.into()); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_has_no_values() { + let opts = RequestOptions::default(); + assert!(opts.api_key.is_none()); + assert!(opts.token.is_none()); + assert!(opts.max_retries.is_none()); + assert!(opts.timeout_seconds.is_none()); + assert!(opts.additional_headers.is_empty()); + assert!(opts.additional_query_params.is_empty()); + } + + #[test] + fn test_new_equals_default() { + let opts = RequestOptions::new(); + assert!(opts.api_key.is_none()); + assert!(opts.token.is_none()); + assert!(opts.max_retries.is_none()); + assert!(opts.timeout_seconds.is_none()); + assert!(opts.additional_headers.is_empty()); + assert!(opts.additional_query_params.is_empty()); + } + + #[test] + fn test_api_key() { + let opts = RequestOptions::new().api_key("my-key"); + assert_eq!(opts.api_key, Some("my-key".to_string())); + } + + #[test] + fn test_token() { + let opts = RequestOptions::new().token("my-token"); + assert_eq!(opts.token, Some("my-token".to_string())); + } + + #[test] + fn test_max_retries() { + let opts = RequestOptions::new().max_retries(3); + assert_eq!(opts.max_retries, Some(3)); + } + + #[test] + fn test_timeout_seconds() { + let opts = RequestOptions::new().timeout_seconds(30); + assert_eq!(opts.timeout_seconds, Some(30)); + } + + #[test] + fn test_additional_header() { + let opts = RequestOptions::new().additional_header("X-Custom", "value"); + assert_eq!( + opts.additional_headers.get("X-Custom"), + Some(&"value".to_string()) + ); + } + + #[test] + fn test_additional_headers_accumulate() { + let opts = RequestOptions::new() + .additional_header("X-First", "1") + .additional_header("X-Second", "2"); + assert_eq!(opts.additional_headers.len(), 2); + assert_eq!( + opts.additional_headers.get("X-First"), + Some(&"1".to_string()) + ); + assert_eq!( + opts.additional_headers.get("X-Second"), + Some(&"2".to_string()) + ); + } + + #[test] + fn test_additional_query_param() { + let opts = RequestOptions::new().additional_query_param("page", "1"); + assert_eq!( + opts.additional_query_params.get("page"), + Some(&"1".to_string()) + ); + } + + #[test] + fn test_additional_query_params_accumulate() { + let opts = RequestOptions::new() + .additional_query_param("page", "1") + .additional_query_param("limit", "10"); + assert_eq!(opts.additional_query_params.len(), 2); + assert_eq!( + opts.additional_query_params.get("page"), + Some(&"1".to_string()) + ); + assert_eq!( + opts.additional_query_params.get("limit"), + Some(&"10".to_string()) + ); + } + + #[test] + fn test_full_method_chaining() { + let opts = RequestOptions::new() + .api_key("key") + .token("tok") + .max_retries(5) + .timeout_seconds(60) + .additional_header("X-Foo", "bar") + .additional_query_param("q", "search"); + assert_eq!(opts.api_key, Some("key".to_string())); + assert_eq!(opts.token, Some("tok".to_string())); + assert_eq!(opts.max_retries, Some(5)); + assert_eq!(opts.timeout_seconds, Some(60)); + assert_eq!(opts.additional_headers.len(), 1); + assert_eq!(opts.additional_query_params.len(), 1); + } +} diff --git a/seed/rust-sdk/allof/src/core/utils.rs b/seed/rust-sdk/allof/src/core/utils.rs new file mode 100644 index 000000000000..323676f39ea2 --- /dev/null +++ b/seed/rust-sdk/allof/src/core/utils.rs @@ -0,0 +1,77 @@ +/// URL building utilities +/// Safely join a base URL with a path, handling slashes properly +/// +/// # Examples +/// ``` +/// use example_api::utils::url::join_url; +/// +/// assert_eq!(join_url("https://api.example.com", "users"), "https://api.example.com/users"); +/// assert_eq!(join_url("https://api.example.com/", "users"), "https://api.example.com/users"); +/// assert_eq!(join_url("https://api.example.com", "/users"), "https://api.example.com/users"); +/// assert_eq!(join_url("https://api.example.com/", "/users"), "https://api.example.com/users"); +/// ``` +pub fn join_url(base_url: &str, path: &str) -> String { + format!( + "{}/{}", + base_url.trim_end_matches('/'), + path.trim_start_matches('/') + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_join_url_no_slashes() { + assert_eq!( + join_url("https://api.example.com", "users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_trailing_slash_on_base() { + assert_eq!( + join_url("https://api.example.com/", "users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_leading_slash_on_path() { + assert_eq!( + join_url("https://api.example.com", "/users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_both_slashes() { + assert_eq!( + join_url("https://api.example.com/", "/users"), + "https://api.example.com/users" + ); + } + + #[test] + fn test_join_url_multi_segment_path() { + assert_eq!( + join_url("https://api.example.com", "v1/users/123"), + "https://api.example.com/v1/users/123" + ); + } + + #[test] + fn test_join_url_empty_path() { + assert_eq!( + join_url("https://api.example.com", ""), + "https://api.example.com/" + ); + } + + #[test] + fn test_join_url_empty_base() { + assert_eq!(join_url("", "users"), "/users"); + } +} diff --git a/seed/rust-sdk/allof/src/environment.rs b/seed/rust-sdk/allof/src/environment.rs new file mode 100644 index 000000000000..9034ff803954 --- /dev/null +++ b/seed/rust-sdk/allof/src/environment.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Environment { + #[serde(rename = "default")] + Default, +} +impl Environment { + pub fn url(&self) -> &'static str { + match self { + Self::Default => "https://api.example.com", + } + } +} +impl Default for Environment { + fn default() -> Self { + Self::Default + } +} diff --git a/seed/rust-sdk/allof/src/error.rs b/seed/rust-sdk/allof/src/error.rs new file mode 100644 index 000000000000..655d945d71f1 --- /dev/null +++ b/seed/rust-sdk/allof/src/error.rs @@ -0,0 +1,54 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ApiError { + #[error("HTTP error {status}: {message}")] + Http { status: u16, message: String }, + #[error("Network error: {0}")] + Network(reqwest::Error), + #[error("Serialization error: {0}")] + Serialization(serde_json::Error), + #[error("Configuration error: {0}")] + Configuration(String), + #[error("Invalid header value")] + InvalidHeader, + #[error("Could not clone request for retry")] + RequestClone, + #[error("SSE stream terminated")] + StreamTerminated, + #[error("SSE stream timed out waiting for next event")] + StreamTimeout, + #[error("SSE parse error: {0}")] + SseParseError(String), +} + +impl ApiError { + pub fn from_response(status_code: u16, body: Option<&str>) -> Self { + match status_code { + _ => Self::Http { + status: status_code, + message: body.unwrap_or("Unknown error").to_string(), + }, + } + } +} + +/// Error returned when a required field was not set on a builder. +#[derive(Debug)] +pub struct BuildError { + field: &'static str, +} + +impl BuildError { + pub fn missing_field(field: &'static str) -> Self { + Self { field } + } +} + +impl std::fmt::Display for BuildError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "`{}` was not set but is required", self.field) + } +} + +impl std::error::Error for BuildError {} diff --git a/seed/rust-sdk/allof/src/lib.rs b/seed/rust-sdk/allof/src/lib.rs new file mode 100644 index 000000000000..626894135f95 --- /dev/null +++ b/seed/rust-sdk/allof/src/lib.rs @@ -0,0 +1,49 @@ +//! # allOf Composition SDK +//! +//! The official Rust SDK for the allOf Composition. +//! +//! ## Getting Started +//! +//! ```rust +//! use seed_api::prelude::*; +//! +//! #[tokio::main] +//! async fn main() { +//! let config = ClientConfig { +//! ..Default::default() +//! }; +//! let client = ApiClient::new(config).expect("Failed to build client"); +//! client +//! .search_rule_types( +//! &SearchRuleTypesQueryRequest { +//! ..Default::default() +//! }, +//! None, +//! ) +//! .await; +//! } +//! ``` +//! +//! ## Modules +//! +//! - [`api`] - Core API types and models +//! - [`client`] - Client implementations +//! - [`config`] - Configuration options +//! - [`core`] - Core utilities and infrastructure +//! - [`error`] - Error types and handling +//! - [`prelude`] - Common imports for convenience + +pub mod api; +pub mod client; +pub mod config; +pub mod core; +pub mod environment; +pub mod error; +pub mod prelude; + +pub use api::*; +pub use client::*; +pub use config::*; +pub use core::*; +pub use environment::*; +pub use error::{ApiError, BuildError}; diff --git a/seed/rust-sdk/allof/src/prelude.rs b/seed/rust-sdk/allof/src/prelude.rs new file mode 100644 index 000000000000..5656ffc7e1b7 --- /dev/null +++ b/seed/rust-sdk/allof/src/prelude.rs @@ -0,0 +1,19 @@ +//! Prelude module for convenient imports +//! +//! This module re-exports the most commonly used types and traits. +//! Import it with: `use seed_api::prelude::*;` + +// Client and configuration +pub use crate::config::ClientConfig; +pub use crate::core::{HttpClient, RequestOptions}; +pub use crate::error::{ApiError, BuildError}; + +// Main client and resource clients +pub use crate::api::*; + +// Re-export commonly used external types +pub use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, Utc}; +pub use serde::{Deserialize, Serialize}; +pub use serde_json::{json, Value}; +pub use std::collections::{HashMap, HashSet}; +pub use std::fmt; diff --git a/seed/swift-sdk/allof-inline/.fern/metadata.json b/seed/swift-sdk/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..c78c560bfd6a --- /dev/null +++ b/seed/swift-sdk/allof-inline/.fern/metadata.json @@ -0,0 +1,8 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-swift-sdk", + "generatorVersion": "latest", + "generatorConfig": {}, + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Package.swift b/seed/swift-sdk/allof-inline/Package.swift new file mode 100644 index 000000000000..a66695fa5685 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "Api", + platforms: [ + .iOS(.v15), + .macOS(.v12), + .tvOS(.v15), + .watchOS(.v8) + ], + products: [ + .library( + name: "Api", + targets: ["Api"] + ) + ], + dependencies: [], + targets: [ + .target( + name: "Api", + path: "Sources" + ), + .testTarget( + name: "ApiTests", + dependencies: ["Api"], + path: "Tests" + ) + ] +) diff --git a/seed/swift-sdk/allof-inline/README.md b/seed/swift-sdk/allof-inline/README.md new file mode 100644 index 000000000000..65c624269c6a --- /dev/null +++ b/seed/swift-sdk/allof-inline/README.md @@ -0,0 +1,180 @@ +# Seed Swift Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FSwift) +![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-orange.svg) + +The Seed Swift library provides convenient access to the Seed APIs from Swift. + +## Table of Contents + +- [Requirements](#requirements) +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Request Types](#request-types) +- [Advanced](#advanced) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) + - [Timeouts](#timeouts) + - [Custom Networking Client](#custom-networking-client) +- [Contributing](#contributing) + +## Requirements + +This SDK requires: +- Swift 5.7+ +- iOS 15+ +- macOS 12+ +- tvOS 15+ +- watchOS 8+ + +## Installation + +With Swift Package Manager (SPM), add the following to the top-level `dependencies` array within your `Package.swift` file: + +```swift +dependencies: [ + .package(url: "https://github.com/allof-inline/fern", from: "0.0.1"), +] +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```swift +import Api + +let client = ApiClient( + ..., + environment: .default +) +``` + +## Errors + +The SDK throws a single error enum for all failures. Client-side issues encoding/decoding failures and network errors use dedicated cases, while non-success HTTP responses are wrapped in an `HTTPError` that exposes the status code, a simple classification and an optional decoded message. + +```swift +import Api + +let client = ApiClient(...) + +do { + let response = try await client.createRule(...) + // Handle successful response +} catch let error as ApiError { + switch error { + case .httpError(let httpError): + print("Status code:", httpError.statusCode) + print("Kind:", httpError.kind) + print("Message:", httpError.body?.message ?? httpError.localizedDescription) + case .encodingError(let underlying): + print("Encoding error:", underlying) + case .networkError(let underlying): + print("Network error:", underlying) + default: + print("Other client error:", error) + } +} catch { + print("Unexpected error:", error) +} +``` + +## Request Types + +The SDK exports all request types as Swift structs. Simply import the SDK module to access them: + +```swift +import Api + +let request = Requests.RuleCreateRequest( + ... +) +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `additionalHeaders` request option. + +```swift +try await client.createRule(..., requestOptions: .init( + additionalHeaders: [ + "X-Custom-Header": "custom value" + ] +)) +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `additionalQueryParameters` request option. + +```swift +try await client.createRule(..., requestOptions: .init( + additionalQueryParameters: [ + "custom_query_param_key": "custom_query_param_value" + ] +)) +``` + +### Timeouts + +The SDK defaults to a 60-second timeout. Use the `timeout` option to configure this behavior. + +```swift +try await client.createRule(..., requestOptions: .init( + timeout: 30 +)) +``` + +### Custom Networking Client + +The SDK allows you to customize the underlying `URLSession` used for HTTP requests. Use the `urlSession` option to provide your own configured `URLSession` instance. + +```swift +import Foundation +import Api + +let client = ApiClient( + ..., + urlSession: // Provide your implementation here +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/swift-sdk/allof-inline/Snippets/Example0.swift b/seed/swift-sdk/allof-inline/Snippets/Example0.swift new file mode 100644 index 000000000000..6a8a354329f0 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example0.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.searchRuleTypes() +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example1.swift b/seed/swift-sdk/allof-inline/Snippets/Example1.swift new file mode 100644 index 000000000000..165f0fa4c377 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example1.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.searchRuleTypes(query: "query") +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example2.swift b/seed/swift-sdk/allof-inline/Snippets/Example2.swift new file mode 100644 index 000000000000..77e7ea37ecaf --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example2.swift @@ -0,0 +1,13 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example3.swift b/seed/swift-sdk/allof-inline/Snippets/Example3.swift new file mode 100644 index 000000000000..77e7ea37ecaf --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example3.swift @@ -0,0 +1,13 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example4.swift b/seed/swift-sdk/allof-inline/Snippets/Example4.swift new file mode 100644 index 000000000000..25d9aafe2c0e --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example4.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.listUsers() +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example5.swift b/seed/swift-sdk/allof-inline/Snippets/Example5.swift new file mode 100644 index 000000000000..25d9aafe2c0e --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example5.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.listUsers() +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example6.swift b/seed/swift-sdk/allof-inline/Snippets/Example6.swift new file mode 100644 index 000000000000..cd12bd038474 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example6.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getEntity() +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example7.swift b/seed/swift-sdk/allof-inline/Snippets/Example7.swift new file mode 100644 index 000000000000..cd12bd038474 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example7.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getEntity() +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example8.swift b/seed/swift-sdk/allof-inline/Snippets/Example8.swift new file mode 100644 index 000000000000..935ba9bb5c06 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example8.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getOrganization() +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example9.swift b/seed/swift-sdk/allof-inline/Snippets/Example9.swift new file mode 100644 index 000000000000..935ba9bb5c06 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Snippets/Example9.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getOrganization() +} + +try await main() diff --git a/seed/swift-sdk/allof-inline/Sources/ApiClient.swift b/seed/swift-sdk/allof-inline/Sources/ApiClient.swift new file mode 100644 index 000000000000..3bb0c30a67c5 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/ApiClient.swift @@ -0,0 +1,104 @@ +import Foundation + +/// Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. +public final class ApiClient: Sendable { + private let httpClient: HTTPClient + + /// Initialize the client with the specified configuration. + /// + /// - Parameter baseURL: The base URL to use for requests from the client. If not provided, the default base URL will be used. + /// - Parameter headers: Additional headers to send with each request. + /// - Parameter timeout: Request timeout in seconds. Defaults to 60 seconds. Ignored if a custom `urlSession` is provided. + /// - Parameter maxRetries: Maximum number of retries for failed requests. Defaults to 2. + /// - Parameter urlSession: Custom `URLSession` to use for requests. If not provided, a default session will be created with the specified timeout. + public convenience init( + baseURL: String = ApiEnvironment.default.rawValue, + headers: [String: String]? = nil, + timeout: Int? = nil, + maxRetries: Int? = nil, + urlSession: Networking.URLSession? = nil + ) { + self.init( + baseURL: baseURL, + headerAuth: nil, + bearerAuth: nil, + basicAuth: nil, + headers: headers, + timeout: timeout, + maxRetries: maxRetries, + urlSession: urlSession + ) + } + + init( + baseURL: String, + headerAuth: ClientConfig.HeaderAuth? = nil, + bearerAuth: ClientConfig.BearerAuth? = nil, + basicAuth: ClientConfig.BasicAuth? = nil, + headers: [String: String]? = nil, + timeout: Int? = nil, + maxRetries: Int? = nil, + urlSession: Networking.URLSession? = nil + ) { + let config = ClientConfig( + baseURL: baseURL, + headerAuth: headerAuth, + bearerAuth: bearerAuth, + basicAuth: basicAuth, + headers: headers, + timeout: timeout, + maxRetries: maxRetries, + urlSession: urlSession + ) + self.httpClient = HTTPClient(config: config) + } + + public func searchRuleTypes(query: String? = nil, requestOptions: RequestOptions? = nil) async throws -> RuleTypeSearchResponse { + return try await httpClient.performRequest( + method: .get, + path: "/rule-types", + queryParams: [ + "query": query.map { .string($0) } + ], + requestOptions: requestOptions, + responseType: RuleTypeSearchResponse.self + ) + } + + public func createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions? = nil) async throws -> RuleResponse { + return try await httpClient.performRequest( + method: .post, + path: "/rules", + body: request, + requestOptions: requestOptions, + responseType: RuleResponse.self + ) + } + + public func listUsers(requestOptions: RequestOptions? = nil) async throws -> UserSearchResponse { + return try await httpClient.performRequest( + method: .get, + path: "/users", + requestOptions: requestOptions, + responseType: UserSearchResponse.self + ) + } + + public func getEntity(requestOptions: RequestOptions? = nil) async throws -> CombinedEntity { + return try await httpClient.performRequest( + method: .get, + path: "/entities", + requestOptions: requestOptions, + responseType: CombinedEntity.self + ) + } + + public func getOrganization(requestOptions: RequestOptions? = nil) async throws -> Organization { + return try await httpClient.performRequest( + method: .get, + path: "/organizations", + requestOptions: requestOptions, + responseType: Organization.self + ) + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift b/seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift new file mode 100644 index 000000000000..817df36e4a46 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift @@ -0,0 +1,5 @@ +import Foundation + +public enum ApiEnvironment: String, CaseIterable { + case `default` = "https://api.example.com" +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/ApiError.swift b/seed/swift-sdk/allof-inline/Sources/ApiError.swift new file mode 100644 index 000000000000..37bf4a118e61 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/ApiError.swift @@ -0,0 +1,57 @@ +import Foundation + +/// High-level error type thrown by generated Swift SDKs. +/// +/// Request / client-side failures are represented as dedicated cases and +/// HTTP response failures are wrapped in an `HTTPError` that classifies the status code. +public enum ApiError: Swift.Error { + // MARK: - Client / transport errors + + /// The request URL could not be constructed. + case invalidURL(Swift.String) + + /// The request body could not be encoded. + case encodingError(Swift.Error) + + /// The response body could not be decoded. + case decodingError(Swift.Error) + + /// The SDK received a response it could not interpret as a valid HTTP response. + case invalidResponse + + /// An underlying networking error occurred (e.g., connection reset). + case networkError(Swift.Error) + + /// The request timed out. + case timeout(Swift.Error?) + + // MARK: - HTTP response errors + + /// An error HTTP response was returned by the server. + case httpError(HTTPError) + + // MARK: - Description + + public var errorDescription: Swift.String? { + switch self { + case .invalidURL(let url): + return "Invalid URL '\(url)'" + case .encodingError(let error): + return "Failed to encode request: \(error.localizedDescription)" + case .decodingError(let error): + return "Failed to decode response: \(error.localizedDescription)" + case .invalidResponse: + return "Invalid response received" + case .networkError(let error): + return "Network error: \(error.localizedDescription)" + case .timeout(let underlying): + if let underlying { + return "Request timed out: \(underlying.localizedDescription)" + } else { + return "Request timed out" + } + case .httpError(let httpError): + return httpError.localizedDescription + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift b/seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift new file mode 100644 index 000000000000..ddf326ffbfe6 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift @@ -0,0 +1,15 @@ +import Foundation + +// MARK: - Data + String Extensions +extension Foundation.Data { + /// Safely appends a UTF-8 encoded string to the data + /// + /// - Parameter string: The string to append + mutating func appendUTF8String(_ string: Swift.String) { + guard let data = string.data(using: .utf8) else { + assertionFailure("Failed to encode string to UTF-8: \(string)") + return + } + self.append(data) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift new file mode 100644 index 000000000000..445e45cbdb96 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift @@ -0,0 +1,24 @@ +import Foundation + +enum HTTP { + enum Method: Swift.String, Swift.CaseIterable { + case get = "GET" + case post = "POST" + case put = "PUT" + case delete = "DELETE" + case patch = "PATCH" + case head = "HEAD" + } + + enum ContentType: Swift.String, Swift.CaseIterable { + case applicationJson = "application/json" + case applicationOctetStream = "application/octet-stream" + case multipartFormData = "multipart/form-data" + } + + enum RequestBody { + case jsonEncodable(any Swift.Encodable) + case data(Foundation.Data) + case multipartFormData(MultipartFormData) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift new file mode 100644 index 000000000000..18e8927122ef --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift @@ -0,0 +1,397 @@ +import Foundation + +final class HTTPClient: Swift.Sendable { + private let clientConfig: ClientConfig + private let jsonEncoder = Serde.jsonEncoder + private let jsonDecoder = Serde.jsonDecoder + + private static let initialRetryDelay: Foundation.TimeInterval = 1.0 // 1 second + private static let maxRetryDelay: Foundation.TimeInterval = 60.0 // 60 seconds + private static let jitterFactor: Swift.Double = 0.2 // 20% jitter + + init(config: ClientConfig) { + self.clientConfig = config + } + + /// Performs a request with no response. + func performRequest( + method: HTTP.Method, + path: Swift.String, + contentType requestContentType: HTTP.ContentType = .applicationJson, + headers requestHeaders: [Swift.String: Swift.String?] = [:], + queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], + body requestBody: Any? = nil, + requestOptions: RequestOptions? = nil + ) async throws { + _ = try await performRequest( + method: method, + path: path, + contentType: requestContentType, + headers: requestHeaders, + queryParams: requestQueryParams, + body: requestBody, + requestOptions: requestOptions, + responseType: Foundation.Data.self + ) + } + + /// Performs a request with the specified response type. + func performRequest( + method: HTTP.Method, + path: Swift.String, + contentType requestContentType: HTTP.ContentType = .applicationJson, + headers requestHeaders: [Swift.String: Swift.String?] = [:], + queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], + body requestBody: Any? = nil, + requestOptions: RequestOptions? = nil, + responseType: T.Type + ) async throws -> T { + let requestBody: HTTP.RequestBody? = requestBody.map { body in + if let multipartData = body as? MultipartFormData { + return .multipartFormData(multipartData) + } else if let data = body as? Foundation.Data { + return .data(data) + } else if let encodable = body as? any Swift.Encodable { + return .jsonEncodable(encodable) + } else { + preconditionFailure("Unsupported body type: \(type(of: body))") + } + } + + let request = try await buildRequest( + method: method, + path: path, + requestContentType: requestContentType, + requestHeaders: requestHeaders, + requestQueryParams: requestQueryParams, + requestBody: requestBody, + requestOptions: requestOptions + ) + + let (data, _) = try await executeRequestWithURLSession( + request, + requestOptions: requestOptions + ) + + if responseType == Foundation.Data.self { + if let data = data as? T { + return data + } else { + throw ApiError.invalidResponse + } + } + + if responseType == Swift.String.self { + if let string = Swift.String(data: data, encoding: .utf8) as? T { + return string + } else { + throw ApiError.invalidResponse + } + } + + do { + return try jsonDecoder.decode(responseType, from: data) + } catch { + throw ApiError.decodingError(error) + } + } + + private func buildRequest( + method: HTTP.Method, + path: Swift.String, + requestContentType: HTTP.ContentType, + requestHeaders: [Swift.String: Swift.String?], + requestQueryParams: [Swift.String: QueryParameter?], + requestBody: HTTP.RequestBody? = nil, + requestOptions: RequestOptions? = nil + ) async throws -> Networking.URLRequest { + // Init with URL + let url = buildRequestURL( + path: path, requestQueryParams: requestQueryParams, requestOptions: requestOptions + ) + var request = Networking.URLRequest(url: url) + + // Set timeout + if let timeout = requestOptions?.timeout { + request.timeoutInterval = Foundation.TimeInterval(timeout) + } + + // Set method + request.httpMethod = method.rawValue + + // Set headers + let headers = try await buildRequestHeaders( + requestBody: requestBody, + requestContentType: requestContentType, + requestHeaders: requestHeaders, + requestOptions: requestOptions + ) + for (key, value) in headers { + request.setValue(value, forHTTPHeaderField: key) + } + + // Set body + if let requestBody = requestBody { + request.httpBody = buildRequestBody( + requestBody: requestBody, + requestOptions: requestOptions + ) + } + + return request + } + + private func buildRequestURL( + path: Swift.String, + requestQueryParams: [Swift.String: QueryParameter?], + requestOptions: RequestOptions? = nil + ) -> URL { + let endpointURL = "\(clientConfig.baseURL)\(path)" + guard var components = Foundation.URLComponents(string: endpointURL) else { + preconditionFailure( + "Invalid URL '\(endpointURL)' - this indicates an unexpected error in the SDK." + ) + } + if !requestQueryParams.isEmpty { + let baseItems: [Foundation.URLQueryItem] = requestQueryParams.compactMap { key, value in + guard let unwrapped = value else { return nil } + let stringValue = unwrapped.toString() + guard !stringValue.isEmpty else { return nil } + return Foundation.URLQueryItem(name: key, value: stringValue) + } + if !baseItems.isEmpty { + components.queryItems = baseItems + } + } + if let additionalQueryParams = requestOptions?.additionalQueryParameters { + let extraItems = additionalQueryParams.compactMap { key, value in + value.isEmpty ? nil : Foundation.URLQueryItem(name: key, value: value) + } + if components.queryItems == nil { + components.queryItems = extraItems + } else { + components.queryItems?.append(contentsOf: extraItems) + } + } + guard let url = components.url else { + preconditionFailure( + "Failed to construct URL from components - this indicates an unexpected error in the SDK." + ) + } + return url + } + + private func buildRequestHeaders( + requestBody: HTTP.RequestBody?, + requestContentType: HTTP.ContentType, + requestHeaders: [Swift.String: Swift.String?], + requestOptions: RequestOptions? = nil + ) async throws -> [Swift.String: Swift.String] { + var headers = clientConfig.headers ?? [:] + + headers["Content-Type"] = buildContentTypeHeader( + requestBody: requestBody, + requestContentType: requestContentType + ) + + if let headerAuth = clientConfig.headerAuth { + headers[headerAuth.header] = requestOptions?.apiKey ?? headerAuth.key + } + if let basicAuthToken = clientConfig.basicAuth?.token { + headers["Authorization"] = "Basic \(basicAuthToken)" + } + if let bearerAuthToken = try await getBearerAuthToken(requestOptions) { + headers["Authorization"] = "Bearer \(bearerAuthToken)" + } + for (key, value) in requestHeaders { + if let value = value { + headers[key] = value + } + } + for (key, value) in requestOptions?.additionalHeaders ?? [:] { + headers[key] = value + } + + return headers + } + + private func buildContentTypeHeader( + requestBody: HTTP.RequestBody?, + requestContentType: HTTP.ContentType, + ) -> Swift.String { + var contentType = requestContentType.rawValue + if let requestBody, case .multipartFormData(let multipartData) = requestBody { + if contentType != HTTP.ContentType.multipartFormData.rawValue { + preconditionFailure( + "The content type for multipart form data requests must be multipart/form-data - this indicates an unexpected error in the SDK." + ) + } + // Multipart form data content type must include the boundary + contentType = "\(contentType); boundary=\(multipartData.boundary)" + } + return contentType + } + + private func getBearerAuthToken(_ requestOptions: RequestOptions?) async throws -> Swift.String? + { + if let tokenString = requestOptions?.token { + return tokenString + } + if let bearerAuth = clientConfig.bearerAuth { + return try await bearerAuth.token.retrieve() + } + return nil + } + + private func buildRequestBody( + requestBody: HTTP.RequestBody, + requestOptions: RequestOptions? = nil + ) -> Data { + switch requestBody { + case .jsonEncodable(let encodableBody): + do { + return try jsonEncoder.encode(encodableBody) + } catch { + preconditionFailure( + "Failed to encode request body: \(error) - this indicates an unexpected error in the SDK." + ) + } + case .data(let dataBody): + return dataBody + case .multipartFormData(let multipartData): + return multipartData.data() + } + } + + private func executeRequestWithURLSession( + _ request: Networking.URLRequest, + requestOptions: RequestOptions? = nil + ) async throws -> (Foundation.Data, Swift.String?) { + let maxRetries = requestOptions?.maxRetries ?? clientConfig.maxRetries + var lastResponse: (Foundation.Data, Networking.HTTPURLResponse)? + + for attempt in 0...maxRetries { + do { + let (data, response) = try await clientConfig.urlSession.data(for: request) + + guard let httpResponse = response as? Networking.HTTPURLResponse else { + throw ApiError.invalidResponse + } + + // Handle successful responses + if 200...299 ~= httpResponse.statusCode { + let contentType = httpResponse.value(forHTTPHeaderField: "Content-Type") + return (data, contentType) + } + + lastResponse = (data, httpResponse) + + if attempt < maxRetries && shouldRetry(statusCode: httpResponse.statusCode) { + let delay = getRetryDelay(response: httpResponse, retryAttempt: attempt) + try await _Concurrency.Task.sleep( + nanoseconds: Swift.UInt64(delay * 1_000_000_000)) + continue + } + + throw makeErrorFromResponse( + statusCode: httpResponse.statusCode, + data: data + ) + } catch { + let clientError: ApiError? + + // Treat timeouts as a first-class, non-retryable error + if let urlError = error as? Foundation.URLError, urlError.code == .timedOut { + clientError = .timeout(error) + } else if let existingClientError = error as? ApiError { + clientError = existingClientError + } else { + clientError = nil + } + + if attempt >= maxRetries || clientError != nil { + if let clientError { + throw clientError + } else { + throw ApiError.networkError(error) + } + } + let delay = Self.initialRetryDelay * pow(2.0, Swift.Double(attempt)) + let cappedDelay = min(delay, Self.maxRetryDelay) + let jitteredDelay = addSymmetricJitter(to: cappedDelay) + try await _Concurrency.Task.sleep( + nanoseconds: Swift.UInt64(jitteredDelay * 1_000_000_000)) + } + } + + if let (data, httpResponse) = lastResponse { + throw makeErrorFromResponse(statusCode: httpResponse.statusCode, data: data) + } + throw ApiError.invalidResponse + } + + private func shouldRetry(statusCode: Swift.Int) -> Swift.Bool { + return statusCode == 408 || statusCode == 429 || statusCode >= 500 + } + + private func getRetryDelay(response: Networking.HTTPURLResponse, retryAttempt: Swift.Int) + -> Foundation.TimeInterval + { + if let retryAfter = response.value(forHTTPHeaderField: "Retry-After") { + if let seconds = Swift.Double(retryAfter), seconds > 0 { + return min(seconds, Self.maxRetryDelay) + } + + if let date = parseHTTPDate(retryAfter) { + let delay = date.timeIntervalSinceNow + if delay > 0 { + return min(delay, Self.maxRetryDelay) + } + } + } + + if let rateLimitReset = response.value(forHTTPHeaderField: "X-RateLimit-Reset") { + if let resetTimeSeconds = Swift.Double(rateLimitReset) { + let resetDate = Foundation.Date(timeIntervalSince1970: resetTimeSeconds) + let delay = resetDate.timeIntervalSinceNow + if delay > 0 { + let cappedDelay = min(delay, Self.maxRetryDelay) + return addPositiveJitter(to: cappedDelay) + } + } + } + + let baseDelay = Self.initialRetryDelay * pow(2.0, Swift.Double(retryAttempt)) + let cappedDelay = min(baseDelay, Self.maxRetryDelay) + return addSymmetricJitter(to: cappedDelay) + } + + private func parseHTTPDate(_ dateString: Swift.String) -> Foundation.Date? { + let formatter = Foundation.DateFormatter() + formatter.locale = Foundation.Locale(identifier: "en_US_POSIX") + formatter.timeZone = Foundation.TimeZone(abbreviation: "GMT") + formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" + return formatter.date(from: dateString) + } + + private func addPositiveJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { + let jitterMultiplier = 1.0 + Swift.Double.random(in: 0...Self.jitterFactor) + return delay * jitterMultiplier + } + + private func addSymmetricJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { + let jitterMultiplier = + 1.0 + Swift.Double.random(in: -Self.jitterFactor / 2...Self.jitterFactor / 2) + return delay * jitterMultiplier + } + + private func makeErrorFromResponse(statusCode: Swift.Int, data: Foundation.Data) -> ApiError + { + let httpError = HTTPError.from( + statusCode: statusCode, + data: data, + jsonDecoder: jsonDecoder + ) + return ApiError.httpError(httpError) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift new file mode 100644 index 000000000000..06d0a4474d13 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift @@ -0,0 +1,45 @@ +import Foundation + +/// Helper class for building multipart form data requests +class MultipartFormData { + let boundary: Swift.String + private var bodyData: Foundation.Data + + init() { + self.boundary = "Boundary-\(Foundation.UUID().uuidString)" + self.bodyData = Foundation.Data() + } + + /// Append a file field to the form data + func appendFile( + _ data: Foundation.Data, withName name: Swift.String, fileName: Swift.String? = nil + ) { + bodyData.appendUTF8String("--\(boundary)\r\n") + var contentDisposition = "Content-Disposition: form-data; name=\"\(name)\"" + if let fileName { + contentDisposition += "; filename=\"\(fileName)\"" + } + contentDisposition += "\r\n" + bodyData.appendUTF8String(contentDisposition) + bodyData.appendUTF8String( + "Content-Type: \(HTTP.ContentType.applicationOctetStream.rawValue)\r\n\r\n") + bodyData.append(data) + bodyData.appendUTF8String("\r\n") + } + + /// Append a text field to the form data + func appendField(_ value: Swift.String, withName name: Swift.String) { + bodyData.appendUTF8String("--\(boundary)\r\n") + bodyData.appendUTF8String( + "Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") + bodyData.appendUTF8String(value) + bodyData.appendUTF8String("\r\n") + } + + /// Returns the complete multipart form data with closing boundary + func data() -> Foundation.Data { + var finalData = bodyData + finalData.appendUTF8String("--\(boundary)--\r\n") + return finalData + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift new file mode 100644 index 000000000000..6184b103cf1a --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift @@ -0,0 +1,42 @@ +import Foundation + +/// Protocol for types that can be converted to multipart form data +protocol MultipartFormDataConvertible { + /// The multipart fields that represent this request + var multipartFormFields: [MultipartFormField] { get } +} + +extension MultipartFormDataConvertible { + /// Converts this request to multipart form data + func asMultipartFormData() -> MultipartFormData { + let multipartData = MultipartFormData() + let jsonEncoder = Serde.jsonEncoder + + for field in multipartFormFields { + switch field { + case .file(let file, let fieldName): + multipartData.appendFile(file.data, withName: fieldName, fileName: file.filename) + case .fileArray(let files, let fieldName): + for file in files { + multipartData.appendFile( + file.data, + withName: fieldName, + fileName: file.filename + ) + } + case .field(let encodableValue, let fieldName): + do { + let encodedData = try jsonEncoder.encode(value: encodableValue) + if let encodedString = Swift.String(data: encodedData, encoding: .utf8) { + multipartData.appendField(encodedString, withName: fieldName) + } + } catch { + // Fallback - this should rarely happen with well-formed Encodable types + multipartData.appendField("", withName: fieldName) + } + } + } + + return multipartData + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift new file mode 100644 index 000000000000..3b57f57d8904 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift @@ -0,0 +1,17 @@ +import Foundation + +/// Represents a field in multipart form data +enum MultipartFormField { + /// A single file field + case file(_ file: FormFile, fieldName: Swift.String) + /// An array of files with the same field name + case fileArray(_ files: [FormFile], fieldName: Swift.String) + /// A text field with JSON-encoded value (for strings, numbers, booleans, dates, etc.) + case field(_ value: EncodableValue, fieldName: Swift.String) + + /// Create a text field from any Encodable value + static func field(_ value: T, fieldName: Swift.String) -> MultipartFormField + { + return .field(.init(value), fieldName: fieldName) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift new file mode 100644 index 000000000000..504f439749b5 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift @@ -0,0 +1,48 @@ +import Foundation + +enum QueryParameter { + case string(Swift.String) + case bool(Swift.Bool) + case int(Swift.Int) + case uint(Swift.UInt) + case uint64(Swift.UInt64) + case int64(Swift.Int64) + case float(Swift.Float) + case double(Swift.Double) + case date(Foundation.Date) + case calendarDate(CalendarDate) + case stringArray([Swift.String]) + case uuid(Foundation.UUID) + case unknown(Any) + + func toString() -> Swift.String { + switch self { + case .string(let value): + return value + case .bool(let value): + return value ? "true" : "false" + case .int(let value): + return Swift.String(value) + case .uint(let value): + return Swift.String(value) + case .uint64(let value): + return Swift.String(value) + case .int64(let value): + return Swift.String(value) + case .float(let value): + return Swift.String(value) + case .double(let value): + return Swift.String(value) + case .date(let value): + return value.ISO8601Format() + case .calendarDate(let value): + return value.description + case .stringArray(let values): + return values.joined(separator: ",") + case .uuid(let value): + return value.uuidString + case .unknown: + return "" + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift new file mode 100644 index 000000000000..4148f56b643d --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift @@ -0,0 +1,27 @@ +import Foundation + +extension Swift.Decoder { + func decodeAdditionalProperties< + T: Swift.Decodable, C: Swift.CaseIterable & Swift.RawRepresentable + >( + using codingKeysType: C.Type + ) throws + -> [Swift.String: T] where C.RawValue == Swift.String + { + return try decodeAdditionalProperties( + knownKeys: Swift.Set(codingKeysType.allCases.map(\.rawValue))) + } + + func decodeAdditionalProperties(knownKeys: Swift.Set) throws + -> [Swift.String: T] + { + let container = try container(keyedBy: StringKey.self) + let unknownKeys = Swift.Set(container.allKeys).subtracting( + knownKeys.map(StringKey.init(_:))) + guard !unknownKeys.isEmpty else { return .init() } + let keyValuePairs: [(Swift.String, T)] = try unknownKeys.compactMap { key in + (key.stringValue, try container.decode(T.self, forKey: key)) + } + return .init(uniqueKeysWithValues: keyValuePairs) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift new file mode 100644 index 000000000000..69a075f244f5 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift @@ -0,0 +1,10 @@ +import Foundation + +/// Type-erased wrapper for encodable values +struct EncodableValue { + let value: any Swift.Encodable + + init(_ value: T) { + self.value = value + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift new file mode 100644 index 000000000000..e1e7c48f9fc0 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift @@ -0,0 +1,13 @@ +import Foundation + +extension Swift.Encoder { + func encodeAdditionalProperties(_ additionalProperties: [Swift.String: T]) + throws + { + guard !additionalProperties.isEmpty else { return } + var container = self.container(keyedBy: StringKey.self) + for (key, value) in additionalProperties { + try container.encode(value, forKey: .init(key)) + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift new file mode 100644 index 000000000000..3f0ea998c148 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift @@ -0,0 +1,20 @@ +import Foundation + +extension Foundation.JSONEncoder { + /// Helper for type-erasing Encodable values + private struct AnyEncodable: Swift.Encodable { + private let value: any Swift.Encodable + + init(_ value: any Swift.Encodable) { + self.value = value + } + + func encode(to encoder: Swift.Encoder) throws { + try value.encode(to: encoder) + } + } + + func encode(value: EncodableValue) throws -> Foundation.Data { + return try self.encode(AnyEncodable(value.value)) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift new file mode 100644 index 000000000000..a5dfedba2c4b --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift @@ -0,0 +1,20 @@ +import Foundation + +extension Swift.KeyedDecodingContainer { + /// Decodes a Nullable? value, properly handling missing vs null vs value + /// Use this for `Optional>` fields + public func decodeNullableIfPresent( + _ type: T.Type, forKey key: Swift.KeyedDecodingContainer.Key + ) throws -> Nullable? where T: Swift.Decodable { + if contains(key) { + if try decodeNil(forKey: key) { + return .null + } else { + let value = try decode(type, forKey: key) + return .value(value) + } + } else { + return nil + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift new file mode 100644 index 000000000000..89ac59ace39b --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift @@ -0,0 +1,19 @@ +import Foundation + +extension Swift.KeyedEncodingContainer { + /// Encodes a Nullable? value, properly handling missing vs null vs value + /// Use this for `Optional>` fields + public mutating func encodeNullableIfPresent( + _ value: Nullable?, forKey key: Swift.KeyedEncodingContainer.Key + ) throws where T: Swift.Encodable { + switch value { + case nil: + // Don't encode the key at all - field is missing + break + case .some(.null): + try encodeNil(forKey: key) + case .some(.value(let wrapped)): + try encode(wrapped, forKey: key) + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift new file mode 100644 index 000000000000..b64e6cb3d410 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift @@ -0,0 +1,35 @@ +import Foundation + +final class Serde { + static var jsonEncoder: Foundation.JSONEncoder { + let encoder = Foundation.JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + return encoder + } + + static var jsonDecoder: Foundation.JSONDecoder { + let decoder = Foundation.JSONDecoder() + // Use custom strategy for robust ISO 8601 date parsing with fractional seconds + decoder.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + + let formatter = Foundation.ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + + if let date = formatter.date(from: dateString) { + return date + } + + // Fallback for dates without fractional seconds + formatter.formatOptions = [.withInternetDateTime] + if let date = formatter.date(from: dateString) { + return date + } + + throw Swift.DecodingError.dataCorruptedError( + in: container, debugDescription: "Invalid date format: \(dateString)") + } + return decoder + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift new file mode 100644 index 000000000000..a4f0c42b3c30 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift @@ -0,0 +1,18 @@ +import Foundation + +struct StringKey: Swift.CodingKey, Swift.Hashable { + var stringValue: Swift.String + var intValue: Swift.Int? { Swift.Int(stringValue) } + + init(_ string: Swift.String) { + self.stringValue = string + } + + init?(stringValue: Swift.String) { + self.stringValue = stringValue + } + + init?(intValue: Swift.Int) { + self.stringValue = Swift.String(intValue) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift b/seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift new file mode 100644 index 000000000000..f852dbd94cb1 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift @@ -0,0 +1,11 @@ +import Foundation + +extension Swift.String { + func urlPathEncoded() -> Swift.String { + return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self + } + + func urlQueryEncoded() -> Swift.String { + return self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift b/seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift new file mode 100644 index 000000000000..3159252bcd09 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift @@ -0,0 +1,105 @@ +import Foundation + +/// Represents a calendar date without time information, following RFC 3339 section 5.6 (`YYYY-MM-DD` format) +public struct CalendarDate: Swift.Codable, Swift.Hashable, Swift.Sendable, Swift + .CustomStringConvertible, Swift.Comparable +{ + /// The year component (expected range: 1-9999) + public let year: Swift.Int + + /// The month component (valid range: 1-12) + public let month: Swift.Int + + /// The day component (valid range: 1-31, depending on month) + public let day: Swift.Int + + /// Failable initializer for creating a CalendarDate with validation + public init?(year: Swift.Int, month: Swift.Int, day: Swift.Int) { + guard Self.isValidDate(year: year, month: month, day: day) else { + return nil + } + self.year = year + self.month = month + self.day = day + } + + /// Failable initializer for creating a CalendarDate from a `YYYY-MM-DD` string + public init?(_ dateString: Swift.String) { + let components = dateString.split(separator: "-") + guard components.count == 3, + let year = Swift.Int(components[0]), + let month = Swift.Int(components[1]), + let day = Swift.Int(components[2]) + else { + return nil + } + self.init(year: year, month: month, day: day) + } + + // MARK: - Codable + + public init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + guard let calendarDate = CalendarDate(dateString) else { + throw Error.invalidFormat(dateString) + } + self = calendarDate + } + + public func encode(to encoder: Swift.Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) + } + + // MARK: - CustomStringConvertible + + public var description: Swift.String { + // Format as YYYY-MM-DD with zero-padding + // %04d = 4-digit year with leading zeros (e.g., 2025) + // %02d = 2-digit month/day with leading zeros (e.g., 01, 05) + Swift.String(format: "%04d-%02d-%02d", year, month, day) + } + + // MARK: - Comparable + + public static func < (lhs: CalendarDate, rhs: CalendarDate) -> Swift.Bool { + if lhs.year != rhs.year { return lhs.year < rhs.year } + if lhs.month != rhs.month { return lhs.month < rhs.month } + return lhs.day < rhs.day + } + + // MARK: - Private Helpers + + /// Validates that the given year, month, and day form a valid calendar date using Foundation's Calendar APIs. + private static func isValidDate(year: Swift.Int, month: Swift.Int, day: Swift.Int) -> Swift.Bool + { + let calendar = Foundation.Calendar(identifier: .gregorian) + let components = Foundation.DateComponents(year: year, month: month, day: day) + + guard let date = calendar.date(from: components) else { + return false + } + + // Ensure the date components match what we created (handles invalid dates like Feb 30) + let reconstructedComponents = calendar.dateComponents([.year, .month, .day], from: date) + return + (reconstructedComponents.year == year + && reconstructedComponents.month == month + && reconstructedComponents.day == day) + } + + // MARK: - Error Types + + /// Errors that can occur when working with CalendarDate + public enum Error: Swift.Error, Foundation.LocalizedError { + case invalidFormat(Swift.String) + + public var errorDescription: Swift.String? { + switch self { + case .invalidFormat(let string): + return "Invalid date format: '\(string)'. Expected YYYY-MM-DD" + } + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift b/seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift new file mode 100644 index 000000000000..01ab9ae13b67 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift @@ -0,0 +1,82 @@ +import Foundation + +public final class ClientConfig: Swift.Sendable { + public typealias CredentialProvider = @Sendable () async throws -> Swift.String + + struct Defaults { + static let timeout: Swift.Int = 60 + static let maxRetries: Swift.Int = 2 + } + + struct HeaderAuth { + let key: Swift.String + let header: Swift.String + } + + struct BearerAuth { + let token: Token + + enum Token: Swift.Sendable { + case staticToken(Swift.String) + case provider(CredentialProvider) + + func retrieve() async throws -> Swift.String { + switch self { + case .staticToken(let token): + return token + case .provider(let provider): + return try await provider() + } + } + } + } + + struct BasicAuth { + let username: Swift.String? + let password: Swift.String? + + var token: Swift.String? { + if let username, let password { + let credentials: Swift.String = "\(username):\(password)" + let data = credentials.data(using: .utf8) ?? Foundation.Data() + return data.base64EncodedString() + } + return nil + } + } + + let baseURL: Swift.String + let headerAuth: HeaderAuth? + let bearerAuth: BearerAuth? + let basicAuth: BasicAuth? + let headers: [Swift.String: Swift.String]? + let timeout: Swift.Int + let maxRetries: Swift.Int + let urlSession: Networking.URLSession + + init( + baseURL: Swift.String, + headerAuth: HeaderAuth? = nil, + bearerAuth: BearerAuth? = nil, + basicAuth: BasicAuth? = nil, + headers: [Swift.String: Swift.String]? = nil, + timeout: Swift.Int? = nil, + maxRetries: Swift.Int? = nil, + urlSession: Networking.URLSession? = nil + ) { + self.baseURL = baseURL + self.headerAuth = headerAuth + self.bearerAuth = bearerAuth + self.basicAuth = basicAuth + self.headers = headers + self.timeout = timeout ?? Defaults.timeout + self.maxRetries = maxRetries ?? Defaults.maxRetries + self.urlSession = urlSession ?? buildURLSession(timeoutSeconds: self.timeout) + } +} + +private func buildURLSession(timeoutSeconds: Swift.Int) -> Networking.URLSession { + let configuration = Networking.URLSessionConfiguration.default + configuration.timeoutIntervalForRequest = .init(timeoutSeconds) + return .init(configuration: configuration) +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift b/seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift new file mode 100644 index 000000000000..4960a15bc000 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift @@ -0,0 +1,32 @@ +import Foundation + +/// Represents a file with its data and optional metadata for multipart uploads +public struct FormFile { + /// The file data + public let data: Foundation.Data + /// Optional filename + public let filename: Swift.String? + + public init(data: Foundation.Data, filename: Swift.String? = nil) { + self.data = data + self.filename = filename + } +} + +// MARK: - Convenience Initializers + +extension FormFile { + /// Create a FormFile from raw Data with no metadata + /// - Parameter data: The file data + public static func data(_ data: Foundation.Data) -> FormFile { + return FormFile(data: data) + } + + /// Create a FormFile from Data with a filename + /// - Parameters: + /// - data: The file data + /// - filename: The filename + public static func named(_ data: Foundation.Data, filename: Swift.String) -> FormFile { + return FormFile(data: data, filename: filename) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift b/seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift new file mode 100644 index 000000000000..69a1e2962c60 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift @@ -0,0 +1,190 @@ +import Foundation + +/// Represents an HTTP error response from the server. +/// +/// This type provides a structured view of non-success HTTP responses, including +/// the status code, an optional parsed error payload and a semantic classification. +public struct HTTPError: Swift.Error, Swift.CustomStringConvertible { + /// The HTTP status code returned by the server. + public let statusCode: Swift.Int + + /// Parsed error payload returned by the server, if any. + public let body: ResponseBody? + + /// Semantic classification of the error based on the status code. + public let kind: Kind + + public init( + statusCode: Swift.Int, + body: ResponseBody?, + kind: Kind + ) { + self.statusCode = statusCode + self.body = body + self.kind = kind + } + + // MARK: - Description + + public var description: Swift.String { + localizedDescription + } + + public var localizedDescription: Swift.String { + let defaultPrefix: Swift.String + switch kind { + case .redirect: + defaultPrefix = "Redirect error" + case .unauthorized: + defaultPrefix = "Unauthorized" + case .forbidden: + defaultPrefix = "Forbidden" + case .notFound: + defaultPrefix = "Not found" + case .client: + defaultPrefix = "Client error" + case .validation: + defaultPrefix = "Validation error" + case .serviceUnavailable: + defaultPrefix = "Service unavailable" + case .server: + defaultPrefix = "Server error" + case .other: + defaultPrefix = "HTTP error" + } + + guard let body = body else { + return "\(defaultPrefix) (Code: \(statusCode))" + } + + var message = defaultPrefix + + // Use server message if available and meaningful. + if let serverMessage = body.message, !serverMessage.isEmpty { + message = serverMessage + } + + var details = "Code: \(body.code)" + if let type = body.type, !type.isEmpty { + details += ", Type: \(type)" + } + + return "\(message) (\(details))" + } + + // MARK: - Factories + + /// Builds an ``HTTPError`` from an HTTP status code and raw response data. + /// + /// The mapping from status code to ``Kind`` is: + /// - `300...399` → `.redirect` + /// - `401` → `.unauthorized` + /// - `403` → `.forbidden` + /// - `404` → `.notFound` + /// - `422` → `.validation` + /// - `400...499` → `.client` + /// - `503` → `.serviceUnavailable` + /// - `500...599` → `.server` + /// - anything else → `.other` + public static func from( + statusCode: Swift.Int, + data: Foundation.Data, + jsonDecoder: Foundation.JSONDecoder + ) -> HTTPError { + let parsedBody = ResponseBody.decode( + statusCode: statusCode, + data: data, + using: jsonDecoder + ) + + let kind: Kind + switch statusCode { + case 300..<400: + kind = .redirect + case 401: + kind = .unauthorized + case 403: + kind = .forbidden + case 404: + kind = .notFound + case 422: + kind = .validation + case 503: + kind = .serviceUnavailable + case 400..<500: + kind = .client + case 500..<600: + kind = .server + default: + kind = .other + } + + return HTTPError(statusCode: statusCode, body: parsedBody, kind: kind) + } + + public enum Kind { + /// 3xx responses. + case redirect + /// 401 responses. + case unauthorized + /// 403 responses. + case forbidden + /// 404 responses. + case notFound + /// Other 4xx responses. + case client + /// 422 responses. + case validation + /// 503 responses. + case serviceUnavailable + /// Other 5xx responses. + case server + /// Any other non-success status code. + case other + } + + /// A best-effort representation of an error payload returned by an API. + /// + /// This type is intentionally minimal and is populated from a variety of possible + /// response body formats (typed JSON, loose JSON with a `"message"` field, or plain text). + public struct ResponseBody: Swift.Codable, Swift.Sendable { + public let code: Swift.Int + public let type: Swift.String? + public let message: Swift.String? + + /// Attempts to decode an `HTTPError.ResponseBody` from the given HTTP response payload. + public static func decode( + statusCode: Swift.Int, + data: Foundation.Data, + using jsonDecoder: Foundation.JSONDecoder + ) -> ResponseBody? { + // Try to parse as a strongly-typed error response first + if let errorResponse = try? jsonDecoder.decode(ResponseBody.self, from: data) { + return errorResponse + } + + // Try to parse as a simple JSON object with a "message" field + if let json = try? Foundation.JSONSerialization.jsonObject(with: data) + as? [Swift.String: Any], + let message = json["message"] as? Swift.String + { + return ResponseBody(code: statusCode, message: message) + } + + // Try to parse as plain text + if let errorMessage: String = Swift.String(data: data, encoding: .utf8), + !errorMessage.isEmpty + { + return ResponseBody(code: statusCode, message: errorMessage) + } + + return nil + } + + public init(code: Swift.Int, type: Swift.String? = nil, message: Swift.String? = nil) { + self.code = code + self.type = type + self.message = message + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift b/seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift new file mode 100644 index 000000000000..ec6aa924fc8c --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift @@ -0,0 +1,27 @@ +public final class Indirect: Codable, @unchecked Sendable { + public let value: T + + public init(_ value: T) { + self.value = value + } + + public convenience init(from decoder: Decoder) throws { + self.init(try T(from: decoder)) + } + + public func encode(to encoder: Encoder) throws { + try value.encode(to: encoder) + } +} + +extension Indirect: Equatable where T: Equatable { + public static func == (lhs: Indirect, rhs: Indirect) -> Bool { + lhs.value == rhs.value + } +} + +extension Indirect: Hashable where T: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift b/seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift new file mode 100644 index 000000000000..f8c56db4debf --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift @@ -0,0 +1,136 @@ +import Foundation + +/// A type that can represent any JSON value. +public enum JSONValue: Swift.Codable, Swift.Hashable, Swift.Sendable { + case string(Swift.String) + case number(Swift.Double) + case bool(Swift.Bool) + case null + case array([JSONValue]) + case object([Swift.String: JSONValue]) + + public init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self = .null + } else if let bool = try? container.decode(Swift.Bool.self) { + self = .bool(bool) + } else if let int = try? container.decode(Swift.Int.self) { + self = .number(Swift.Double(int)) + } else if let double = try? container.decode(Swift.Double.self) { + self = .number(double) + } else if let string = try? container.decode(Swift.String.self) { + self = .string(string) + } else if let array = try? container.decode([JSONValue].self) { + self = .array(array) + } else if let object = try? container.decode([Swift.String: JSONValue].self) { + self = .object(object) + } else { + throw Swift.DecodingError.dataCorrupted( + Swift.DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unable to decode JSONValue" + ) + ) + } + } + + public func encode(to encoder: Swift.Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .string(let string): + try container.encode(string) + case .number(let number): + try container.encode(number) + case .bool(let bool): + try container.encode(bool) + case .null: + try container.encodeNil() + case .array(let array): + try container.encode(array) + case .object(let object): + try container.encode(object) + } + } +} + +// MARK: - Convenience initializers +extension JSONValue { + public init(_ value: String) { + self = .string(value) + } + + public init(_ value: Int) { + self = .number(Double(value)) + } + + public init(_ value: Double) { + self = .number(value) + } + + public init(_ value: Bool) { + self = .bool(value) + } + + public init(_ value: [JSONValue]) { + self = .array(value) + } + + public init(_ value: [String: JSONValue]) { + self = .object(value) + } +} + +// MARK: - Value extraction +extension JSONValue { + public var stringValue: String? { + if case .string(let value) = self { + return value + } + return nil + } + + public var numberValue: Double? { + if case .number(let value) = self { + return value + } + return nil + } + + public var intValue: Int? { + if case .number(let value) = self { + return Int(value) + } + return nil + } + + public var boolValue: Bool? { + if case .bool(let value) = self { + return value + } + return nil + } + + public var arrayValue: [JSONValue]? { + if case .array(let value) = self { + return value + } + return nil + } + + public var objectValue: [String: JSONValue]? { + if case .object(let value) = self { + return value + } + return nil + } + + public var isNull: Bool { + if case .null = self { + return true + } + return false + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/Networking.swift b/seed/swift-sdk/allof-inline/Sources/Public/Networking.swift new file mode 100644 index 000000000000..ad54acdcf5fb --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/Networking.swift @@ -0,0 +1,21 @@ +import Foundation + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public enum Networking { + #if canImport(FoundationNetworking) + public typealias URLSession = FoundationNetworking.URLSession + public typealias URLSessionConfiguration = FoundationNetworking.URLSessionConfiguration + public typealias URLProtocol = FoundationNetworking.URLProtocol + public typealias URLRequest = FoundationNetworking.URLRequest + public typealias HTTPURLResponse = FoundationNetworking.HTTPURLResponse + #else + public typealias URLSession = Foundation.URLSession + public typealias URLSessionConfiguration = Foundation.URLSessionConfiguration + public typealias URLProtocol = Foundation.URLProtocol + public typealias URLRequest = Foundation.URLRequest + public typealias HTTPURLResponse = Foundation.HTTPURLResponse + #endif +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift b/seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift new file mode 100644 index 000000000000..81c92d7b82c6 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift @@ -0,0 +1,59 @@ +import Foundation + +/// Represents a value that can be either a concrete value or explicit `null`, distinguishing between null and missing fields in JSON. +public enum Nullable: Swift.Codable, Swift.Hashable, Swift.Sendable +where Wrapped: Swift.Codable & Swift.Hashable & Swift.Sendable { + case value(Wrapped) + case null + + public init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self = .null + } else { + let wrappedValue = try container.decode(Wrapped.self) + self = .value(wrappedValue) + } + } + + public func encode(to encoder: Swift.Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .value(let wrapped): + try container.encode(wrapped) + case .null: + try container.encodeNil() + } + } + + /// Returns the wrapped value if present, otherwise nil + public var wrappedValue: Wrapped? { + switch self { + case .value(let wrapped): + return wrapped + case .null: + return nil + } + } + + /// Returns true if this contains an explicit null value + public var isNull: Swift.Bool { + switch self { + case .value(_): + return false + case .null: + return true + } + } + + /// Convenience initializer from optional value + public init(_ value: Wrapped?) { + if let value = value { + self = .value(value) + } else { + self = .null + } + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift b/seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift new file mode 100644 index 000000000000..3f210d8cf887 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift @@ -0,0 +1,45 @@ +import Foundation + +/// Options for customizing an individual API request. +/// +/// Use this struct to override or supplement client-wide configuration for a single request. +public struct RequestOptions { + /// The API key to use for this request, overriding the client-wide API key if provided. + let apiKey: Swift.String? + + /// The token to use for this request, overriding the client-wide token if provided. + let token: Swift.String? + + /// The number of seconds to await an API call before timing out. If `nil`, uses the client or system default. + let timeout: Swift.Int? + + /// The number of times to retry a failed API call. If `nil`, uses the client or system default. + let maxRetries: Swift.Int? + + /// Additional HTTP headers to include with this request. These can override or supplement client-wide headers. + let additionalHeaders: [Swift.String: Swift.String]? + + /// Additional query parameters to include in the request URL. These are merged with any parameters generated from the request model. + let additionalQueryParameters: [Swift.String: Swift.String]? + + /// Additional body parameters to include in the request payload. These are merged with any parameters generated from the request model. + let additionalBodyParameters: [Swift.String: Swift.String]? + + public init( + apiKey: Swift.String? = nil, + token: Swift.String? = nil, + timeout: Swift.Int? = nil, + maxRetries: Swift.Int? = nil, + additionalHeaders: [Swift.String: Swift.String]? = nil, + additionalQueryParameters: [Swift.String: Swift.String]? = nil, + additionalBodyParameters: [Swift.String: Swift.String]? = nil + ) { + self.apiKey = apiKey + self.token = token + self.timeout = timeout + self.maxRetries = maxRetries + self.additionalHeaders = additionalHeaders + self.additionalQueryParameters = additionalQueryParameters + self.additionalBodyParameters = additionalBodyParameters + } +} diff --git a/seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift b/seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift new file mode 100644 index 000000000000..652324b903b1 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift @@ -0,0 +1,40 @@ +import Foundation + +extension Requests { + public struct RuleCreateRequest: Codable, Hashable, Sendable { + public let name: String + public let executionContext: RuleExecutionContext + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + name: String, + executionContext: RuleExecutionContext, + additionalProperties: [String: JSONValue] = .init() + ) { + self.name = name + self.executionContext = executionContext + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.executionContext = try container.decode(RuleExecutionContext.self, forKey: .executionContext) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.name, forKey: .name) + try container.encode(self.executionContext, forKey: .executionContext) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case name + case executionContext + } + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift b/seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift new file mode 100644 index 000000000000..2b06437b8213 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift @@ -0,0 +1,6 @@ +import Foundation + +/// Container for all inline request types used throughout the SDK. +/// +/// This enum serves as a namespace to organize request types that are defined inline within endpoint specifications. +public enum Requests {} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift new file mode 100644 index 000000000000..e2d2dca164b6 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift @@ -0,0 +1,55 @@ +import Foundation + +/// Common audit metadata. +public struct AuditInfo: Codable, Hashable, Sendable { + /// The user who created this resource. + public let createdBy: String? + /// When this resource was created. + public let createdDateTime: Date? + /// The user who last modified this resource. + public let modifiedBy: String? + /// When this resource was last modified. + public let modifiedDateTime: Date? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + createdBy: String? = nil, + createdDateTime: Date? = nil, + modifiedBy: String? = nil, + modifiedDateTime: Date? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.createdBy = createdBy + self.createdDateTime = createdDateTime + self.modifiedBy = modifiedBy + self.modifiedDateTime = modifiedDateTime + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) + self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) + self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) + self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.createdBy, forKey: .createdBy) + try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) + try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) + try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case createdBy + case createdDateTime + case modifiedBy + case modifiedDateTime + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift new file mode 100644 index 000000000000..826006803d13 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift @@ -0,0 +1,38 @@ +import Foundation + +public struct BaseOrg: Codable, Hashable, Sendable { + public let id: String + public let metadata: BaseOrgMetadata? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + metadata: BaseOrgMetadata? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.metadata = metadata + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.metadata = try container.decodeIfPresent(BaseOrgMetadata.self, forKey: .metadata) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.metadata, forKey: .metadata) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case metadata + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift new file mode 100644 index 000000000000..8b76b582c6ff --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct BaseOrgMetadata: Codable, Hashable, Sendable { + /// Deployment region from BaseOrg. + public let region: String + /// Subscription tier. + public let tier: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + region: String, + tier: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.region = region + self.tier = tier + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.region = try container.decode(String.self, forKey: .region) + self.tier = try container.decodeIfPresent(String.self, forKey: .tier) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.region, forKey: .region) + try container.encodeIfPresent(self.tier, forKey: .tier) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case region + case tier + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift new file mode 100644 index 000000000000..23e7e2d4b104 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift @@ -0,0 +1,53 @@ +import Foundation + +public struct CombinedEntity: Codable, Hashable, Sendable { + /// Unique identifier. + public let id: String + /// Display name from Describable. + public let name: String? + /// A short summary. + public let summary: String? + public let status: CombinedEntityStatus + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + name: String? = nil, + summary: String? = nil, + status: CombinedEntityStatus, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.name = name + self.summary = summary + self.status = status + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + self.summary = try container.decodeIfPresent(String.self, forKey: .summary) + self.status = try container.decode(CombinedEntityStatus.self, forKey: .status) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.name, forKey: .name) + try container.encodeIfPresent(self.summary, forKey: .summary) + try container.encode(self.status, forKey: .status) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case name + case summary + case status + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift new file mode 100644 index 000000000000..ad507eb4b537 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum CombinedEntityStatus: String, Codable, Hashable, CaseIterable, Sendable { + case active + case archived +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift new file mode 100644 index 000000000000..5f59f3f5a66b --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct Describable: Codable, Hashable, Sendable { + /// Display name from Describable. + public let name: String? + /// A short summary. + public let summary: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + name: String? = nil, + summary: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.name = name + self.summary = summary + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + self.summary = try container.decodeIfPresent(String.self, forKey: .summary) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.name, forKey: .name) + try container.encodeIfPresent(self.summary, forKey: .summary) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case name + case summary + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift new file mode 100644 index 000000000000..412a6626a9c5 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift @@ -0,0 +1,32 @@ +import Foundation + +public struct DetailedOrg: Codable, Hashable, Sendable { + public let metadata: DetailedOrgMetadata? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + metadata: DetailedOrgMetadata? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.metadata = metadata + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.metadata = try container.decodeIfPresent(DetailedOrgMetadata.self, forKey: .metadata) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.metadata, forKey: .metadata) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case metadata + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift new file mode 100644 index 000000000000..bf4903b3d0ea --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct DetailedOrgMetadata: Codable, Hashable, Sendable { + /// Deployment region from DetailedOrg. + public let region: String + /// Custom domain name. + public let domain: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + region: String, + domain: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.region = region + self.domain = domain + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.region = try container.decode(String.self, forKey: .region) + self.domain = try container.decodeIfPresent(String.self, forKey: .domain) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.region, forKey: .region) + try container.encodeIfPresent(self.domain, forKey: .domain) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case region + case domain + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift new file mode 100644 index 000000000000..c0c54d0a6b65 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct Identifiable: Codable, Hashable, Sendable { + /// Unique identifier. + public let id: String + /// Display name from Identifiable. + public let name: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + name: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.name = name + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.name, forKey: .name) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case name + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift new file mode 100644 index 000000000000..4b1c75c5b4fa --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct Organization: Codable, Hashable, Sendable { + public let id: String + public let metadata: OrganizationMetadata? + public let name: String + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + metadata: OrganizationMetadata? = nil, + name: String, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.metadata = metadata + self.name = name + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.metadata = try container.decodeIfPresent(OrganizationMetadata.self, forKey: .metadata) + self.name = try container.decode(String.self, forKey: .name) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.metadata, forKey: .metadata) + try container.encode(self.name, forKey: .name) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case metadata + case name + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift new file mode 100644 index 000000000000..0799ed018c50 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct OrganizationMetadata: Codable, Hashable, Sendable { + /// Deployment region from DetailedOrg. + public let region: String + /// Custom domain name. + public let domain: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + region: String, + domain: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.region = region + self.domain = domain + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.region = try container.decode(String.self, forKey: .region) + self.domain = try container.decodeIfPresent(String.self, forKey: .domain) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.region, forKey: .region) + try container.encodeIfPresent(self.domain, forKey: .domain) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case region + case domain + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift new file mode 100644 index 000000000000..60920a570d88 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct PaginatedResult: Codable, Hashable, Sendable { + public let paging: PagingCursors + /// Current page of results from the requested resource. + public let results: [JSONValue] + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + paging: PagingCursors, + results: [JSONValue], + additionalProperties: [String: JSONValue] = .init() + ) { + self.paging = paging + self.results = results + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.paging = try container.decode(PagingCursors.self, forKey: .paging) + self.results = try container.decode([JSONValue].self, forKey: .results) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.paging, forKey: .paging) + try container.encode(self.results, forKey: .results) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case paging + case results + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift new file mode 100644 index 000000000000..a6c6369f7a4e --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct PagingCursors: Codable, Hashable, Sendable { + /// Cursor for the next page of results. + public let next: String + /// Cursor for the previous page of results. + public let previous: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + next: String, + previous: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.next = next + self.previous = previous + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.next = try container.decode(String.self, forKey: .next) + self.previous = try container.decodeIfPresent(String.self, forKey: .previous) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.next, forKey: .next) + try container.encodeIfPresent(self.previous, forKey: .previous) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case next + case previous + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift new file mode 100644 index 000000000000..b3825cc062d2 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift @@ -0,0 +1,8 @@ +import Foundation + +/// Execution environment for a rule. +public enum RuleExecutionContext: String, Codable, Hashable, CaseIterable, Sendable { + case prod + case staging + case dev +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift new file mode 100644 index 000000000000..23834241d4e1 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift @@ -0,0 +1,78 @@ +import Foundation + +public struct RuleResponse: Codable, Hashable, Sendable { + /// The user who created this resource. + public let createdBy: String? + /// When this resource was created. + public let createdDateTime: Date? + /// The user who last modified this resource. + public let modifiedBy: String? + /// When this resource was last modified. + public let modifiedDateTime: Date? + public let id: String + public let name: String + public let status: RuleResponseStatus + public let executionContext: RuleExecutionContext? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + createdBy: String? = nil, + createdDateTime: Date? = nil, + modifiedBy: String? = nil, + modifiedDateTime: Date? = nil, + id: String, + name: String, + status: RuleResponseStatus, + executionContext: RuleExecutionContext? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.createdBy = createdBy + self.createdDateTime = createdDateTime + self.modifiedBy = modifiedBy + self.modifiedDateTime = modifiedDateTime + self.id = id + self.name = name + self.status = status + self.executionContext = executionContext + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) + self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) + self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) + self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decode(String.self, forKey: .name) + self.status = try container.decode(RuleResponseStatus.self, forKey: .status) + self.executionContext = try container.decodeIfPresent(RuleExecutionContext.self, forKey: .executionContext) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.createdBy, forKey: .createdBy) + try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) + try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) + try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) + try container.encode(self.id, forKey: .id) + try container.encode(self.name, forKey: .name) + try container.encode(self.status, forKey: .status) + try container.encodeIfPresent(self.executionContext, forKey: .executionContext) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case createdBy + case createdDateTime + case modifiedBy + case modifiedDateTime + case id + case name + case status + case executionContext + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift new file mode 100644 index 000000000000..1f8cbe93a34c --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum RuleResponseStatus: String, Codable, Hashable, CaseIterable, Sendable { + case active + case inactive + case draft +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift new file mode 100644 index 000000000000..89987c5def90 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct RuleType: Codable, Hashable, Sendable { + public let id: String + public let name: String + public let description: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + name: String, + description: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.name = name + self.description = description + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decode(String.self, forKey: .name) + self.description = try container.decodeIfPresent(String.self, forKey: .description) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encode(self.name, forKey: .name) + try container.encodeIfPresent(self.description, forKey: .description) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case name + case description + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift new file mode 100644 index 000000000000..4d5765dfec4d --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct RuleTypeSearchResponse: Codable, Hashable, Sendable { + public let paging: PagingCursors + /// Current page of results from the requested resource. + public let results: [RuleType]? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + paging: PagingCursors, + results: [RuleType]? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.paging = paging + self.results = results + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.paging = try container.decode(PagingCursors.self, forKey: .paging) + self.results = try container.decodeIfPresent([RuleType].self, forKey: .results) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.paging, forKey: .paging) + try container.encodeIfPresent(self.results, forKey: .results) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case paging + case results + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/User.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/User.swift new file mode 100644 index 000000000000..3e9a5ec45166 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/User.swift @@ -0,0 +1,38 @@ +import Foundation + +public struct User: Codable, Hashable, Sendable { + public let id: String + public let email: String + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + email: String, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.email = email + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.email = try container.decode(String.self, forKey: .email) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encode(self.email, forKey: .email) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case email + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift new file mode 100644 index 000000000000..50a9d6d4067a --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct UserSearchResponse: Codable, Hashable, Sendable { + public let paging: PagingCursors + /// Current page of results from the requested resource. + public let results: [User]? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + paging: PagingCursors, + results: [User]? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.paging = paging + self.results = results + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.paging = try container.decode(PagingCursors.self, forKey: .paging) + self.results = try container.decodeIfPresent([User].self, forKey: .results) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.paging, forKey: .paging) + try container.encodeIfPresent(self.results, forKey: .results) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case paging + case results + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Version.swift b/seed/swift-sdk/allof-inline/Sources/Version.swift new file mode 100644 index 000000000000..b17ac8c77973 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Sources/Version.swift @@ -0,0 +1 @@ +public let sdkVersion = "0.0.1" diff --git a/seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift b/seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift new file mode 100644 index 000000000000..63de33596c7b --- /dev/null +++ b/seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift @@ -0,0 +1,222 @@ +import Api +import Foundation +import Testing + +@Suite("Client Error & HTTP Error Tests") struct ClientErrorTests { + // MARK: - 4xx client errors + + @Test func testClientErrorFor400BadRequest() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 400, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Bad request"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 400) + try #require(httpError.kind == .client) + try #require(httpError.body?.message == "Bad request") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorFor404NotFound() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 404, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Not found"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 404) + try #require(httpError.kind == .notFound) + try #require(httpError.body?.message == "Not found") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorFor422ValidationError() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 422, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Validation failed"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 422) + try #require(httpError.kind == .validation) + try #require(httpError.body?.message == "Validation failed") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + // MARK: - 5xx server errors + + @Test func testClientErrorFor500ServerError() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 500, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Internal error"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 500) + try #require(httpError.kind == .server) + try #require(httpError.body?.message == "Internal error") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorFor503ServiceUnavailable() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 503, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Unavailable"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 503) + try #require(httpError.kind == .serviceUnavailable) + try #require(httpError.body?.message == "Unavailable") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + // MARK: - 3xx redirect & plain-text bodies + + @Test func testClientErrorFor302RedirectNoBody() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 302, + headers: ["Location": "https://example.com"], + body: Data() + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 302) + try #require(httpError.kind == .redirect) + try #require(httpError.body == nil) + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorPlainTextBodyIsDecoded() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 500, + headers: ["Content-Type": "text/plain"], + body: Data("Plain text error".utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 500) + try #require(httpError.kind == .server) + try #require(httpError.body?.message == "Plain text error") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } +} + diff --git a/seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift b/seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift new file mode 100644 index 000000000000..8ebbf4c73e22 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift @@ -0,0 +1,355 @@ +import Api +import Foundation +import Testing + +@Suite("Client Retry Tests") struct ClientRetryTests { + @Test func testRetryOn408RequestTimeout() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 3) + } + + @Test func testRetryOn429TooManyRequests() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 3) + } + + @Test func testRetryOn500InternalServerError() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 3) + } + + @Test func testRetryOn503ServiceUnavailable() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 503, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 2) + } + + @Test func testNoRetryOn400BadRequest() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 400, headers: ["Content-Type": "application/json"], + body: Data("{\"errorName\":\"BadRequest\"}".utf8) + ) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 1) + } + } + + @Test func testNoRetryOn404NotFound() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 404, headers: ["Content-Type": "application/json"], + body: Data("{\"errorName\":\"NotFound\"}".utf8) + ) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 1) + } + } + + @Test func testMaxRetriesExhausted() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 3) + } + } + + @Test func testRetryAfterHeaderWithSeconds() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 429, headers: ["Content-Type": "application/json", "Retry-After": "1"], + body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + let startTime = Date() + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + let elapsed = Date().timeIntervalSince(startTime) + + try #require(stub.getRequestCount() == 2) + try #require(elapsed >= 1.0) + } + + @Test func testRetryAfterHeaderWithHTTPDate() async throws { + let stub = HTTPStub() + let futureEpoch = ceil(Date().timeIntervalSince1970) + 1.0 + let futureDate = Date(timeIntervalSince1970: futureEpoch) + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(abbreviation: "GMT") + formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" + let httpDate = formatter.string(from: futureDate) + + stub.setResponseSequence([ + ( + statusCode: 429, + headers: ["Content-Type": "application/json", "Retry-After": httpDate], body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + let startTime = Date() + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + let elapsed = Date().timeIntervalSince(startTime) + + try #require(stub.getRequestCount() == 2) + try #require(elapsed >= 0.9) + } + + @Test func testXRateLimitResetHeader() async throws { + let stub = HTTPStub() + let futureTimestamp = Int(ceil(Date().timeIntervalSince1970)) + 1 + + stub.setResponseSequence([ + ( + statusCode: 429, + headers: [ + "Content-Type": "application/json", "X-RateLimit-Reset": "\(futureTimestamp)", + ], body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + let startTime = Date() + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + let elapsed = Date().timeIntervalSince(startTime) + + try #require(stub.getRequestCount() == 2) + try #require(elapsed >= 0.9) + } + + @Test func testEndpointLevelMaxRetriesOverride() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 5, additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 6) + } + + @Test func testEndpointLevelMaxRetriesZero() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 0, additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 1) + } + } + + @Test func testSuccessOnFirstAttempt() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 1) + } +} diff --git a/seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift b/seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift new file mode 100644 index 000000000000..82a3aa80ad22 --- /dev/null +++ b/seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift @@ -0,0 +1,317 @@ +import Api +import Foundation + +final class HTTPStub { + private static func buildURLSession(stubId: String, operationQueue: OperationQueue) + -> Networking.URLSession + { + let config = buildURLSessionConfiguration(stubId: stubId) + if let uuid = UUID(uuidString: stubId) { + StubURLProtocol.register(queue: operationQueue, id: uuid) + } + return Networking.URLSession(configuration: config, delegate: nil, delegateQueue: operationQueue) + } + + private static func buildURLSessionConfiguration(stubId: String) -> Networking.URLSessionConfiguration { + let config = Networking.URLSessionConfiguration.ephemeral + config.protocolClasses = [StubURLProtocol.self] + config.requestCachePolicy = .reloadIgnoringLocalCacheData + config.urlCache = nil + config.httpAdditionalHeaders = ["Stub-ID": stubId] + return config + } + + private static func buildOperationQueue() -> OperationQueue { + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + queue.qualityOfService = .userInitiated + return queue + } + + private let session: Networking.URLSession + private let delegateQueue: OperationQueue + private let identifier: UUID + + init() { + self.identifier = UUID() + self.delegateQueue = Self.buildOperationQueue() + self.session = Self.buildURLSession( + stubId: identifier.uuidString, operationQueue: delegateQueue) + #if !canImport(Darwin) + // On Linux, URLProtocol doesn't get the additional headers at canInit time. + // Track the active stub id so the protocol can resolve responses without headers. + StubURLProtocol.setActiveStubId(identifier) + #endif + } + + var urlSession: Networking.URLSession { + session + } + + var headers: [String: String] { + ["Stub-ID": identifier.uuidString] + } + + func setResponse( + statusCode: Int = 200, + headers: [String: String] = ["Content-Type": "application/json"], + body: Data + ) { + StubURLProtocol.configure( + id: identifier, + statusCode: statusCode, + headers: headers, + body: body + ) + } + + func setResponseSequence( + _ responses: [(statusCode: Int, headers: [String: String], body: Data)] + ) { + StubURLProtocol.configureSequence(id: identifier, responses: responses) + } + + func takeLastRequest() -> Networking.URLRequest? { + StubURLProtocol.takeLastRequest(for: identifier) + } + + func getRequestCount() -> Int { + StubURLProtocol.getRequestCount(for: identifier) + } + + deinit { + StubURLProtocol.reset(id: identifier) + StubURLProtocol.deregister(queue: delegateQueue) + #if !canImport(Darwin) + StubURLProtocol.clearActiveStubId(identifier) + #endif + } +} + +private final class StubURLProtocol: Networking.URLProtocol { + struct Response { + let statusCode: Int + let headers: [String: String] + let body: Data + var lastRequest: Networking.URLRequest? + } + + struct ResponseSequence { + var responses: [Response] + var currentIndex: Int = 0 + var lastRequest: Networking.URLRequest? + + mutating func nextResponse() -> Response? { + guard currentIndex < responses.count else { return nil } + let response = responses[currentIndex] + currentIndex += 1 + return response + } + } + + private static var responses: [UUID: Response] = [:] + private static var responseSequences: [UUID: ResponseSequence] = [:] + private static let lock = NSLock() + private static var queueIdMap: [ObjectIdentifier: UUID] = [:] + #if !canImport(Darwin) + // Fallback for Linux where request headers may not be visible in canInit. + private static var activeStubIds: [UUID] = [] + #endif + + static func configure( + id: UUID, + statusCode: Int, + headers: [String: String], + body: Data + ) { + lock.lock() + responses[id] = Response( + statusCode: statusCode, headers: headers, body: body, lastRequest: nil) + responseSequences[id] = nil + lock.unlock() + } + + static func configureSequence( + id: UUID, + responses: [(statusCode: Int, headers: [String: String], body: Data)] + ) { + lock.lock() + let responseList = responses.map { + Response( + statusCode: $0.statusCode, headers: $0.headers, body: $0.body, lastRequest: nil) + } + responseSequences[id] = ResponseSequence(responses: responseList) + StubURLProtocol.responses[id] = nil + lock.unlock() + } + + static func takeLastRequest(for id: UUID) -> Networking.URLRequest? { + lock.lock() + defer { lock.unlock() } + + if var sequence = responseSequences[id], let request = sequence.lastRequest { + sequence.lastRequest = nil + responseSequences[id] = sequence + return request + } + + guard var response = responses[id], let request = response.lastRequest else { + return nil + } + response.lastRequest = nil + responses[id] = response + return request + } + + static func getRequestCount(for id: UUID) -> Int { + lock.lock() + defer { lock.unlock() } + + if let sequence = responseSequences[id] { + return sequence.currentIndex + } + + return responses[id]?.lastRequest != nil ? 1 : 0 + } + + static func reset(id: UUID) { + lock.lock() + responses[id] = nil + responseSequences[id] = nil + lock.unlock() + } + + static func register(queue: OperationQueue, id: UUID) { + lock.lock() + queueIdMap[ObjectIdentifier(queue)] = id + lock.unlock() + } + + static func deregister(queue: OperationQueue) { + lock.lock() + queueIdMap[ObjectIdentifier(queue)] = nil + lock.unlock() + } + + #if !canImport(Darwin) + static func setActiveStubId(_ id: UUID) { + lock.lock() + activeStubIds.append(id) + lock.unlock() + } + + static func clearActiveStubId(_ id: UUID) { + lock.lock() + if let idx = activeStubIds.lastIndex(of: id) { + activeStubIds.remove(at: idx) + } + lock.unlock() + } + #endif + + override class func canInit(with request: Networking.URLRequest) -> Bool { + #if canImport(Darwin) + return request.value(forHTTPHeaderField: "Stub-ID") != nil + #else + // On Linux, intercept all requests created by the session that installed this protocol. + // We'll resolve the correct stub id during startLoading using the active id stack. + return true + #endif + } + + override class func canonicalRequest(for request: Networking.URLRequest) -> Networking.URLRequest { + request + } + + override func startLoading() { + guard let client else { return } + #if canImport(Darwin) + guard let idValue = request.value(forHTTPHeaderField: "Stub-ID"), + let id = UUID(uuidString: idValue) + else { + client.urlProtocol(self, didFailWithError: URLError(.cannotFindHost)) + return + } + #else + // Prefer the Stub-ID header if available on Linux; fall back to the active id stack. + var resolvedId: UUID? + if let idValue = request.value(forHTTPHeaderField: "Stub-ID"), + let headerId = UUID(uuidString: idValue) + { + resolvedId = headerId + } else { + if let currentQueue = OperationQueue.current { + StubURLProtocol.lock.lock() + if let mapped = StubURLProtocol.queueIdMap[ObjectIdentifier(currentQueue)] { + resolvedId = mapped + } + StubURLProtocol.lock.unlock() + } + StubURLProtocol.lock.lock() + resolvedId = StubURLProtocol.activeStubIds.last + StubURLProtocol.lock.unlock() + } + guard let id = resolvedId else { + client.urlProtocol(self, didFailWithError: URLError(.unknown)) + return + } + #endif + + StubURLProtocol.lock.lock() + + if var sequence = StubURLProtocol.responseSequences[id] { + guard let response = sequence.nextResponse() else { + StubURLProtocol.lock.unlock() + client.urlProtocol(self, didFailWithError: URLError(.unknown)) + return + } + sequence.lastRequest = request + StubURLProtocol.responseSequences[id] = sequence + StubURLProtocol.lock.unlock() + + guard let url = request.url else { + client.urlProtocol(self, didFailWithError: URLError(.badURL)) + return + } + + let httpResponse = Networking.HTTPURLResponse( + url: url, + statusCode: response.statusCode, + httpVersion: "HTTP/1.1", + headerFields: response.headers + )! + + client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) + client.urlProtocol(self, didLoad: response.body) + client.urlProtocolDidFinishLoading(self) + return + } + + guard var response = StubURLProtocol.responses[id] else { + StubURLProtocol.lock.unlock() + client.urlProtocol(self, didFailWithError: URLError(.unknown)) + return + } + response.lastRequest = request + StubURLProtocol.responses[id] = response + StubURLProtocol.lock.unlock() + + guard let url = request.url else { + client.urlProtocol(self, didFailWithError: URLError(.badURL)) + return + } + + let httpResponse = Networking.HTTPURLResponse( + url: url, + statusCode: response.statusCode, + httpVersion: "HTTP/1.1", + headerFields: response.headers + )! + + client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) + client.urlProtocol(self, didLoad: response.body) + client.urlProtocolDidFinishLoading(self) + } + + override func stopLoading() {} +} diff --git a/seed/swift-sdk/allof-inline/reference.md b/seed/swift-sdk/allof-inline/reference.md new file mode 100644 index 000000000000..75ea9a025cca --- /dev/null +++ b/seed/swift-sdk/allof-inline/reference.md @@ -0,0 +1,265 @@ +# Reference +
client.searchRuleTypes(query: String?, requestOptions: RequestOptions?) -> RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.searchRuleTypes() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `String?` + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions?) -> RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `Requests.RuleCreateRequest` + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.listUsers(requestOptions: RequestOptions?) -> UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.listUsers() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.getEntity(requestOptions: RequestOptions?) -> CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.getEntity() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.getOrganization(requestOptions: RequestOptions?) -> Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.getOrganization() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/swift-sdk/allof-inline/snippet.json b/seed/swift-sdk/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/swift-sdk/allof/.fern/metadata.json b/seed/swift-sdk/allof/.fern/metadata.json new file mode 100644 index 000000000000..c78c560bfd6a --- /dev/null +++ b/seed/swift-sdk/allof/.fern/metadata.json @@ -0,0 +1,8 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-swift-sdk", + "generatorVersion": "latest", + "generatorConfig": {}, + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Package.swift b/seed/swift-sdk/allof/Package.swift new file mode 100644 index 000000000000..a66695fa5685 --- /dev/null +++ b/seed/swift-sdk/allof/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "Api", + platforms: [ + .iOS(.v15), + .macOS(.v12), + .tvOS(.v15), + .watchOS(.v8) + ], + products: [ + .library( + name: "Api", + targets: ["Api"] + ) + ], + dependencies: [], + targets: [ + .target( + name: "Api", + path: "Sources" + ), + .testTarget( + name: "ApiTests", + dependencies: ["Api"], + path: "Tests" + ) + ] +) diff --git a/seed/swift-sdk/allof/README.md b/seed/swift-sdk/allof/README.md new file mode 100644 index 000000000000..f17a8fa15733 --- /dev/null +++ b/seed/swift-sdk/allof/README.md @@ -0,0 +1,180 @@ +# Seed Swift Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FSwift) +![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-orange.svg) + +The Seed Swift library provides convenient access to the Seed APIs from Swift. + +## Table of Contents + +- [Requirements](#requirements) +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Errors](#errors) +- [Request Types](#request-types) +- [Advanced](#advanced) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) + - [Timeouts](#timeouts) + - [Custom Networking Client](#custom-networking-client) +- [Contributing](#contributing) + +## Requirements + +This SDK requires: +- Swift 5.7+ +- iOS 15+ +- macOS 12+ +- tvOS 15+ +- watchOS 8+ + +## Installation + +With Swift Package Manager (SPM), add the following to the top-level `dependencies` array within your `Package.swift` file: + +```swift +dependencies: [ + .package(url: "https://github.com/allof/fern", from: "0.0.1"), +] +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```swift +import Api + +let client = ApiClient( + ..., + environment: .default +) +``` + +## Errors + +The SDK throws a single error enum for all failures. Client-side issues encoding/decoding failures and network errors use dedicated cases, while non-success HTTP responses are wrapped in an `HTTPError` that exposes the status code, a simple classification and an optional decoded message. + +```swift +import Api + +let client = ApiClient(...) + +do { + let response = try await client.createRule(...) + // Handle successful response +} catch let error as ApiError { + switch error { + case .httpError(let httpError): + print("Status code:", httpError.statusCode) + print("Kind:", httpError.kind) + print("Message:", httpError.body?.message ?? httpError.localizedDescription) + case .encodingError(let underlying): + print("Encoding error:", underlying) + case .networkError(let underlying): + print("Network error:", underlying) + default: + print("Other client error:", error) + } +} catch { + print("Unexpected error:", error) +} +``` + +## Request Types + +The SDK exports all request types as Swift structs. Simply import the SDK module to access them: + +```swift +import Api + +let request = Requests.RuleCreateRequest( + ... +) +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `additionalHeaders` request option. + +```swift +try await client.createRule(..., requestOptions: .init( + additionalHeaders: [ + "X-Custom-Header": "custom value" + ] +)) +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `additionalQueryParameters` request option. + +```swift +try await client.createRule(..., requestOptions: .init( + additionalQueryParameters: [ + "custom_query_param_key": "custom_query_param_value" + ] +)) +``` + +### Timeouts + +The SDK defaults to a 60-second timeout. Use the `timeout` option to configure this behavior. + +```swift +try await client.createRule(..., requestOptions: .init( + timeout: 30 +)) +``` + +### Custom Networking Client + +The SDK allows you to customize the underlying `URLSession` used for HTTP requests. Use the `urlSession` option to provide your own configured `URLSession` instance. + +```swift +import Foundation +import Api + +let client = ApiClient( + ..., + urlSession: // Provide your implementation here +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/swift-sdk/allof/Snippets/Example0.swift b/seed/swift-sdk/allof/Snippets/Example0.swift new file mode 100644 index 000000000000..6a8a354329f0 --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example0.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.searchRuleTypes() +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example1.swift b/seed/swift-sdk/allof/Snippets/Example1.swift new file mode 100644 index 000000000000..165f0fa4c377 --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example1.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.searchRuleTypes(query: "query") +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example2.swift b/seed/swift-sdk/allof/Snippets/Example2.swift new file mode 100644 index 000000000000..77e7ea37ecaf --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example2.swift @@ -0,0 +1,13 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example3.swift b/seed/swift-sdk/allof/Snippets/Example3.swift new file mode 100644 index 000000000000..77e7ea37ecaf --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example3.swift @@ -0,0 +1,13 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example4.swift b/seed/swift-sdk/allof/Snippets/Example4.swift new file mode 100644 index 000000000000..25d9aafe2c0e --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example4.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.listUsers() +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example5.swift b/seed/swift-sdk/allof/Snippets/Example5.swift new file mode 100644 index 000000000000..25d9aafe2c0e --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example5.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.listUsers() +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example6.swift b/seed/swift-sdk/allof/Snippets/Example6.swift new file mode 100644 index 000000000000..cd12bd038474 --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example6.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getEntity() +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example7.swift b/seed/swift-sdk/allof/Snippets/Example7.swift new file mode 100644 index 000000000000..cd12bd038474 --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example7.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getEntity() +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example8.swift b/seed/swift-sdk/allof/Snippets/Example8.swift new file mode 100644 index 000000000000..935ba9bb5c06 --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example8.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getOrganization() +} + +try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example9.swift b/seed/swift-sdk/allof/Snippets/Example9.swift new file mode 100644 index 000000000000..935ba9bb5c06 --- /dev/null +++ b/seed/swift-sdk/allof/Snippets/Example9.swift @@ -0,0 +1,10 @@ +import Foundation +import Api + +private func main() async throws { + let client = ApiClient(baseURL: "https://api.fern.com") + + _ = try await client.getOrganization() +} + +try await main() diff --git a/seed/swift-sdk/allof/Sources/ApiClient.swift b/seed/swift-sdk/allof/Sources/ApiClient.swift new file mode 100644 index 000000000000..3bb0c30a67c5 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/ApiClient.swift @@ -0,0 +1,104 @@ +import Foundation + +/// Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. +public final class ApiClient: Sendable { + private let httpClient: HTTPClient + + /// Initialize the client with the specified configuration. + /// + /// - Parameter baseURL: The base URL to use for requests from the client. If not provided, the default base URL will be used. + /// - Parameter headers: Additional headers to send with each request. + /// - Parameter timeout: Request timeout in seconds. Defaults to 60 seconds. Ignored if a custom `urlSession` is provided. + /// - Parameter maxRetries: Maximum number of retries for failed requests. Defaults to 2. + /// - Parameter urlSession: Custom `URLSession` to use for requests. If not provided, a default session will be created with the specified timeout. + public convenience init( + baseURL: String = ApiEnvironment.default.rawValue, + headers: [String: String]? = nil, + timeout: Int? = nil, + maxRetries: Int? = nil, + urlSession: Networking.URLSession? = nil + ) { + self.init( + baseURL: baseURL, + headerAuth: nil, + bearerAuth: nil, + basicAuth: nil, + headers: headers, + timeout: timeout, + maxRetries: maxRetries, + urlSession: urlSession + ) + } + + init( + baseURL: String, + headerAuth: ClientConfig.HeaderAuth? = nil, + bearerAuth: ClientConfig.BearerAuth? = nil, + basicAuth: ClientConfig.BasicAuth? = nil, + headers: [String: String]? = nil, + timeout: Int? = nil, + maxRetries: Int? = nil, + urlSession: Networking.URLSession? = nil + ) { + let config = ClientConfig( + baseURL: baseURL, + headerAuth: headerAuth, + bearerAuth: bearerAuth, + basicAuth: basicAuth, + headers: headers, + timeout: timeout, + maxRetries: maxRetries, + urlSession: urlSession + ) + self.httpClient = HTTPClient(config: config) + } + + public func searchRuleTypes(query: String? = nil, requestOptions: RequestOptions? = nil) async throws -> RuleTypeSearchResponse { + return try await httpClient.performRequest( + method: .get, + path: "/rule-types", + queryParams: [ + "query": query.map { .string($0) } + ], + requestOptions: requestOptions, + responseType: RuleTypeSearchResponse.self + ) + } + + public func createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions? = nil) async throws -> RuleResponse { + return try await httpClient.performRequest( + method: .post, + path: "/rules", + body: request, + requestOptions: requestOptions, + responseType: RuleResponse.self + ) + } + + public func listUsers(requestOptions: RequestOptions? = nil) async throws -> UserSearchResponse { + return try await httpClient.performRequest( + method: .get, + path: "/users", + requestOptions: requestOptions, + responseType: UserSearchResponse.self + ) + } + + public func getEntity(requestOptions: RequestOptions? = nil) async throws -> CombinedEntity { + return try await httpClient.performRequest( + method: .get, + path: "/entities", + requestOptions: requestOptions, + responseType: CombinedEntity.self + ) + } + + public func getOrganization(requestOptions: RequestOptions? = nil) async throws -> Organization { + return try await httpClient.performRequest( + method: .get, + path: "/organizations", + requestOptions: requestOptions, + responseType: Organization.self + ) + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/ApiEnvironment.swift b/seed/swift-sdk/allof/Sources/ApiEnvironment.swift new file mode 100644 index 000000000000..817df36e4a46 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/ApiEnvironment.swift @@ -0,0 +1,5 @@ +import Foundation + +public enum ApiEnvironment: String, CaseIterable { + case `default` = "https://api.example.com" +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/ApiError.swift b/seed/swift-sdk/allof/Sources/ApiError.swift new file mode 100644 index 000000000000..37bf4a118e61 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/ApiError.swift @@ -0,0 +1,57 @@ +import Foundation + +/// High-level error type thrown by generated Swift SDKs. +/// +/// Request / client-side failures are represented as dedicated cases and +/// HTTP response failures are wrapped in an `HTTPError` that classifies the status code. +public enum ApiError: Swift.Error { + // MARK: - Client / transport errors + + /// The request URL could not be constructed. + case invalidURL(Swift.String) + + /// The request body could not be encoded. + case encodingError(Swift.Error) + + /// The response body could not be decoded. + case decodingError(Swift.Error) + + /// The SDK received a response it could not interpret as a valid HTTP response. + case invalidResponse + + /// An underlying networking error occurred (e.g., connection reset). + case networkError(Swift.Error) + + /// The request timed out. + case timeout(Swift.Error?) + + // MARK: - HTTP response errors + + /// An error HTTP response was returned by the server. + case httpError(HTTPError) + + // MARK: - Description + + public var errorDescription: Swift.String? { + switch self { + case .invalidURL(let url): + return "Invalid URL '\(url)'" + case .encodingError(let error): + return "Failed to encode request: \(error.localizedDescription)" + case .decodingError(let error): + return "Failed to decode response: \(error.localizedDescription)" + case .invalidResponse: + return "Invalid response received" + case .networkError(let error): + return "Network error: \(error.localizedDescription)" + case .timeout(let underlying): + if let underlying { + return "Request timed out: \(underlying.localizedDescription)" + } else { + return "Request timed out" + } + case .httpError(let httpError): + return httpError.localizedDescription + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Data+String.swift b/seed/swift-sdk/allof/Sources/Core/Data+String.swift new file mode 100644 index 000000000000..ddf326ffbfe6 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Data+String.swift @@ -0,0 +1,15 @@ +import Foundation + +// MARK: - Data + String Extensions +extension Foundation.Data { + /// Safely appends a UTF-8 encoded string to the data + /// + /// - Parameter string: The string to append + mutating func appendUTF8String(_ string: Swift.String) { + guard let data = string.data(using: .utf8) else { + assertionFailure("Failed to encode string to UTF-8: \(string)") + return + } + self.append(data) + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift b/seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift new file mode 100644 index 000000000000..445e45cbdb96 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift @@ -0,0 +1,24 @@ +import Foundation + +enum HTTP { + enum Method: Swift.String, Swift.CaseIterable { + case get = "GET" + case post = "POST" + case put = "PUT" + case delete = "DELETE" + case patch = "PATCH" + case head = "HEAD" + } + + enum ContentType: Swift.String, Swift.CaseIterable { + case applicationJson = "application/json" + case applicationOctetStream = "application/octet-stream" + case multipartFormData = "multipart/form-data" + } + + enum RequestBody { + case jsonEncodable(any Swift.Encodable) + case data(Foundation.Data) + case multipartFormData(MultipartFormData) + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift b/seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift new file mode 100644 index 000000000000..18e8927122ef --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift @@ -0,0 +1,397 @@ +import Foundation + +final class HTTPClient: Swift.Sendable { + private let clientConfig: ClientConfig + private let jsonEncoder = Serde.jsonEncoder + private let jsonDecoder = Serde.jsonDecoder + + private static let initialRetryDelay: Foundation.TimeInterval = 1.0 // 1 second + private static let maxRetryDelay: Foundation.TimeInterval = 60.0 // 60 seconds + private static let jitterFactor: Swift.Double = 0.2 // 20% jitter + + init(config: ClientConfig) { + self.clientConfig = config + } + + /// Performs a request with no response. + func performRequest( + method: HTTP.Method, + path: Swift.String, + contentType requestContentType: HTTP.ContentType = .applicationJson, + headers requestHeaders: [Swift.String: Swift.String?] = [:], + queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], + body requestBody: Any? = nil, + requestOptions: RequestOptions? = nil + ) async throws { + _ = try await performRequest( + method: method, + path: path, + contentType: requestContentType, + headers: requestHeaders, + queryParams: requestQueryParams, + body: requestBody, + requestOptions: requestOptions, + responseType: Foundation.Data.self + ) + } + + /// Performs a request with the specified response type. + func performRequest( + method: HTTP.Method, + path: Swift.String, + contentType requestContentType: HTTP.ContentType = .applicationJson, + headers requestHeaders: [Swift.String: Swift.String?] = [:], + queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], + body requestBody: Any? = nil, + requestOptions: RequestOptions? = nil, + responseType: T.Type + ) async throws -> T { + let requestBody: HTTP.RequestBody? = requestBody.map { body in + if let multipartData = body as? MultipartFormData { + return .multipartFormData(multipartData) + } else if let data = body as? Foundation.Data { + return .data(data) + } else if let encodable = body as? any Swift.Encodable { + return .jsonEncodable(encodable) + } else { + preconditionFailure("Unsupported body type: \(type(of: body))") + } + } + + let request = try await buildRequest( + method: method, + path: path, + requestContentType: requestContentType, + requestHeaders: requestHeaders, + requestQueryParams: requestQueryParams, + requestBody: requestBody, + requestOptions: requestOptions + ) + + let (data, _) = try await executeRequestWithURLSession( + request, + requestOptions: requestOptions + ) + + if responseType == Foundation.Data.self { + if let data = data as? T { + return data + } else { + throw ApiError.invalidResponse + } + } + + if responseType == Swift.String.self { + if let string = Swift.String(data: data, encoding: .utf8) as? T { + return string + } else { + throw ApiError.invalidResponse + } + } + + do { + return try jsonDecoder.decode(responseType, from: data) + } catch { + throw ApiError.decodingError(error) + } + } + + private func buildRequest( + method: HTTP.Method, + path: Swift.String, + requestContentType: HTTP.ContentType, + requestHeaders: [Swift.String: Swift.String?], + requestQueryParams: [Swift.String: QueryParameter?], + requestBody: HTTP.RequestBody? = nil, + requestOptions: RequestOptions? = nil + ) async throws -> Networking.URLRequest { + // Init with URL + let url = buildRequestURL( + path: path, requestQueryParams: requestQueryParams, requestOptions: requestOptions + ) + var request = Networking.URLRequest(url: url) + + // Set timeout + if let timeout = requestOptions?.timeout { + request.timeoutInterval = Foundation.TimeInterval(timeout) + } + + // Set method + request.httpMethod = method.rawValue + + // Set headers + let headers = try await buildRequestHeaders( + requestBody: requestBody, + requestContentType: requestContentType, + requestHeaders: requestHeaders, + requestOptions: requestOptions + ) + for (key, value) in headers { + request.setValue(value, forHTTPHeaderField: key) + } + + // Set body + if let requestBody = requestBody { + request.httpBody = buildRequestBody( + requestBody: requestBody, + requestOptions: requestOptions + ) + } + + return request + } + + private func buildRequestURL( + path: Swift.String, + requestQueryParams: [Swift.String: QueryParameter?], + requestOptions: RequestOptions? = nil + ) -> URL { + let endpointURL = "\(clientConfig.baseURL)\(path)" + guard var components = Foundation.URLComponents(string: endpointURL) else { + preconditionFailure( + "Invalid URL '\(endpointURL)' - this indicates an unexpected error in the SDK." + ) + } + if !requestQueryParams.isEmpty { + let baseItems: [Foundation.URLQueryItem] = requestQueryParams.compactMap { key, value in + guard let unwrapped = value else { return nil } + let stringValue = unwrapped.toString() + guard !stringValue.isEmpty else { return nil } + return Foundation.URLQueryItem(name: key, value: stringValue) + } + if !baseItems.isEmpty { + components.queryItems = baseItems + } + } + if let additionalQueryParams = requestOptions?.additionalQueryParameters { + let extraItems = additionalQueryParams.compactMap { key, value in + value.isEmpty ? nil : Foundation.URLQueryItem(name: key, value: value) + } + if components.queryItems == nil { + components.queryItems = extraItems + } else { + components.queryItems?.append(contentsOf: extraItems) + } + } + guard let url = components.url else { + preconditionFailure( + "Failed to construct URL from components - this indicates an unexpected error in the SDK." + ) + } + return url + } + + private func buildRequestHeaders( + requestBody: HTTP.RequestBody?, + requestContentType: HTTP.ContentType, + requestHeaders: [Swift.String: Swift.String?], + requestOptions: RequestOptions? = nil + ) async throws -> [Swift.String: Swift.String] { + var headers = clientConfig.headers ?? [:] + + headers["Content-Type"] = buildContentTypeHeader( + requestBody: requestBody, + requestContentType: requestContentType + ) + + if let headerAuth = clientConfig.headerAuth { + headers[headerAuth.header] = requestOptions?.apiKey ?? headerAuth.key + } + if let basicAuthToken = clientConfig.basicAuth?.token { + headers["Authorization"] = "Basic \(basicAuthToken)" + } + if let bearerAuthToken = try await getBearerAuthToken(requestOptions) { + headers["Authorization"] = "Bearer \(bearerAuthToken)" + } + for (key, value) in requestHeaders { + if let value = value { + headers[key] = value + } + } + for (key, value) in requestOptions?.additionalHeaders ?? [:] { + headers[key] = value + } + + return headers + } + + private func buildContentTypeHeader( + requestBody: HTTP.RequestBody?, + requestContentType: HTTP.ContentType, + ) -> Swift.String { + var contentType = requestContentType.rawValue + if let requestBody, case .multipartFormData(let multipartData) = requestBody { + if contentType != HTTP.ContentType.multipartFormData.rawValue { + preconditionFailure( + "The content type for multipart form data requests must be multipart/form-data - this indicates an unexpected error in the SDK." + ) + } + // Multipart form data content type must include the boundary + contentType = "\(contentType); boundary=\(multipartData.boundary)" + } + return contentType + } + + private func getBearerAuthToken(_ requestOptions: RequestOptions?) async throws -> Swift.String? + { + if let tokenString = requestOptions?.token { + return tokenString + } + if let bearerAuth = clientConfig.bearerAuth { + return try await bearerAuth.token.retrieve() + } + return nil + } + + private func buildRequestBody( + requestBody: HTTP.RequestBody, + requestOptions: RequestOptions? = nil + ) -> Data { + switch requestBody { + case .jsonEncodable(let encodableBody): + do { + return try jsonEncoder.encode(encodableBody) + } catch { + preconditionFailure( + "Failed to encode request body: \(error) - this indicates an unexpected error in the SDK." + ) + } + case .data(let dataBody): + return dataBody + case .multipartFormData(let multipartData): + return multipartData.data() + } + } + + private func executeRequestWithURLSession( + _ request: Networking.URLRequest, + requestOptions: RequestOptions? = nil + ) async throws -> (Foundation.Data, Swift.String?) { + let maxRetries = requestOptions?.maxRetries ?? clientConfig.maxRetries + var lastResponse: (Foundation.Data, Networking.HTTPURLResponse)? + + for attempt in 0...maxRetries { + do { + let (data, response) = try await clientConfig.urlSession.data(for: request) + + guard let httpResponse = response as? Networking.HTTPURLResponse else { + throw ApiError.invalidResponse + } + + // Handle successful responses + if 200...299 ~= httpResponse.statusCode { + let contentType = httpResponse.value(forHTTPHeaderField: "Content-Type") + return (data, contentType) + } + + lastResponse = (data, httpResponse) + + if attempt < maxRetries && shouldRetry(statusCode: httpResponse.statusCode) { + let delay = getRetryDelay(response: httpResponse, retryAttempt: attempt) + try await _Concurrency.Task.sleep( + nanoseconds: Swift.UInt64(delay * 1_000_000_000)) + continue + } + + throw makeErrorFromResponse( + statusCode: httpResponse.statusCode, + data: data + ) + } catch { + let clientError: ApiError? + + // Treat timeouts as a first-class, non-retryable error + if let urlError = error as? Foundation.URLError, urlError.code == .timedOut { + clientError = .timeout(error) + } else if let existingClientError = error as? ApiError { + clientError = existingClientError + } else { + clientError = nil + } + + if attempt >= maxRetries || clientError != nil { + if let clientError { + throw clientError + } else { + throw ApiError.networkError(error) + } + } + let delay = Self.initialRetryDelay * pow(2.0, Swift.Double(attempt)) + let cappedDelay = min(delay, Self.maxRetryDelay) + let jitteredDelay = addSymmetricJitter(to: cappedDelay) + try await _Concurrency.Task.sleep( + nanoseconds: Swift.UInt64(jitteredDelay * 1_000_000_000)) + } + } + + if let (data, httpResponse) = lastResponse { + throw makeErrorFromResponse(statusCode: httpResponse.statusCode, data: data) + } + throw ApiError.invalidResponse + } + + private func shouldRetry(statusCode: Swift.Int) -> Swift.Bool { + return statusCode == 408 || statusCode == 429 || statusCode >= 500 + } + + private func getRetryDelay(response: Networking.HTTPURLResponse, retryAttempt: Swift.Int) + -> Foundation.TimeInterval + { + if let retryAfter = response.value(forHTTPHeaderField: "Retry-After") { + if let seconds = Swift.Double(retryAfter), seconds > 0 { + return min(seconds, Self.maxRetryDelay) + } + + if let date = parseHTTPDate(retryAfter) { + let delay = date.timeIntervalSinceNow + if delay > 0 { + return min(delay, Self.maxRetryDelay) + } + } + } + + if let rateLimitReset = response.value(forHTTPHeaderField: "X-RateLimit-Reset") { + if let resetTimeSeconds = Swift.Double(rateLimitReset) { + let resetDate = Foundation.Date(timeIntervalSince1970: resetTimeSeconds) + let delay = resetDate.timeIntervalSinceNow + if delay > 0 { + let cappedDelay = min(delay, Self.maxRetryDelay) + return addPositiveJitter(to: cappedDelay) + } + } + } + + let baseDelay = Self.initialRetryDelay * pow(2.0, Swift.Double(retryAttempt)) + let cappedDelay = min(baseDelay, Self.maxRetryDelay) + return addSymmetricJitter(to: cappedDelay) + } + + private func parseHTTPDate(_ dateString: Swift.String) -> Foundation.Date? { + let formatter = Foundation.DateFormatter() + formatter.locale = Foundation.Locale(identifier: "en_US_POSIX") + formatter.timeZone = Foundation.TimeZone(abbreviation: "GMT") + formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" + return formatter.date(from: dateString) + } + + private func addPositiveJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { + let jitterMultiplier = 1.0 + Swift.Double.random(in: 0...Self.jitterFactor) + return delay * jitterMultiplier + } + + private func addSymmetricJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { + let jitterMultiplier = + 1.0 + Swift.Double.random(in: -Self.jitterFactor / 2...Self.jitterFactor / 2) + return delay * jitterMultiplier + } + + private func makeErrorFromResponse(statusCode: Swift.Int, data: Foundation.Data) -> ApiError + { + let httpError = HTTPError.from( + statusCode: statusCode, + data: data, + jsonDecoder: jsonDecoder + ) + return ApiError.httpError(httpError) + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift new file mode 100644 index 000000000000..06d0a4474d13 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift @@ -0,0 +1,45 @@ +import Foundation + +/// Helper class for building multipart form data requests +class MultipartFormData { + let boundary: Swift.String + private var bodyData: Foundation.Data + + init() { + self.boundary = "Boundary-\(Foundation.UUID().uuidString)" + self.bodyData = Foundation.Data() + } + + /// Append a file field to the form data + func appendFile( + _ data: Foundation.Data, withName name: Swift.String, fileName: Swift.String? = nil + ) { + bodyData.appendUTF8String("--\(boundary)\r\n") + var contentDisposition = "Content-Disposition: form-data; name=\"\(name)\"" + if let fileName { + contentDisposition += "; filename=\"\(fileName)\"" + } + contentDisposition += "\r\n" + bodyData.appendUTF8String(contentDisposition) + bodyData.appendUTF8String( + "Content-Type: \(HTTP.ContentType.applicationOctetStream.rawValue)\r\n\r\n") + bodyData.append(data) + bodyData.appendUTF8String("\r\n") + } + + /// Append a text field to the form data + func appendField(_ value: Swift.String, withName name: Swift.String) { + bodyData.appendUTF8String("--\(boundary)\r\n") + bodyData.appendUTF8String( + "Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") + bodyData.appendUTF8String(value) + bodyData.appendUTF8String("\r\n") + } + + /// Returns the complete multipart form data with closing boundary + func data() -> Foundation.Data { + var finalData = bodyData + finalData.appendUTF8String("--\(boundary)--\r\n") + return finalData + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift new file mode 100644 index 000000000000..6184b103cf1a --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift @@ -0,0 +1,42 @@ +import Foundation + +/// Protocol for types that can be converted to multipart form data +protocol MultipartFormDataConvertible { + /// The multipart fields that represent this request + var multipartFormFields: [MultipartFormField] { get } +} + +extension MultipartFormDataConvertible { + /// Converts this request to multipart form data + func asMultipartFormData() -> MultipartFormData { + let multipartData = MultipartFormData() + let jsonEncoder = Serde.jsonEncoder + + for field in multipartFormFields { + switch field { + case .file(let file, let fieldName): + multipartData.appendFile(file.data, withName: fieldName, fileName: file.filename) + case .fileArray(let files, let fieldName): + for file in files { + multipartData.appendFile( + file.data, + withName: fieldName, + fileName: file.filename + ) + } + case .field(let encodableValue, let fieldName): + do { + let encodedData = try jsonEncoder.encode(value: encodableValue) + if let encodedString = Swift.String(data: encodedData, encoding: .utf8) { + multipartData.appendField(encodedString, withName: fieldName) + } + } catch { + // Fallback - this should rarely happen with well-formed Encodable types + multipartData.appendField("", withName: fieldName) + } + } + } + + return multipartData + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift new file mode 100644 index 000000000000..3b57f57d8904 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift @@ -0,0 +1,17 @@ +import Foundation + +/// Represents a field in multipart form data +enum MultipartFormField { + /// A single file field + case file(_ file: FormFile, fieldName: Swift.String) + /// An array of files with the same field name + case fileArray(_ files: [FormFile], fieldName: Swift.String) + /// A text field with JSON-encoded value (for strings, numbers, booleans, dates, etc.) + case field(_ value: EncodableValue, fieldName: Swift.String) + + /// Create a text field from any Encodable value + static func field(_ value: T, fieldName: Swift.String) -> MultipartFormField + { + return .field(.init(value), fieldName: fieldName) + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift b/seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift new file mode 100644 index 000000000000..504f439749b5 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift @@ -0,0 +1,48 @@ +import Foundation + +enum QueryParameter { + case string(Swift.String) + case bool(Swift.Bool) + case int(Swift.Int) + case uint(Swift.UInt) + case uint64(Swift.UInt64) + case int64(Swift.Int64) + case float(Swift.Float) + case double(Swift.Double) + case date(Foundation.Date) + case calendarDate(CalendarDate) + case stringArray([Swift.String]) + case uuid(Foundation.UUID) + case unknown(Any) + + func toString() -> Swift.String { + switch self { + case .string(let value): + return value + case .bool(let value): + return value ? "true" : "false" + case .int(let value): + return Swift.String(value) + case .uint(let value): + return Swift.String(value) + case .uint64(let value): + return Swift.String(value) + case .int64(let value): + return Swift.String(value) + case .float(let value): + return Swift.String(value) + case .double(let value): + return Swift.String(value) + case .date(let value): + return value.ISO8601Format() + case .calendarDate(let value): + return value.description + case .stringArray(let values): + return values.joined(separator: ",") + case .uuid(let value): + return value.uuidString + case .unknown: + return "" + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift b/seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift new file mode 100644 index 000000000000..4148f56b643d --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift @@ -0,0 +1,27 @@ +import Foundation + +extension Swift.Decoder { + func decodeAdditionalProperties< + T: Swift.Decodable, C: Swift.CaseIterable & Swift.RawRepresentable + >( + using codingKeysType: C.Type + ) throws + -> [Swift.String: T] where C.RawValue == Swift.String + { + return try decodeAdditionalProperties( + knownKeys: Swift.Set(codingKeysType.allCases.map(\.rawValue))) + } + + func decodeAdditionalProperties(knownKeys: Swift.Set) throws + -> [Swift.String: T] + { + let container = try container(keyedBy: StringKey.self) + let unknownKeys = Swift.Set(container.allKeys).subtracting( + knownKeys.map(StringKey.init(_:))) + guard !unknownKeys.isEmpty else { return .init() } + let keyValuePairs: [(Swift.String, T)] = try unknownKeys.compactMap { key in + (key.stringValue, try container.decode(T.self, forKey: key)) + } + return .init(uniqueKeysWithValues: keyValuePairs) + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift b/seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift new file mode 100644 index 000000000000..69a075f244f5 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift @@ -0,0 +1,10 @@ +import Foundation + +/// Type-erased wrapper for encodable values +struct EncodableValue { + let value: any Swift.Encodable + + init(_ value: T) { + self.value = value + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift b/seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift new file mode 100644 index 000000000000..e1e7c48f9fc0 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift @@ -0,0 +1,13 @@ +import Foundation + +extension Swift.Encoder { + func encodeAdditionalProperties(_ additionalProperties: [Swift.String: T]) + throws + { + guard !additionalProperties.isEmpty else { return } + var container = self.container(keyedBy: StringKey.self) + for (key, value) in additionalProperties { + try container.encode(value, forKey: .init(key)) + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift b/seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift new file mode 100644 index 000000000000..3f0ea998c148 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift @@ -0,0 +1,20 @@ +import Foundation + +extension Foundation.JSONEncoder { + /// Helper for type-erasing Encodable values + private struct AnyEncodable: Swift.Encodable { + private let value: any Swift.Encodable + + init(_ value: any Swift.Encodable) { + self.value = value + } + + func encode(to encoder: Swift.Encoder) throws { + try value.encode(to: encoder) + } + } + + func encode(value: EncodableValue) throws -> Foundation.Data { + return try self.encode(AnyEncodable(value.value)) + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift b/seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift new file mode 100644 index 000000000000..a5dfedba2c4b --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift @@ -0,0 +1,20 @@ +import Foundation + +extension Swift.KeyedDecodingContainer { + /// Decodes a Nullable? value, properly handling missing vs null vs value + /// Use this for `Optional>` fields + public func decodeNullableIfPresent( + _ type: T.Type, forKey key: Swift.KeyedDecodingContainer.Key + ) throws -> Nullable? where T: Swift.Decodable { + if contains(key) { + if try decodeNil(forKey: key) { + return .null + } else { + let value = try decode(type, forKey: key) + return .value(value) + } + } else { + return nil + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift b/seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift new file mode 100644 index 000000000000..89ac59ace39b --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift @@ -0,0 +1,19 @@ +import Foundation + +extension Swift.KeyedEncodingContainer { + /// Encodes a Nullable? value, properly handling missing vs null vs value + /// Use this for `Optional>` fields + public mutating func encodeNullableIfPresent( + _ value: Nullable?, forKey key: Swift.KeyedEncodingContainer.Key + ) throws where T: Swift.Encodable { + switch value { + case nil: + // Don't encode the key at all - field is missing + break + case .some(.null): + try encodeNil(forKey: key) + case .some(.value(let wrapped)): + try encode(wrapped, forKey: key) + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift b/seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift new file mode 100644 index 000000000000..b64e6cb3d410 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift @@ -0,0 +1,35 @@ +import Foundation + +final class Serde { + static var jsonEncoder: Foundation.JSONEncoder { + let encoder = Foundation.JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + return encoder + } + + static var jsonDecoder: Foundation.JSONDecoder { + let decoder = Foundation.JSONDecoder() + // Use custom strategy for robust ISO 8601 date parsing with fractional seconds + decoder.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + + let formatter = Foundation.ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + + if let date = formatter.date(from: dateString) { + return date + } + + // Fallback for dates without fractional seconds + formatter.formatOptions = [.withInternetDateTime] + if let date = formatter.date(from: dateString) { + return date + } + + throw Swift.DecodingError.dataCorruptedError( + in: container, debugDescription: "Invalid date format: \(dateString)") + } + return decoder + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift b/seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift new file mode 100644 index 000000000000..a4f0c42b3c30 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift @@ -0,0 +1,18 @@ +import Foundation + +struct StringKey: Swift.CodingKey, Swift.Hashable { + var stringValue: Swift.String + var intValue: Swift.Int? { Swift.Int(stringValue) } + + init(_ string: Swift.String) { + self.stringValue = string + } + + init?(stringValue: Swift.String) { + self.stringValue = stringValue + } + + init?(intValue: Swift.Int) { + self.stringValue = Swift.String(intValue) + } +} diff --git a/seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift b/seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift new file mode 100644 index 000000000000..f852dbd94cb1 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift @@ -0,0 +1,11 @@ +import Foundation + +extension Swift.String { + func urlPathEncoded() -> Swift.String { + return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self + } + + func urlQueryEncoded() -> Swift.String { + return self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self + } +} diff --git a/seed/swift-sdk/allof/Sources/Public/CalendarDate.swift b/seed/swift-sdk/allof/Sources/Public/CalendarDate.swift new file mode 100644 index 000000000000..3159252bcd09 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/CalendarDate.swift @@ -0,0 +1,105 @@ +import Foundation + +/// Represents a calendar date without time information, following RFC 3339 section 5.6 (`YYYY-MM-DD` format) +public struct CalendarDate: Swift.Codable, Swift.Hashable, Swift.Sendable, Swift + .CustomStringConvertible, Swift.Comparable +{ + /// The year component (expected range: 1-9999) + public let year: Swift.Int + + /// The month component (valid range: 1-12) + public let month: Swift.Int + + /// The day component (valid range: 1-31, depending on month) + public let day: Swift.Int + + /// Failable initializer for creating a CalendarDate with validation + public init?(year: Swift.Int, month: Swift.Int, day: Swift.Int) { + guard Self.isValidDate(year: year, month: month, day: day) else { + return nil + } + self.year = year + self.month = month + self.day = day + } + + /// Failable initializer for creating a CalendarDate from a `YYYY-MM-DD` string + public init?(_ dateString: Swift.String) { + let components = dateString.split(separator: "-") + guard components.count == 3, + let year = Swift.Int(components[0]), + let month = Swift.Int(components[1]), + let day = Swift.Int(components[2]) + else { + return nil + } + self.init(year: year, month: month, day: day) + } + + // MARK: - Codable + + public init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + guard let calendarDate = CalendarDate(dateString) else { + throw Error.invalidFormat(dateString) + } + self = calendarDate + } + + public func encode(to encoder: Swift.Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) + } + + // MARK: - CustomStringConvertible + + public var description: Swift.String { + // Format as YYYY-MM-DD with zero-padding + // %04d = 4-digit year with leading zeros (e.g., 2025) + // %02d = 2-digit month/day with leading zeros (e.g., 01, 05) + Swift.String(format: "%04d-%02d-%02d", year, month, day) + } + + // MARK: - Comparable + + public static func < (lhs: CalendarDate, rhs: CalendarDate) -> Swift.Bool { + if lhs.year != rhs.year { return lhs.year < rhs.year } + if lhs.month != rhs.month { return lhs.month < rhs.month } + return lhs.day < rhs.day + } + + // MARK: - Private Helpers + + /// Validates that the given year, month, and day form a valid calendar date using Foundation's Calendar APIs. + private static func isValidDate(year: Swift.Int, month: Swift.Int, day: Swift.Int) -> Swift.Bool + { + let calendar = Foundation.Calendar(identifier: .gregorian) + let components = Foundation.DateComponents(year: year, month: month, day: day) + + guard let date = calendar.date(from: components) else { + return false + } + + // Ensure the date components match what we created (handles invalid dates like Feb 30) + let reconstructedComponents = calendar.dateComponents([.year, .month, .day], from: date) + return + (reconstructedComponents.year == year + && reconstructedComponents.month == month + && reconstructedComponents.day == day) + } + + // MARK: - Error Types + + /// Errors that can occur when working with CalendarDate + public enum Error: Swift.Error, Foundation.LocalizedError { + case invalidFormat(Swift.String) + + public var errorDescription: Swift.String? { + switch self { + case .invalidFormat(let string): + return "Invalid date format: '\(string)'. Expected YYYY-MM-DD" + } + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Public/ClientConfig.swift b/seed/swift-sdk/allof/Sources/Public/ClientConfig.swift new file mode 100644 index 000000000000..01ab9ae13b67 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/ClientConfig.swift @@ -0,0 +1,82 @@ +import Foundation + +public final class ClientConfig: Swift.Sendable { + public typealias CredentialProvider = @Sendable () async throws -> Swift.String + + struct Defaults { + static let timeout: Swift.Int = 60 + static let maxRetries: Swift.Int = 2 + } + + struct HeaderAuth { + let key: Swift.String + let header: Swift.String + } + + struct BearerAuth { + let token: Token + + enum Token: Swift.Sendable { + case staticToken(Swift.String) + case provider(CredentialProvider) + + func retrieve() async throws -> Swift.String { + switch self { + case .staticToken(let token): + return token + case .provider(let provider): + return try await provider() + } + } + } + } + + struct BasicAuth { + let username: Swift.String? + let password: Swift.String? + + var token: Swift.String? { + if let username, let password { + let credentials: Swift.String = "\(username):\(password)" + let data = credentials.data(using: .utf8) ?? Foundation.Data() + return data.base64EncodedString() + } + return nil + } + } + + let baseURL: Swift.String + let headerAuth: HeaderAuth? + let bearerAuth: BearerAuth? + let basicAuth: BasicAuth? + let headers: [Swift.String: Swift.String]? + let timeout: Swift.Int + let maxRetries: Swift.Int + let urlSession: Networking.URLSession + + init( + baseURL: Swift.String, + headerAuth: HeaderAuth? = nil, + bearerAuth: BearerAuth? = nil, + basicAuth: BasicAuth? = nil, + headers: [Swift.String: Swift.String]? = nil, + timeout: Swift.Int? = nil, + maxRetries: Swift.Int? = nil, + urlSession: Networking.URLSession? = nil + ) { + self.baseURL = baseURL + self.headerAuth = headerAuth + self.bearerAuth = bearerAuth + self.basicAuth = basicAuth + self.headers = headers + self.timeout = timeout ?? Defaults.timeout + self.maxRetries = maxRetries ?? Defaults.maxRetries + self.urlSession = urlSession ?? buildURLSession(timeoutSeconds: self.timeout) + } +} + +private func buildURLSession(timeoutSeconds: Swift.Int) -> Networking.URLSession { + let configuration = Networking.URLSessionConfiguration.default + configuration.timeoutIntervalForRequest = .init(timeoutSeconds) + return .init(configuration: configuration) +} diff --git a/seed/swift-sdk/allof/Sources/Public/FormFile.swift b/seed/swift-sdk/allof/Sources/Public/FormFile.swift new file mode 100644 index 000000000000..4960a15bc000 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/FormFile.swift @@ -0,0 +1,32 @@ +import Foundation + +/// Represents a file with its data and optional metadata for multipart uploads +public struct FormFile { + /// The file data + public let data: Foundation.Data + /// Optional filename + public let filename: Swift.String? + + public init(data: Foundation.Data, filename: Swift.String? = nil) { + self.data = data + self.filename = filename + } +} + +// MARK: - Convenience Initializers + +extension FormFile { + /// Create a FormFile from raw Data with no metadata + /// - Parameter data: The file data + public static func data(_ data: Foundation.Data) -> FormFile { + return FormFile(data: data) + } + + /// Create a FormFile from Data with a filename + /// - Parameters: + /// - data: The file data + /// - filename: The filename + public static func named(_ data: Foundation.Data, filename: Swift.String) -> FormFile { + return FormFile(data: data, filename: filename) + } +} diff --git a/seed/swift-sdk/allof/Sources/Public/HTTPError.swift b/seed/swift-sdk/allof/Sources/Public/HTTPError.swift new file mode 100644 index 000000000000..69a1e2962c60 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/HTTPError.swift @@ -0,0 +1,190 @@ +import Foundation + +/// Represents an HTTP error response from the server. +/// +/// This type provides a structured view of non-success HTTP responses, including +/// the status code, an optional parsed error payload and a semantic classification. +public struct HTTPError: Swift.Error, Swift.CustomStringConvertible { + /// The HTTP status code returned by the server. + public let statusCode: Swift.Int + + /// Parsed error payload returned by the server, if any. + public let body: ResponseBody? + + /// Semantic classification of the error based on the status code. + public let kind: Kind + + public init( + statusCode: Swift.Int, + body: ResponseBody?, + kind: Kind + ) { + self.statusCode = statusCode + self.body = body + self.kind = kind + } + + // MARK: - Description + + public var description: Swift.String { + localizedDescription + } + + public var localizedDescription: Swift.String { + let defaultPrefix: Swift.String + switch kind { + case .redirect: + defaultPrefix = "Redirect error" + case .unauthorized: + defaultPrefix = "Unauthorized" + case .forbidden: + defaultPrefix = "Forbidden" + case .notFound: + defaultPrefix = "Not found" + case .client: + defaultPrefix = "Client error" + case .validation: + defaultPrefix = "Validation error" + case .serviceUnavailable: + defaultPrefix = "Service unavailable" + case .server: + defaultPrefix = "Server error" + case .other: + defaultPrefix = "HTTP error" + } + + guard let body = body else { + return "\(defaultPrefix) (Code: \(statusCode))" + } + + var message = defaultPrefix + + // Use server message if available and meaningful. + if let serverMessage = body.message, !serverMessage.isEmpty { + message = serverMessage + } + + var details = "Code: \(body.code)" + if let type = body.type, !type.isEmpty { + details += ", Type: \(type)" + } + + return "\(message) (\(details))" + } + + // MARK: - Factories + + /// Builds an ``HTTPError`` from an HTTP status code and raw response data. + /// + /// The mapping from status code to ``Kind`` is: + /// - `300...399` → `.redirect` + /// - `401` → `.unauthorized` + /// - `403` → `.forbidden` + /// - `404` → `.notFound` + /// - `422` → `.validation` + /// - `400...499` → `.client` + /// - `503` → `.serviceUnavailable` + /// - `500...599` → `.server` + /// - anything else → `.other` + public static func from( + statusCode: Swift.Int, + data: Foundation.Data, + jsonDecoder: Foundation.JSONDecoder + ) -> HTTPError { + let parsedBody = ResponseBody.decode( + statusCode: statusCode, + data: data, + using: jsonDecoder + ) + + let kind: Kind + switch statusCode { + case 300..<400: + kind = .redirect + case 401: + kind = .unauthorized + case 403: + kind = .forbidden + case 404: + kind = .notFound + case 422: + kind = .validation + case 503: + kind = .serviceUnavailable + case 400..<500: + kind = .client + case 500..<600: + kind = .server + default: + kind = .other + } + + return HTTPError(statusCode: statusCode, body: parsedBody, kind: kind) + } + + public enum Kind { + /// 3xx responses. + case redirect + /// 401 responses. + case unauthorized + /// 403 responses. + case forbidden + /// 404 responses. + case notFound + /// Other 4xx responses. + case client + /// 422 responses. + case validation + /// 503 responses. + case serviceUnavailable + /// Other 5xx responses. + case server + /// Any other non-success status code. + case other + } + + /// A best-effort representation of an error payload returned by an API. + /// + /// This type is intentionally minimal and is populated from a variety of possible + /// response body formats (typed JSON, loose JSON with a `"message"` field, or plain text). + public struct ResponseBody: Swift.Codable, Swift.Sendable { + public let code: Swift.Int + public let type: Swift.String? + public let message: Swift.String? + + /// Attempts to decode an `HTTPError.ResponseBody` from the given HTTP response payload. + public static func decode( + statusCode: Swift.Int, + data: Foundation.Data, + using jsonDecoder: Foundation.JSONDecoder + ) -> ResponseBody? { + // Try to parse as a strongly-typed error response first + if let errorResponse = try? jsonDecoder.decode(ResponseBody.self, from: data) { + return errorResponse + } + + // Try to parse as a simple JSON object with a "message" field + if let json = try? Foundation.JSONSerialization.jsonObject(with: data) + as? [Swift.String: Any], + let message = json["message"] as? Swift.String + { + return ResponseBody(code: statusCode, message: message) + } + + // Try to parse as plain text + if let errorMessage: String = Swift.String(data: data, encoding: .utf8), + !errorMessage.isEmpty + { + return ResponseBody(code: statusCode, message: errorMessage) + } + + return nil + } + + public init(code: Swift.Int, type: Swift.String? = nil, message: Swift.String? = nil) { + self.code = code + self.type = type + self.message = message + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Public/Indirect.swift b/seed/swift-sdk/allof/Sources/Public/Indirect.swift new file mode 100644 index 000000000000..ec6aa924fc8c --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/Indirect.swift @@ -0,0 +1,27 @@ +public final class Indirect: Codable, @unchecked Sendable { + public let value: T + + public init(_ value: T) { + self.value = value + } + + public convenience init(from decoder: Decoder) throws { + self.init(try T(from: decoder)) + } + + public func encode(to encoder: Encoder) throws { + try value.encode(to: encoder) + } +} + +extension Indirect: Equatable where T: Equatable { + public static func == (lhs: Indirect, rhs: Indirect) -> Bool { + lhs.value == rhs.value + } +} + +extension Indirect: Hashable where T: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + } +} diff --git a/seed/swift-sdk/allof/Sources/Public/JSONValue.swift b/seed/swift-sdk/allof/Sources/Public/JSONValue.swift new file mode 100644 index 000000000000..f8c56db4debf --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/JSONValue.swift @@ -0,0 +1,136 @@ +import Foundation + +/// A type that can represent any JSON value. +public enum JSONValue: Swift.Codable, Swift.Hashable, Swift.Sendable { + case string(Swift.String) + case number(Swift.Double) + case bool(Swift.Bool) + case null + case array([JSONValue]) + case object([Swift.String: JSONValue]) + + public init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self = .null + } else if let bool = try? container.decode(Swift.Bool.self) { + self = .bool(bool) + } else if let int = try? container.decode(Swift.Int.self) { + self = .number(Swift.Double(int)) + } else if let double = try? container.decode(Swift.Double.self) { + self = .number(double) + } else if let string = try? container.decode(Swift.String.self) { + self = .string(string) + } else if let array = try? container.decode([JSONValue].self) { + self = .array(array) + } else if let object = try? container.decode([Swift.String: JSONValue].self) { + self = .object(object) + } else { + throw Swift.DecodingError.dataCorrupted( + Swift.DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unable to decode JSONValue" + ) + ) + } + } + + public func encode(to encoder: Swift.Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .string(let string): + try container.encode(string) + case .number(let number): + try container.encode(number) + case .bool(let bool): + try container.encode(bool) + case .null: + try container.encodeNil() + case .array(let array): + try container.encode(array) + case .object(let object): + try container.encode(object) + } + } +} + +// MARK: - Convenience initializers +extension JSONValue { + public init(_ value: String) { + self = .string(value) + } + + public init(_ value: Int) { + self = .number(Double(value)) + } + + public init(_ value: Double) { + self = .number(value) + } + + public init(_ value: Bool) { + self = .bool(value) + } + + public init(_ value: [JSONValue]) { + self = .array(value) + } + + public init(_ value: [String: JSONValue]) { + self = .object(value) + } +} + +// MARK: - Value extraction +extension JSONValue { + public var stringValue: String? { + if case .string(let value) = self { + return value + } + return nil + } + + public var numberValue: Double? { + if case .number(let value) = self { + return value + } + return nil + } + + public var intValue: Int? { + if case .number(let value) = self { + return Int(value) + } + return nil + } + + public var boolValue: Bool? { + if case .bool(let value) = self { + return value + } + return nil + } + + public var arrayValue: [JSONValue]? { + if case .array(let value) = self { + return value + } + return nil + } + + public var objectValue: [String: JSONValue]? { + if case .object(let value) = self { + return value + } + return nil + } + + public var isNull: Bool { + if case .null = self { + return true + } + return false + } +} diff --git a/seed/swift-sdk/allof/Sources/Public/Networking.swift b/seed/swift-sdk/allof/Sources/Public/Networking.swift new file mode 100644 index 000000000000..ad54acdcf5fb --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/Networking.swift @@ -0,0 +1,21 @@ +import Foundation + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public enum Networking { + #if canImport(FoundationNetworking) + public typealias URLSession = FoundationNetworking.URLSession + public typealias URLSessionConfiguration = FoundationNetworking.URLSessionConfiguration + public typealias URLProtocol = FoundationNetworking.URLProtocol + public typealias URLRequest = FoundationNetworking.URLRequest + public typealias HTTPURLResponse = FoundationNetworking.HTTPURLResponse + #else + public typealias URLSession = Foundation.URLSession + public typealias URLSessionConfiguration = Foundation.URLSessionConfiguration + public typealias URLProtocol = Foundation.URLProtocol + public typealias URLRequest = Foundation.URLRequest + public typealias HTTPURLResponse = Foundation.HTTPURLResponse + #endif +} diff --git a/seed/swift-sdk/allof/Sources/Public/Nullable.swift b/seed/swift-sdk/allof/Sources/Public/Nullable.swift new file mode 100644 index 000000000000..81c92d7b82c6 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/Nullable.swift @@ -0,0 +1,59 @@ +import Foundation + +/// Represents a value that can be either a concrete value or explicit `null`, distinguishing between null and missing fields in JSON. +public enum Nullable: Swift.Codable, Swift.Hashable, Swift.Sendable +where Wrapped: Swift.Codable & Swift.Hashable & Swift.Sendable { + case value(Wrapped) + case null + + public init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self = .null + } else { + let wrappedValue = try container.decode(Wrapped.self) + self = .value(wrappedValue) + } + } + + public func encode(to encoder: Swift.Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .value(let wrapped): + try container.encode(wrapped) + case .null: + try container.encodeNil() + } + } + + /// Returns the wrapped value if present, otherwise nil + public var wrappedValue: Wrapped? { + switch self { + case .value(let wrapped): + return wrapped + case .null: + return nil + } + } + + /// Returns true if this contains an explicit null value + public var isNull: Swift.Bool { + switch self { + case .value(_): + return false + case .null: + return true + } + } + + /// Convenience initializer from optional value + public init(_ value: Wrapped?) { + if let value = value { + self = .value(value) + } else { + self = .null + } + } +} diff --git a/seed/swift-sdk/allof/Sources/Public/RequestOptions.swift b/seed/swift-sdk/allof/Sources/Public/RequestOptions.swift new file mode 100644 index 000000000000..3f210d8cf887 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Public/RequestOptions.swift @@ -0,0 +1,45 @@ +import Foundation + +/// Options for customizing an individual API request. +/// +/// Use this struct to override or supplement client-wide configuration for a single request. +public struct RequestOptions { + /// The API key to use for this request, overriding the client-wide API key if provided. + let apiKey: Swift.String? + + /// The token to use for this request, overriding the client-wide token if provided. + let token: Swift.String? + + /// The number of seconds to await an API call before timing out. If `nil`, uses the client or system default. + let timeout: Swift.Int? + + /// The number of times to retry a failed API call. If `nil`, uses the client or system default. + let maxRetries: Swift.Int? + + /// Additional HTTP headers to include with this request. These can override or supplement client-wide headers. + let additionalHeaders: [Swift.String: Swift.String]? + + /// Additional query parameters to include in the request URL. These are merged with any parameters generated from the request model. + let additionalQueryParameters: [Swift.String: Swift.String]? + + /// Additional body parameters to include in the request payload. These are merged with any parameters generated from the request model. + let additionalBodyParameters: [Swift.String: Swift.String]? + + public init( + apiKey: Swift.String? = nil, + token: Swift.String? = nil, + timeout: Swift.Int? = nil, + maxRetries: Swift.Int? = nil, + additionalHeaders: [Swift.String: Swift.String]? = nil, + additionalQueryParameters: [Swift.String: Swift.String]? = nil, + additionalBodyParameters: [Swift.String: Swift.String]? = nil + ) { + self.apiKey = apiKey + self.token = token + self.timeout = timeout + self.maxRetries = maxRetries + self.additionalHeaders = additionalHeaders + self.additionalQueryParameters = additionalQueryParameters + self.additionalBodyParameters = additionalBodyParameters + } +} diff --git a/seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift b/seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift new file mode 100644 index 000000000000..652324b903b1 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift @@ -0,0 +1,40 @@ +import Foundation + +extension Requests { + public struct RuleCreateRequest: Codable, Hashable, Sendable { + public let name: String + public let executionContext: RuleExecutionContext + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + name: String, + executionContext: RuleExecutionContext, + additionalProperties: [String: JSONValue] = .init() + ) { + self.name = name + self.executionContext = executionContext + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.executionContext = try container.decode(RuleExecutionContext.self, forKey: .executionContext) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.name, forKey: .name) + try container.encode(self.executionContext, forKey: .executionContext) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case name + case executionContext + } + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Requests/Requests.swift b/seed/swift-sdk/allof/Sources/Requests/Requests.swift new file mode 100644 index 000000000000..2b06437b8213 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Requests/Requests.swift @@ -0,0 +1,6 @@ +import Foundation + +/// Container for all inline request types used throughout the SDK. +/// +/// This enum serves as a namespace to organize request types that are defined inline within endpoint specifications. +public enum Requests {} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift b/seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift new file mode 100644 index 000000000000..e2d2dca164b6 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift @@ -0,0 +1,55 @@ +import Foundation + +/// Common audit metadata. +public struct AuditInfo: Codable, Hashable, Sendable { + /// The user who created this resource. + public let createdBy: String? + /// When this resource was created. + public let createdDateTime: Date? + /// The user who last modified this resource. + public let modifiedBy: String? + /// When this resource was last modified. + public let modifiedDateTime: Date? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + createdBy: String? = nil, + createdDateTime: Date? = nil, + modifiedBy: String? = nil, + modifiedDateTime: Date? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.createdBy = createdBy + self.createdDateTime = createdDateTime + self.modifiedBy = modifiedBy + self.modifiedDateTime = modifiedDateTime + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) + self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) + self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) + self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.createdBy, forKey: .createdBy) + try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) + try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) + try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case createdBy + case createdDateTime + case modifiedBy + case modifiedDateTime + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift b/seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift new file mode 100644 index 000000000000..826006803d13 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift @@ -0,0 +1,38 @@ +import Foundation + +public struct BaseOrg: Codable, Hashable, Sendable { + public let id: String + public let metadata: BaseOrgMetadata? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + metadata: BaseOrgMetadata? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.metadata = metadata + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.metadata = try container.decodeIfPresent(BaseOrgMetadata.self, forKey: .metadata) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.metadata, forKey: .metadata) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case metadata + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift b/seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift new file mode 100644 index 000000000000..8b76b582c6ff --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct BaseOrgMetadata: Codable, Hashable, Sendable { + /// Deployment region from BaseOrg. + public let region: String + /// Subscription tier. + public let tier: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + region: String, + tier: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.region = region + self.tier = tier + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.region = try container.decode(String.self, forKey: .region) + self.tier = try container.decodeIfPresent(String.self, forKey: .tier) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.region, forKey: .region) + try container.encodeIfPresent(self.tier, forKey: .tier) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case region + case tier + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift b/seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift new file mode 100644 index 000000000000..fe35e084034d --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift @@ -0,0 +1,53 @@ +import Foundation + +public struct CombinedEntity: Codable, Hashable, Sendable { + public let status: CombinedEntityStatus + /// Unique identifier. + public let id: String + /// Display name from Identifiable. + public let name: String? + /// A short summary. + public let summary: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + status: CombinedEntityStatus, + id: String, + name: String? = nil, + summary: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.status = status + self.id = id + self.name = name + self.summary = summary + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.status = try container.decode(CombinedEntityStatus.self, forKey: .status) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + self.summary = try container.decodeIfPresent(String.self, forKey: .summary) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.status, forKey: .status) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.name, forKey: .name) + try container.encodeIfPresent(self.summary, forKey: .summary) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case status + case id + case name + case summary + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift b/seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift new file mode 100644 index 000000000000..ad507eb4b537 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum CombinedEntityStatus: String, Codable, Hashable, CaseIterable, Sendable { + case active + case archived +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/Describable.swift b/seed/swift-sdk/allof/Sources/Schemas/Describable.swift new file mode 100644 index 000000000000..5f59f3f5a66b --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/Describable.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct Describable: Codable, Hashable, Sendable { + /// Display name from Describable. + public let name: String? + /// A short summary. + public let summary: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + name: String? = nil, + summary: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.name = name + self.summary = summary + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + self.summary = try container.decodeIfPresent(String.self, forKey: .summary) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.name, forKey: .name) + try container.encodeIfPresent(self.summary, forKey: .summary) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case name + case summary + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift b/seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift new file mode 100644 index 000000000000..412a6626a9c5 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift @@ -0,0 +1,32 @@ +import Foundation + +public struct DetailedOrg: Codable, Hashable, Sendable { + public let metadata: DetailedOrgMetadata? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + metadata: DetailedOrgMetadata? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.metadata = metadata + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.metadata = try container.decodeIfPresent(DetailedOrgMetadata.self, forKey: .metadata) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.metadata, forKey: .metadata) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case metadata + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift b/seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift new file mode 100644 index 000000000000..bf4903b3d0ea --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct DetailedOrgMetadata: Codable, Hashable, Sendable { + /// Deployment region from DetailedOrg. + public let region: String + /// Custom domain name. + public let domain: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + region: String, + domain: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.region = region + self.domain = domain + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.region = try container.decode(String.self, forKey: .region) + self.domain = try container.decodeIfPresent(String.self, forKey: .domain) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.region, forKey: .region) + try container.encodeIfPresent(self.domain, forKey: .domain) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case region + case domain + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift b/seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift new file mode 100644 index 000000000000..c0c54d0a6b65 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct Identifiable: Codable, Hashable, Sendable { + /// Unique identifier. + public let id: String + /// Display name from Identifiable. + public let name: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + name: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.name = name + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.name, forKey: .name) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case name + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/Organization.swift b/seed/swift-sdk/allof/Sources/Schemas/Organization.swift new file mode 100644 index 000000000000..fe270f91811e --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/Organization.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct Organization: Codable, Hashable, Sendable { + public let name: String + public let id: String + public let metadata: BaseOrgMetadata? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + name: String, + id: String, + metadata: BaseOrgMetadata? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.name = name + self.id = id + self.metadata = metadata + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.id = try container.decode(String.self, forKey: .id) + self.metadata = try container.decodeIfPresent(BaseOrgMetadata.self, forKey: .metadata) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.name, forKey: .name) + try container.encode(self.id, forKey: .id) + try container.encodeIfPresent(self.metadata, forKey: .metadata) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case name + case id + case metadata + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift b/seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift new file mode 100644 index 000000000000..60920a570d88 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct PaginatedResult: Codable, Hashable, Sendable { + public let paging: PagingCursors + /// Current page of results from the requested resource. + public let results: [JSONValue] + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + paging: PagingCursors, + results: [JSONValue], + additionalProperties: [String: JSONValue] = .init() + ) { + self.paging = paging + self.results = results + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.paging = try container.decode(PagingCursors.self, forKey: .paging) + self.results = try container.decode([JSONValue].self, forKey: .results) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.paging, forKey: .paging) + try container.encode(self.results, forKey: .results) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case paging + case results + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift b/seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift new file mode 100644 index 000000000000..a6c6369f7a4e --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct PagingCursors: Codable, Hashable, Sendable { + /// Cursor for the next page of results. + public let next: String + /// Cursor for the previous page of results. + public let previous: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + next: String, + previous: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.next = next + self.previous = previous + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.next = try container.decode(String.self, forKey: .next) + self.previous = try container.decodeIfPresent(String.self, forKey: .previous) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.next, forKey: .next) + try container.encodeIfPresent(self.previous, forKey: .previous) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case next + case previous + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift new file mode 100644 index 000000000000..b3825cc062d2 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift @@ -0,0 +1,8 @@ +import Foundation + +/// Execution environment for a rule. +public enum RuleExecutionContext: String, Codable, Hashable, CaseIterable, Sendable { + case prod + case staging + case dev +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift new file mode 100644 index 000000000000..23834241d4e1 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift @@ -0,0 +1,78 @@ +import Foundation + +public struct RuleResponse: Codable, Hashable, Sendable { + /// The user who created this resource. + public let createdBy: String? + /// When this resource was created. + public let createdDateTime: Date? + /// The user who last modified this resource. + public let modifiedBy: String? + /// When this resource was last modified. + public let modifiedDateTime: Date? + public let id: String + public let name: String + public let status: RuleResponseStatus + public let executionContext: RuleExecutionContext? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + createdBy: String? = nil, + createdDateTime: Date? = nil, + modifiedBy: String? = nil, + modifiedDateTime: Date? = nil, + id: String, + name: String, + status: RuleResponseStatus, + executionContext: RuleExecutionContext? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.createdBy = createdBy + self.createdDateTime = createdDateTime + self.modifiedBy = modifiedBy + self.modifiedDateTime = modifiedDateTime + self.id = id + self.name = name + self.status = status + self.executionContext = executionContext + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) + self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) + self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) + self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decode(String.self, forKey: .name) + self.status = try container.decode(RuleResponseStatus.self, forKey: .status) + self.executionContext = try container.decodeIfPresent(RuleExecutionContext.self, forKey: .executionContext) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.createdBy, forKey: .createdBy) + try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) + try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) + try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) + try container.encode(self.id, forKey: .id) + try container.encode(self.name, forKey: .name) + try container.encode(self.status, forKey: .status) + try container.encodeIfPresent(self.executionContext, forKey: .executionContext) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case createdBy + case createdDateTime + case modifiedBy + case modifiedDateTime + case id + case name + case status + case executionContext + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift new file mode 100644 index 000000000000..1f8cbe93a34c --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum RuleResponseStatus: String, Codable, Hashable, CaseIterable, Sendable { + case active + case inactive + case draft +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleType.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleType.swift new file mode 100644 index 000000000000..89987c5def90 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/RuleType.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct RuleType: Codable, Hashable, Sendable { + public let id: String + public let name: String + public let description: String? + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + name: String, + description: String? = nil, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.name = name + self.description = description + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decode(String.self, forKey: .name) + self.description = try container.decodeIfPresent(String.self, forKey: .description) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encode(self.name, forKey: .name) + try container.encodeIfPresent(self.description, forKey: .description) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case name + case description + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift new file mode 100644 index 000000000000..47a4aae72f09 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct RuleTypeSearchResponse: Codable, Hashable, Sendable { + /// Current page of results from the requested resource. + public let results: [RuleType]? + public let paging: PagingCursors + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + results: [RuleType]? = nil, + paging: PagingCursors, + additionalProperties: [String: JSONValue] = .init() + ) { + self.results = results + self.paging = paging + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.results = try container.decodeIfPresent([RuleType].self, forKey: .results) + self.paging = try container.decode(PagingCursors.self, forKey: .paging) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.results, forKey: .results) + try container.encode(self.paging, forKey: .paging) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case results + case paging + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/User.swift b/seed/swift-sdk/allof/Sources/Schemas/User.swift new file mode 100644 index 000000000000..3e9a5ec45166 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/User.swift @@ -0,0 +1,38 @@ +import Foundation + +public struct User: Codable, Hashable, Sendable { + public let id: String + public let email: String + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + id: String, + email: String, + additionalProperties: [String: JSONValue] = .init() + ) { + self.id = id + self.email = email + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.email = try container.decode(String.self, forKey: .email) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encode(self.id, forKey: .id) + try container.encode(self.email, forKey: .email) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case email + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift b/seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift new file mode 100644 index 000000000000..079620fbcf3f --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct UserSearchResponse: Codable, Hashable, Sendable { + /// Current page of results from the requested resource. + public let results: [User]? + public let paging: PagingCursors + /// Additional properties that are not explicitly defined in the schema + public let additionalProperties: [String: JSONValue] + + public init( + results: [User]? = nil, + paging: PagingCursors, + additionalProperties: [String: JSONValue] = .init() + ) { + self.results = results + self.paging = paging + self.additionalProperties = additionalProperties + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.results = try container.decodeIfPresent([User].self, forKey: .results) + self.paging = try container.decode(PagingCursors.self, forKey: .paging) + self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) + } + + public func encode(to encoder: Encoder) throws -> Void { + var container = encoder.container(keyedBy: CodingKeys.self) + try encoder.encodeAdditionalProperties(self.additionalProperties) + try container.encodeIfPresent(self.results, forKey: .results) + try container.encode(self.paging, forKey: .paging) + } + + /// Keys for encoding/decoding struct properties. + enum CodingKeys: String, CodingKey, CaseIterable { + case results + case paging + } +} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Version.swift b/seed/swift-sdk/allof/Sources/Version.swift new file mode 100644 index 000000000000..b17ac8c77973 --- /dev/null +++ b/seed/swift-sdk/allof/Sources/Version.swift @@ -0,0 +1 @@ +public let sdkVersion = "0.0.1" diff --git a/seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift b/seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift new file mode 100644 index 000000000000..63de33596c7b --- /dev/null +++ b/seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift @@ -0,0 +1,222 @@ +import Api +import Foundation +import Testing + +@Suite("Client Error & HTTP Error Tests") struct ClientErrorTests { + // MARK: - 4xx client errors + + @Test func testClientErrorFor400BadRequest() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 400, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Bad request"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 400) + try #require(httpError.kind == .client) + try #require(httpError.body?.message == "Bad request") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorFor404NotFound() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 404, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Not found"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 404) + try #require(httpError.kind == .notFound) + try #require(httpError.body?.message == "Not found") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorFor422ValidationError() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 422, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Validation failed"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 422) + try #require(httpError.kind == .validation) + try #require(httpError.body?.message == "Validation failed") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + // MARK: - 5xx server errors + + @Test func testClientErrorFor500ServerError() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 500, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Internal error"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 500) + try #require(httpError.kind == .server) + try #require(httpError.body?.message == "Internal error") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorFor503ServiceUnavailable() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 503, + headers: ["Content-Type": "application/json"], + body: Data(#"{"message":"Unavailable"}"#.utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 503) + try #require(httpError.kind == .serviceUnavailable) + try #require(httpError.body?.message == "Unavailable") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + // MARK: - 3xx redirect & plain-text bodies + + @Test func testClientErrorFor302RedirectNoBody() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 302, + headers: ["Location": "https://example.com"], + body: Data() + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 302) + try #require(httpError.kind == .redirect) + try #require(httpError.body == nil) + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } + + @Test func testClientErrorPlainTextBodyIsDecoded() async throws { + let stub = HTTPStub() + stub.setResponse( + statusCode: 500, + headers: ["Content-Type": "text/plain"], + body: Data("Plain text error".utf8) + ) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch let error as ApiError { + guard case .httpError(let httpError) = error else { + Issue.record("Expected ApiError.httpError, got \(error)") + return + } + try #require(httpError.statusCode == 500) + try #require(httpError.kind == .server) + try #require(httpError.body?.message == "Plain text error") + } catch { + Issue.record("Expected ApiError, got \(error)") + } + } +} + diff --git a/seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift b/seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift new file mode 100644 index 000000000000..8ebbf4c73e22 --- /dev/null +++ b/seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift @@ -0,0 +1,355 @@ +import Api +import Foundation +import Testing + +@Suite("Client Retry Tests") struct ClientRetryTests { + @Test func testRetryOn408RequestTimeout() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 3) + } + + @Test func testRetryOn429TooManyRequests() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 3) + } + + @Test func testRetryOn500InternalServerError() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 3) + } + + @Test func testRetryOn503ServiceUnavailable() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 503, headers: ["Content-Type": "application/json"], body: Data()), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 2) + } + + @Test func testNoRetryOn400BadRequest() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 400, headers: ["Content-Type": "application/json"], + body: Data("{\"errorName\":\"BadRequest\"}".utf8) + ) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 1) + } + } + + @Test func testNoRetryOn404NotFound() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 404, headers: ["Content-Type": "application/json"], + body: Data("{\"errorName\":\"NotFound\"}".utf8) + ) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 1) + } + } + + @Test func testMaxRetriesExhausted() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 3) + } + } + + @Test func testRetryAfterHeaderWithSeconds() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 429, headers: ["Content-Type": "application/json", "Retry-After": "1"], + body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + let startTime = Date() + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + let elapsed = Date().timeIntervalSince(startTime) + + try #require(stub.getRequestCount() == 2) + try #require(elapsed >= 1.0) + } + + @Test func testRetryAfterHeaderWithHTTPDate() async throws { + let stub = HTTPStub() + let futureEpoch = ceil(Date().timeIntervalSince1970) + 1.0 + let futureDate = Date(timeIntervalSince1970: futureEpoch) + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(abbreviation: "GMT") + formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" + let httpDate = formatter.string(from: futureDate) + + stub.setResponseSequence([ + ( + statusCode: 429, + headers: ["Content-Type": "application/json", "Retry-After": httpDate], body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + let startTime = Date() + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + let elapsed = Date().timeIntervalSince(startTime) + + try #require(stub.getRequestCount() == 2) + try #require(elapsed >= 0.9) + } + + @Test func testXRateLimitResetHeader() async throws { + let stub = HTTPStub() + let futureTimestamp = Int(ceil(Date().timeIntervalSince1970)) + 1 + + stub.setResponseSequence([ + ( + statusCode: 429, + headers: [ + "Content-Type": "application/json", "X-RateLimit-Reset": "\(futureTimestamp)", + ], body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + let startTime = Date() + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + let elapsed = Date().timeIntervalSince(startTime) + + try #require(stub.getRequestCount() == 2) + try #require(elapsed >= 0.9) + } + + @Test func testEndpointLevelMaxRetriesOverride() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 500, + headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() + ), + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ), + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 5, additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 6) + } + + @Test func testEndpointLevelMaxRetriesZero() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 0, additionalHeaders: stub.headers)) + + Issue.record("Expected error to be thrown") + } catch { + try #require(stub.getRequestCount() == 1) + } + } + + @Test func testSuccessOnFirstAttempt() async throws { + let stub = HTTPStub() + stub.setResponseSequence([ + ( + statusCode: 200, headers: ["Content-Type": "application/json"], + body: Data("true".utf8) + ) + ]) + + let client = ApiClient( + baseURL: "https://api.fern.com", + urlSession: stub.urlSession + ) + + do { + _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) + + } catch { + } + try #require(stub.getRequestCount() == 1) + } +} diff --git a/seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift b/seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift new file mode 100644 index 000000000000..82a3aa80ad22 --- /dev/null +++ b/seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift @@ -0,0 +1,317 @@ +import Api +import Foundation + +final class HTTPStub { + private static func buildURLSession(stubId: String, operationQueue: OperationQueue) + -> Networking.URLSession + { + let config = buildURLSessionConfiguration(stubId: stubId) + if let uuid = UUID(uuidString: stubId) { + StubURLProtocol.register(queue: operationQueue, id: uuid) + } + return Networking.URLSession(configuration: config, delegate: nil, delegateQueue: operationQueue) + } + + private static func buildURLSessionConfiguration(stubId: String) -> Networking.URLSessionConfiguration { + let config = Networking.URLSessionConfiguration.ephemeral + config.protocolClasses = [StubURLProtocol.self] + config.requestCachePolicy = .reloadIgnoringLocalCacheData + config.urlCache = nil + config.httpAdditionalHeaders = ["Stub-ID": stubId] + return config + } + + private static func buildOperationQueue() -> OperationQueue { + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + queue.qualityOfService = .userInitiated + return queue + } + + private let session: Networking.URLSession + private let delegateQueue: OperationQueue + private let identifier: UUID + + init() { + self.identifier = UUID() + self.delegateQueue = Self.buildOperationQueue() + self.session = Self.buildURLSession( + stubId: identifier.uuidString, operationQueue: delegateQueue) + #if !canImport(Darwin) + // On Linux, URLProtocol doesn't get the additional headers at canInit time. + // Track the active stub id so the protocol can resolve responses without headers. + StubURLProtocol.setActiveStubId(identifier) + #endif + } + + var urlSession: Networking.URLSession { + session + } + + var headers: [String: String] { + ["Stub-ID": identifier.uuidString] + } + + func setResponse( + statusCode: Int = 200, + headers: [String: String] = ["Content-Type": "application/json"], + body: Data + ) { + StubURLProtocol.configure( + id: identifier, + statusCode: statusCode, + headers: headers, + body: body + ) + } + + func setResponseSequence( + _ responses: [(statusCode: Int, headers: [String: String], body: Data)] + ) { + StubURLProtocol.configureSequence(id: identifier, responses: responses) + } + + func takeLastRequest() -> Networking.URLRequest? { + StubURLProtocol.takeLastRequest(for: identifier) + } + + func getRequestCount() -> Int { + StubURLProtocol.getRequestCount(for: identifier) + } + + deinit { + StubURLProtocol.reset(id: identifier) + StubURLProtocol.deregister(queue: delegateQueue) + #if !canImport(Darwin) + StubURLProtocol.clearActiveStubId(identifier) + #endif + } +} + +private final class StubURLProtocol: Networking.URLProtocol { + struct Response { + let statusCode: Int + let headers: [String: String] + let body: Data + var lastRequest: Networking.URLRequest? + } + + struct ResponseSequence { + var responses: [Response] + var currentIndex: Int = 0 + var lastRequest: Networking.URLRequest? + + mutating func nextResponse() -> Response? { + guard currentIndex < responses.count else { return nil } + let response = responses[currentIndex] + currentIndex += 1 + return response + } + } + + private static var responses: [UUID: Response] = [:] + private static var responseSequences: [UUID: ResponseSequence] = [:] + private static let lock = NSLock() + private static var queueIdMap: [ObjectIdentifier: UUID] = [:] + #if !canImport(Darwin) + // Fallback for Linux where request headers may not be visible in canInit. + private static var activeStubIds: [UUID] = [] + #endif + + static func configure( + id: UUID, + statusCode: Int, + headers: [String: String], + body: Data + ) { + lock.lock() + responses[id] = Response( + statusCode: statusCode, headers: headers, body: body, lastRequest: nil) + responseSequences[id] = nil + lock.unlock() + } + + static func configureSequence( + id: UUID, + responses: [(statusCode: Int, headers: [String: String], body: Data)] + ) { + lock.lock() + let responseList = responses.map { + Response( + statusCode: $0.statusCode, headers: $0.headers, body: $0.body, lastRequest: nil) + } + responseSequences[id] = ResponseSequence(responses: responseList) + StubURLProtocol.responses[id] = nil + lock.unlock() + } + + static func takeLastRequest(for id: UUID) -> Networking.URLRequest? { + lock.lock() + defer { lock.unlock() } + + if var sequence = responseSequences[id], let request = sequence.lastRequest { + sequence.lastRequest = nil + responseSequences[id] = sequence + return request + } + + guard var response = responses[id], let request = response.lastRequest else { + return nil + } + response.lastRequest = nil + responses[id] = response + return request + } + + static func getRequestCount(for id: UUID) -> Int { + lock.lock() + defer { lock.unlock() } + + if let sequence = responseSequences[id] { + return sequence.currentIndex + } + + return responses[id]?.lastRequest != nil ? 1 : 0 + } + + static func reset(id: UUID) { + lock.lock() + responses[id] = nil + responseSequences[id] = nil + lock.unlock() + } + + static func register(queue: OperationQueue, id: UUID) { + lock.lock() + queueIdMap[ObjectIdentifier(queue)] = id + lock.unlock() + } + + static func deregister(queue: OperationQueue) { + lock.lock() + queueIdMap[ObjectIdentifier(queue)] = nil + lock.unlock() + } + + #if !canImport(Darwin) + static func setActiveStubId(_ id: UUID) { + lock.lock() + activeStubIds.append(id) + lock.unlock() + } + + static func clearActiveStubId(_ id: UUID) { + lock.lock() + if let idx = activeStubIds.lastIndex(of: id) { + activeStubIds.remove(at: idx) + } + lock.unlock() + } + #endif + + override class func canInit(with request: Networking.URLRequest) -> Bool { + #if canImport(Darwin) + return request.value(forHTTPHeaderField: "Stub-ID") != nil + #else + // On Linux, intercept all requests created by the session that installed this protocol. + // We'll resolve the correct stub id during startLoading using the active id stack. + return true + #endif + } + + override class func canonicalRequest(for request: Networking.URLRequest) -> Networking.URLRequest { + request + } + + override func startLoading() { + guard let client else { return } + #if canImport(Darwin) + guard let idValue = request.value(forHTTPHeaderField: "Stub-ID"), + let id = UUID(uuidString: idValue) + else { + client.urlProtocol(self, didFailWithError: URLError(.cannotFindHost)) + return + } + #else + // Prefer the Stub-ID header if available on Linux; fall back to the active id stack. + var resolvedId: UUID? + if let idValue = request.value(forHTTPHeaderField: "Stub-ID"), + let headerId = UUID(uuidString: idValue) + { + resolvedId = headerId + } else { + if let currentQueue = OperationQueue.current { + StubURLProtocol.lock.lock() + if let mapped = StubURLProtocol.queueIdMap[ObjectIdentifier(currentQueue)] { + resolvedId = mapped + } + StubURLProtocol.lock.unlock() + } + StubURLProtocol.lock.lock() + resolvedId = StubURLProtocol.activeStubIds.last + StubURLProtocol.lock.unlock() + } + guard let id = resolvedId else { + client.urlProtocol(self, didFailWithError: URLError(.unknown)) + return + } + #endif + + StubURLProtocol.lock.lock() + + if var sequence = StubURLProtocol.responseSequences[id] { + guard let response = sequence.nextResponse() else { + StubURLProtocol.lock.unlock() + client.urlProtocol(self, didFailWithError: URLError(.unknown)) + return + } + sequence.lastRequest = request + StubURLProtocol.responseSequences[id] = sequence + StubURLProtocol.lock.unlock() + + guard let url = request.url else { + client.urlProtocol(self, didFailWithError: URLError(.badURL)) + return + } + + let httpResponse = Networking.HTTPURLResponse( + url: url, + statusCode: response.statusCode, + httpVersion: "HTTP/1.1", + headerFields: response.headers + )! + + client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) + client.urlProtocol(self, didLoad: response.body) + client.urlProtocolDidFinishLoading(self) + return + } + + guard var response = StubURLProtocol.responses[id] else { + StubURLProtocol.lock.unlock() + client.urlProtocol(self, didFailWithError: URLError(.unknown)) + return + } + response.lastRequest = request + StubURLProtocol.responses[id] = response + StubURLProtocol.lock.unlock() + + guard let url = request.url else { + client.urlProtocol(self, didFailWithError: URLError(.badURL)) + return + } + + let httpResponse = Networking.HTTPURLResponse( + url: url, + statusCode: response.statusCode, + httpVersion: "HTTP/1.1", + headerFields: response.headers + )! + + client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) + client.urlProtocol(self, didLoad: response.body) + client.urlProtocolDidFinishLoading(self) + } + + override func stopLoading() {} +} diff --git a/seed/swift-sdk/allof/reference.md b/seed/swift-sdk/allof/reference.md new file mode 100644 index 000000000000..75ea9a025cca --- /dev/null +++ b/seed/swift-sdk/allof/reference.md @@ -0,0 +1,265 @@ +# Reference +
client.searchRuleTypes(query: String?, requestOptions: RequestOptions?) -> RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.searchRuleTypes() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `String?` + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions?) -> RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.createRule(request: .init( + name: "name", + executionContext: .prod + )) +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `Requests.RuleCreateRequest` + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.listUsers(requestOptions: RequestOptions?) -> UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.listUsers() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.getEntity(requestOptions: RequestOptions?) -> CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.getEntity() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ +
client.getOrganization(requestOptions: RequestOptions?) -> Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```swift +import Foundation +import Api + +private func main() async throws { + let client = ApiClient() + + _ = try await client.getOrganization() +} + +try await main() +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/swift-sdk/allof/snippet.json b/seed/swift-sdk/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/ts-sdk/allof-inline/.fern/metadata.json b/seed/ts-sdk/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..9da357e1fedb --- /dev/null +++ b/seed/ts-sdk/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-typescript-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} diff --git a/seed/ts-sdk/allof-inline/.github/workflows/ci.yml b/seed/ts-sdk/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..93fba226cb67 --- /dev/null +++ b/seed/ts-sdk/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Set up node + uses: actions/setup-node@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Compile + run: pnpm build + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Set up node + uses: actions/setup-node@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Test + run: pnpm test diff --git a/seed/ts-sdk/allof-inline/.gitignore b/seed/ts-sdk/allof-inline/.gitignore new file mode 100644 index 000000000000..72271e049c02 --- /dev/null +++ b/seed/ts-sdk/allof-inline/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store +/dist \ No newline at end of file diff --git a/seed/ts-sdk/allof-inline/CONTRIBUTING.md b/seed/ts-sdk/allof-inline/CONTRIBUTING.md new file mode 100644 index 000000000000..fe5bc2f77e0b --- /dev/null +++ b/seed/ts-sdk/allof-inline/CONTRIBUTING.md @@ -0,0 +1,133 @@ +# Contributing + +Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project. + +## Getting Started + +### Prerequisites + +- Node.js 20 or higher +- pnpm package manager + +### Installation + +Install the project dependencies: + +```bash +pnpm install +``` + +### Building + +Build the project: + +```bash +pnpm build +``` + +### Testing + +Run the test suite: + +```bash +pnpm test +``` + +Run specific test types: +- `pnpm test:unit` - Run unit tests +- `pnpm test:wire` - Run wire/integration tests + +### Linting and Formatting + +Check code style: + +```bash +pnpm run lint +pnpm run format:check +``` + +Fix code style issues: + +```bash +pnpm run lint:fix +pnpm run format:fix +``` + +Or use the combined check command: + +```bash +pnpm run check:fix +``` + +## About Generated Code + +**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated. + +### Generated Files + +The following directories contain generated code: +- `src/api/` - API client classes and types +- `src/serialization/` - Serialization/deserialization logic +- Most TypeScript files in `src/` + +### How to Customize + +If you need to customize the SDK, you have two options: + +#### Option 1: Use `.fernignore` + +For custom code that should persist across SDK regenerations: + +1. Create a `.fernignore` file in the project root +2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax) +3. Add your custom code to those files + +Files listed in `.fernignore` will not be overwritten when the SDK is regenerated. + +For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code). + +#### Option 2: Contribute to the Generator + +If you want to change how code is generated for all users of this SDK: + +1. The TypeScript SDK generator lives in the [Fern repository](https://github.com/fern-api/fern) +2. Generator code is located at `generators/typescript/sdk/` +3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md) +4. Submit a pull request with your changes to the generator + +This approach is best for: +- Bug fixes in generated code +- New features that would benefit all users +- Improvements to code generation patterns + +## Making Changes + +### Workflow + +1. Create a new branch for your changes +2. Make your modifications +3. Run tests to ensure nothing breaks: `pnpm test` +4. Run linting and formatting: `pnpm run check:fix` +5. Build the project: `pnpm build` +6. Commit your changes with a clear commit message +7. Push your branch and create a pull request + +### Commit Messages + +Write clear, descriptive commit messages that explain what changed and why. + +### Code Style + +This project uses automated code formatting and linting. Run `pnpm run check:fix` before committing to ensure your code meets the project's style guidelines. + +## Questions or Issues? + +If you have questions or run into issues: + +1. Check the [Fern documentation](https://buildwithfern.com) +2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues) +3. Open a new issue if your question hasn't been addressed + +## License + +By contributing to this project, you agree that your contributions will be licensed under the same license as the project. diff --git a/seed/ts-sdk/allof-inline/README.md b/seed/ts-sdk/allof-inline/README.md new file mode 100644 index 000000000000..1744d31370b2 --- /dev/null +++ b/seed/ts-sdk/allof-inline/README.md @@ -0,0 +1,292 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) +[![npm shield](https://img.shields.io/npm/v/@fern/allof-inline)](https://www.npmjs.com/package/@fern/allof-inline) + +The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Request and Response Types](#request-and-response-types) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Aborting Requests](#aborting-requests) + - [Access Raw Response Data](#access-raw-response-data) + - [Logging](#logging) + - [Custom Fetch](#custom-fetch) + - [Runtime Compatibility](#runtime-compatibility) +- [Contributing](#contributing) + +## Installation + +```sh +npm i -s @fern/allof-inline +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedApiClient } from "@fern/allof-inline"; + +const client = new SeedApiClient; +await client.createRule({ + name: "name", + executionContext: "prod" +}); +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```typescript +import { SeedApiClient, SeedApiEnvironment } from "@fern/allof-inline"; + +const client = new SeedApiClient({ + environment: SeedApiEnvironment.Default, +}); +``` + +## Request and Response Types + +The SDK exports all request and response types as TypeScript interfaces. Simply import them with the +following namespace: + +```typescript +import { SeedApi } from "@fern/allof-inline"; + +const request: SeedApi.SearchRuleTypesRequest = { + ... +}; +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedApiError } from "@fern/allof-inline"; + +try { + await client.createRule(...); +} catch (err) { + if (err instanceof SeedApiError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + console.log(err.rawResponse); + } +} +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `headers` request option. + +```typescript +import { SeedApiClient } from "@fern/allof-inline"; + +const client = new SeedApiClient({ + ... + headers: { + 'X-Custom-Header': 'custom value' + } +}); + +const response = await client.createRule(..., { + headers: { + 'X-Custom-Header': 'custom value' + } +}); +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. + +```typescript +const response = await client.createRule(..., { + queryParams: { + 'customQueryParamKey': 'custom query param value' + } +}); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.createRule(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.createRule(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.createRule(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. +The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. + +```typescript +const { data, rawResponse } = await client.createRule(...).withRawResponse(); + +console.log(data); +console.log(rawResponse.headers['X-My-Header']); +``` + +### Logging + +The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. + +```typescript +import { SeedApiClient, logging } from "@fern/allof-inline"; + +const client = new SeedApiClient({ + ... + logging: { + level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info + logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger + silent: false, // defaults to true, set to false to enable logging + } +}); +``` +The `logging` object can have the following properties: +- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. +- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. +- `silent`: Whether to silence the logger. Defaults to `true`. + +The `level` property can be one of the following values: +- `logging.LogLevel.Debug` +- `logging.LogLevel.Info` +- `logging.LogLevel.Warn` +- `logging.LogLevel.Error` + +To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. + +
+Custom logger examples + +Here's an example using the popular `winston` logging library. +```ts +import winston from 'winston'; + +const winstonLogger = winston.createLogger({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => winstonLogger.debug(msg, ...args), + info: (msg, ...args) => winstonLogger.info(msg, ...args), + warn: (msg, ...args) => winstonLogger.warn(msg, ...args), + error: (msg, ...args) => winstonLogger.error(msg, ...args), +}; +``` + +Here's an example using the popular `pino` logging library. + +```ts +import pino from 'pino'; + +const pinoLogger = pino({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => pinoLogger.debug(args, msg), + info: (msg, ...args) => pinoLogger.info(args, msg), + warn: (msg, ...args) => pinoLogger.warn(args, msg), + error: (msg, ...args) => pinoLogger.error(args, msg), +}; +``` +
+ + +### Custom Fetch + +The SDK provides a low-level `fetch` method for making custom HTTP requests while still +benefiting from SDK-level configuration like authentication, retries, timeouts, and logging. +This is useful for calling API endpoints not yet supported in the SDK. + +```typescript +const response = await client.fetch("/v1/custom/endpoint", { + method: "GET", +}, { + timeoutInSeconds: 30, + maxRetries: 3, + headers: { + "X-Custom-Header": "custom-value", + }, +}); + +const data = await response.json(); +``` + +### Runtime Compatibility + + +The SDK works in the following runtimes: + + + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/allof-inline/biome.json b/seed/ts-sdk/allof-inline/biome.json new file mode 100644 index 000000000000..6b89164f9f99 --- /dev/null +++ b/seed/ts-sdk/allof-inline/biome.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", + "root": true, + "vcs": { + "enabled": false + }, + "files": { + "ignoreUnknown": true, + "includes": [ + "**", + "!!dist", + "!!**/dist", + "!!lib", + "!!**/lib", + "!!_tmp_*", + "!!**/_tmp_*", + "!!*.tmp", + "!!**/*.tmp", + "!!.tmp/", + "!!**/.tmp/", + "!!*.log", + "!!**/*.log", + "!!**/.DS_Store", + "!!**/Thumbs.db" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 120 + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "rules": { + "style": { + "useNodejsImportProtocol": "off" + }, + "suspicious": { + "noAssignInExpressions": "warn", + "noUselessEscapeInString": { + "level": "warn", + "fix": "none", + "options": {} + }, + "noThenProperty": "warn", + "useIterableCallbackReturn": "warn", + "noShadowRestrictedNames": "warn", + "noTsIgnore": { + "level": "warn", + "fix": "none", + "options": {} + }, + "noConfusingVoidType": { + "level": "warn", + "fix": "none", + "options": {} + } + } + } + } +} diff --git a/seed/ts-sdk/allof-inline/package.json b/seed/ts-sdk/allof-inline/package.json new file mode 100644 index 000000000000..ade4e972fce8 --- /dev/null +++ b/seed/ts-sdk/allof-inline/package.json @@ -0,0 +1,69 @@ +{ + "name": "@fern/allof-inline", + "version": "0.0.1", + "private": false, + "repository": { + "type": "git", + "url": "git+https://github.com/allof-inline/fern.git" + }, + "type": "commonjs", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.mjs", + "types": "./dist/cjs/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.mts", + "default": "./dist/esm/index.mjs" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + }, + "default": "./dist/cjs/index.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "reference.md", + "README.md", + "LICENSE" + ], + "scripts": { + "format": "biome format --write --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "format:check": "biome format --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "lint": "biome lint --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "lint:fix": "biome lint --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "check": "biome check --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "check:fix": "biome check --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "build": "pnpm build:cjs && pnpm build:esm", + "build:cjs": "tsc --project ./tsconfig.cjs.json", + "build:esm": "tsc --project ./tsconfig.esm.json && node scripts/rename-to-esm-files.js dist/esm", + "test": "vitest", + "test:unit": "vitest --project unit", + "test:wire": "vitest --project wire" + }, + "dependencies": {}, + "devDependencies": { + "webpack": "^5.105.4", + "ts-loader": "^9.5.4", + "vitest": "^4.1.1", + "msw": "2.11.2", + "@types/node": "^18.19.70", + "typescript": "~5.9.3", + "@biomejs/biome": "2.4.10" + }, + "browser": { + "fs": false, + "os": false, + "path": false, + "stream": false, + "crypto": false + }, + "packageManager": "pnpm@10.33.0", + "engines": { + "node": ">=18.0.0" + }, + "sideEffects": false +} diff --git a/seed/ts-sdk/allof-inline/pnpm-workspace.yaml b/seed/ts-sdk/allof-inline/pnpm-workspace.yaml new file mode 100644 index 000000000000..6e4c395107df --- /dev/null +++ b/seed/ts-sdk/allof-inline/pnpm-workspace.yaml @@ -0,0 +1 @@ +packages: ['.'] \ No newline at end of file diff --git a/seed/ts-sdk/allof-inline/reference.md b/seed/ts-sdk/allof-inline/reference.md new file mode 100644 index 000000000000..6d75591df79d --- /dev/null +++ b/seed/ts-sdk/allof-inline/reference.md @@ -0,0 +1,225 @@ +# Reference +
client.searchRuleTypes({ ...params }) -> SeedApi.RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.searchRuleTypes(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SeedApi.SearchRuleTypesRequest` + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.createRule({ ...params }) -> SeedApi.RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.createRule({ + name: "name", + executionContext: "prod" +}); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SeedApi.RuleCreateRequest` + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.listUsers() -> SeedApi.UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.listUsers(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.getEntity() -> SeedApi.CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.getEntity(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.getOrganization() -> SeedApi.Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.getOrganization(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ diff --git a/seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js b/seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js new file mode 100644 index 000000000000..dc1df1cbbacb --- /dev/null +++ b/seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js @@ -0,0 +1,123 @@ +#!/usr/bin/env node + +const fs = require("fs").promises; +const path = require("path"); + +const extensionMap = { + ".js": ".mjs", + ".d.ts": ".d.mts", +}; +const oldExtensions = Object.keys(extensionMap); + +async function findFiles(rootPath) { + const files = []; + + async function scan(directory) { + const entries = await fs.readdir(directory, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + if (entry.name !== "node_modules" && !entry.name.startsWith(".")) { + await scan(fullPath); + } + } else if (entry.isFile()) { + if (oldExtensions.some((ext) => entry.name.endsWith(ext))) { + files.push(fullPath); + } + } + } + } + + await scan(rootPath); + return files; +} + +async function updateFiles(files) { + const updatedFiles = []; + for (const file of files) { + const updated = await updateFileContents(file); + updatedFiles.push(updated); + } + + console.log(`Updated imports in ${updatedFiles.length} files.`); +} + +async function updateFileContents(file) { + const content = await fs.readFile(file, "utf8"); + + let newContent = content; + // Update each extension type defined in the map + for (const [oldExt, newExt] of Object.entries(extensionMap)) { + // Handle static imports/exports + const staticRegex = new RegExp(`(import|export)(.+from\\s+['"])(\\.\\.?\\/[^'"]+)(\\${oldExt})(['"])`, "g"); + newContent = newContent.replace(staticRegex, `$1$2$3${newExt}$5`); + + // Handle dynamic imports (yield import, await import, regular import()) + const dynamicRegex = new RegExp( + `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, + "g", + ); + newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); + } + + if (content !== newContent) { + await fs.writeFile(file, newContent, "utf8"); + return true; + } + return false; +} + +async function renameFiles(files) { + let counter = 0; + for (const file of files) { + const ext = oldExtensions.find((ext) => file.endsWith(ext)); + const newExt = extensionMap[ext]; + + if (newExt) { + const newPath = file.slice(0, -ext.length) + newExt; + await fs.rename(file, newPath); + counter++; + } + } + + console.log(`Renamed ${counter} files.`); +} + +async function main() { + try { + const targetDir = process.argv[2]; + if (!targetDir) { + console.error("Please provide a target directory"); + process.exit(1); + } + + const targetPath = path.resolve(targetDir); + const targetStats = await fs.stat(targetPath); + + if (!targetStats.isDirectory()) { + console.error("The provided path is not a directory"); + process.exit(1); + } + + console.log(`Scanning directory: ${targetDir}`); + + const files = await findFiles(targetDir); + + if (files.length === 0) { + console.log("No matching files found."); + process.exit(0); + } + + console.log(`Found ${files.length} files.`); + await updateFiles(files); + await renameFiles(files); + console.log("\nDone!"); + } catch (error) { + console.error("An error occurred:", error.message); + process.exit(1); + } +} + +main(); diff --git a/seed/ts-sdk/allof-inline/snippet.json b/seed/ts-sdk/allof-inline/snippet.json new file mode 100644 index 000000000000..8b75c9d2992f --- /dev/null +++ b/seed/ts-sdk/allof-inline/snippet.json @@ -0,0 +1,60 @@ +{ + "endpoints": [ + { + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.searchRuleTypes();\n" + } + }, + { + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.createRule({\n name: \"name\",\n executionContext: \"prod\"\n});\n" + } + }, + { + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.listUsers();\n" + } + }, + { + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.getEntity();\n" + } + }, + { + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.getOrganization();\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/ts-sdk/allof-inline/src/BaseClient.ts b/seed/ts-sdk/allof-inline/src/BaseClient.ts new file mode 100644 index 000000000000..8bad31218ef2 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/BaseClient.ts @@ -0,0 +1,60 @@ +// This file was auto-generated by Fern from our API Definition. + +import { mergeHeaders } from "./core/headers.js"; +import * as core from "./core/index.js"; +import type * as environments from "./environments.js"; + +export interface BaseClientOptions { + environment?: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | null | undefined>; + /** The default maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The default number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** Provide a custom fetch implementation. Useful for platforms that don't have a built-in fetch or need a custom implementation. */ + fetch?: typeof fetch; + /** Configure logging for the client. */ + logging?: core.logging.LogConfig | core.logging.Logger; +} + +export interface BaseRequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | null | undefined>; +} + +export type NormalizedClientOptions = T & { + logging: core.logging.Logger; +}; + +export function normalizeClientOptions( + options: T, +): NormalizedClientOptions { + const headers = mergeHeaders( + { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/allof-inline", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/allof-inline/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + options?.headers, + ); + + return { + ...options, + logging: core.logging.createLogger(options?.logging), + headers, + } as NormalizedClientOptions; +} diff --git a/seed/ts-sdk/allof-inline/src/Client.ts b/seed/ts-sdk/allof-inline/src/Client.ts new file mode 100644 index 000000000000..01e5b4de5533 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/Client.ts @@ -0,0 +1,303 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "./api/index.js"; +import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; +import { type NormalizedClientOptions, normalizeClientOptions } from "./BaseClient.js"; +import { mergeHeaders } from "./core/headers.js"; +import * as core from "./core/index.js"; +import * as environments from "./environments.js"; +import { handleNonStatusCodeError } from "./errors/handleNonStatusCodeError.js"; +import * as errors from "./errors/index.js"; + +export declare namespace SeedApiClient { + export type Options = BaseClientOptions; + + export interface RequestOptions extends BaseRequestOptions {} +} + +export class SeedApiClient { + protected readonly _options: NormalizedClientOptions; + + constructor(options: SeedApiClient.Options = {}) { + this._options = normalizeClientOptions(options); + } + + /** + * @param {SeedApi.SearchRuleTypesRequest} request + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.searchRuleTypes() + */ + public searchRuleTypes( + request: SeedApi.SearchRuleTypesRequest = {}, + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__searchRuleTypes(request, requestOptions)); + } + + private async __searchRuleTypes( + request: SeedApi.SearchRuleTypesRequest = {}, + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const { query } = request; + const _queryParams: Record = { + query, + }; + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "rule-types", + ), + method: "GET", + headers: _headers, + queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.RuleTypeSearchResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/rule-types"); + } + + /** + * @param {SeedApi.RuleCreateRequest} request + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.createRule({ + * name: "name", + * executionContext: "prod" + * }) + */ + public createRule( + request: SeedApi.RuleCreateRequest, + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__createRule(request, requestOptions)); + } + + private async __createRule( + request: SeedApi.RuleCreateRequest, + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "rules", + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.RuleResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/rules"); + } + + /** + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.listUsers() + */ + public listUsers( + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__listUsers(requestOptions)); + } + + private async __listUsers( + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "users", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.UserSearchResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/users"); + } + + /** + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.getEntity() + */ + public getEntity(requestOptions?: SeedApiClient.RequestOptions): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getEntity(requestOptions)); + } + + private async __getEntity( + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "entities", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.CombinedEntity, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/entities"); + } + + /** + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.getOrganization() + */ + public getOrganization( + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getOrganization(requestOptions)); + } + + private async __getOrganization( + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "organizations", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.Organization, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/organizations"); + } + + /** + * Make a passthrough request using the SDK's configured auth, retry, logging, etc. + * This is useful for making requests to endpoints not yet supported in the SDK. + * The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL. + * + * @param {Request | string | URL} input - The URL, path, or Request object. + * @param {RequestInit} init - Standard fetch RequestInit options. + * @param {core.PassthroughRequest.RequestOptions} requestOptions - Per-request overrides (timeout, retries, headers, abort signal). + * @returns {Promise} A standard Response object. + */ + public async fetch( + input: Request | string | URL, + init?: RequestInit, + requestOptions?: core.PassthroughRequest.RequestOptions, + ): Promise { + return core.makePassthroughRequest( + input, + init, + { + baseUrl: this._options.baseUrl ?? this._options.environment, + headers: this._options.headers, + timeoutInSeconds: this._options.timeoutInSeconds, + maxRetries: this._options.maxRetries, + fetch: this._options.fetch, + logging: this._options.logging, + }, + requestOptions, + ); + } +} diff --git a/seed/ts-sdk/allof-inline/src/api/client/index.ts b/seed/ts-sdk/allof-inline/src/api/client/index.ts new file mode 100644 index 000000000000..195f9aa8a846 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/client/index.ts @@ -0,0 +1 @@ +export * from "./requests/index.js"; diff --git a/seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts b/seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts new file mode 100644 index 000000000000..bb42c4d24ac9 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../../index.js"; + +/** + * @example + * { + * name: "name", + * executionContext: "prod" + * } + */ +export interface RuleCreateRequest { + name: string; + executionContext: SeedApi.RuleExecutionContext; +} diff --git a/seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts b/seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts new file mode 100644 index 000000000000..502888d9c4e3 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * @example + * {} + */ +export interface SearchRuleTypesRequest { + query?: string; +} diff --git a/seed/ts-sdk/allof-inline/src/api/client/requests/index.ts b/seed/ts-sdk/allof-inline/src/api/client/requests/index.ts new file mode 100644 index 000000000000..07aecd81dd45 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/client/requests/index.ts @@ -0,0 +1,2 @@ +export type { RuleCreateRequest } from "./RuleCreateRequest.js"; +export type { SearchRuleTypesRequest } from "./SearchRuleTypesRequest.js"; diff --git a/seed/ts-sdk/allof-inline/src/api/index.ts b/seed/ts-sdk/allof-inline/src/api/index.ts new file mode 100644 index 000000000000..d9adb1af9a93 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/index.ts @@ -0,0 +1,2 @@ +export * from "./client/index.js"; +export * from "./types/index.js"; diff --git a/seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts b/seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts new file mode 100644 index 000000000000..535489319123 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * Common audit metadata. + */ +export interface AuditInfo { + /** The user who created this resource. */ + createdBy?: string | undefined; + /** When this resource was created. */ + createdDateTime?: string | undefined; + /** The user who last modified this resource. */ + modifiedBy?: string | undefined; + /** When this resource was last modified. */ + modifiedDateTime?: string | undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts b/seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts new file mode 100644 index 000000000000..eec0dea9a386 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface BaseOrg { + id: string; + metadata?: BaseOrg.Metadata | undefined; +} + +export namespace BaseOrg { + export interface Metadata { + /** Deployment region from BaseOrg. */ + region: string; + /** Subscription tier. */ + tier?: string | undefined; + } +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts b/seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts new file mode 100644 index 000000000000..bba206ea76fe --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface CombinedEntity { + /** Unique identifier. */ + id: string; + /** Display name from Describable. */ + name?: string | undefined; + /** A short summary. */ + summary?: string | undefined; + status: CombinedEntity.Status; +} + +export namespace CombinedEntity { + export const Status = { + Active: "active", + Archived: "archived", + } as const; + export type Status = (typeof Status)[keyof typeof Status]; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/Describable.ts b/seed/ts-sdk/allof-inline/src/api/types/Describable.ts new file mode 100644 index 000000000000..b5c82cac5a67 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/Describable.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface Describable { + /** Display name from Describable. */ + name?: string | undefined; + /** A short summary. */ + summary?: string | undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts b/seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts new file mode 100644 index 000000000000..84e0ef063cab --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts @@ -0,0 +1,14 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface DetailedOrg { + metadata?: DetailedOrg.Metadata | undefined; +} + +export namespace DetailedOrg { + export interface Metadata { + /** Deployment region from DetailedOrg. */ + region: string; + /** Custom domain name. */ + domain?: string | undefined; + } +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts b/seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts new file mode 100644 index 000000000000..65d2053f6cb8 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface Identifiable { + /** Unique identifier. */ + id: string; + /** Display name from Identifiable. */ + name?: string | undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/Organization.ts b/seed/ts-sdk/allof-inline/src/api/types/Organization.ts new file mode 100644 index 000000000000..4c9106000c07 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/Organization.ts @@ -0,0 +1,16 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface Organization { + id: string; + metadata?: Organization.Metadata | undefined; + name: string; +} + +export namespace Organization { + export interface Metadata { + /** Deployment region from DetailedOrg. */ + region: string; + /** Custom domain name. */ + domain?: string | undefined; + } +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts b/seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts new file mode 100644 index 000000000000..ed373200a8e1 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface PaginatedResult { + paging: SeedApi.PagingCursors; + /** Current page of results from the requested resource. */ + results: unknown[]; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts b/seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts new file mode 100644 index 000000000000..2ff3fa532101 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface PagingCursors { + /** Cursor for the next page of results. */ + next: string; + /** Cursor for the previous page of results. */ + previous?: string | undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts new file mode 100644 index 000000000000..fe794a8856da --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +/** Execution environment for a rule. */ +export const RuleExecutionContext = { + Prod: "prod", + Staging: "staging", + Dev: "dev", +} as const; +export type RuleExecutionContext = (typeof RuleExecutionContext)[keyof typeof RuleExecutionContext]; diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts new file mode 100644 index 000000000000..54edd763c0a2 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts @@ -0,0 +1,27 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface RuleResponse { + /** The user who created this resource. */ + createdBy?: string | undefined; + /** When this resource was created. */ + createdDateTime?: string | undefined; + /** The user who last modified this resource. */ + modifiedBy?: string | undefined; + /** When this resource was last modified. */ + modifiedDateTime?: string | undefined; + id: string; + name: string; + status: RuleResponse.Status; + executionContext?: SeedApi.RuleExecutionContext | undefined; +} + +export namespace RuleResponse { + export const Status = { + Active: "active", + Inactive: "inactive", + Draft: "draft", + } as const; + export type Status = (typeof Status)[keyof typeof Status]; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleType.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleType.ts new file mode 100644 index 000000000000..ac2bde7133b2 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/RuleType.ts @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface RuleType { + id: string; + name: string; + description?: string | undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts new file mode 100644 index 000000000000..c5d63a1d383e --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface RuleTypeSearchResponse { + paging: SeedApi.PagingCursors; + /** Current page of results from the requested resource. */ + results?: SeedApi.RuleType[] | undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/User.ts b/seed/ts-sdk/allof-inline/src/api/types/User.ts new file mode 100644 index 000000000000..7d0e30eaf136 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/User.ts @@ -0,0 +1,6 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface User { + id: string; + email: string; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts b/seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts new file mode 100644 index 000000000000..f9e22de3ec39 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface UserSearchResponse { + paging: SeedApi.PagingCursors; + /** Current page of results from the requested resource. */ + results?: SeedApi.User[] | undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/api/types/index.ts b/seed/ts-sdk/allof-inline/src/api/types/index.ts new file mode 100644 index 000000000000..ae8a133ce81f --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/api/types/index.ts @@ -0,0 +1,15 @@ +export * from "./AuditInfo.js"; +export * from "./BaseOrg.js"; +export * from "./CombinedEntity.js"; +export * from "./Describable.js"; +export * from "./DetailedOrg.js"; +export * from "./Identifiable.js"; +export * from "./Organization.js"; +export * from "./PaginatedResult.js"; +export * from "./PagingCursors.js"; +export * from "./RuleExecutionContext.js"; +export * from "./RuleResponse.js"; +export * from "./RuleType.js"; +export * from "./RuleTypeSearchResponse.js"; +export * from "./User.js"; +export * from "./UserSearchResponse.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/exports.ts b/seed/ts-sdk/allof-inline/src/core/exports.ts new file mode 100644 index 000000000000..69296d7100d6 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/exports.ts @@ -0,0 +1 @@ +export * from "./logging/exports.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts new file mode 100644 index 000000000000..97ab83c2b195 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts @@ -0,0 +1,23 @@ +import type { RawResponse } from "./RawResponse.js"; + +/** + * The response of an API call. + * It is a successful response or a failed response. + */ +export type APIResponse = SuccessfulResponse | FailedResponse; + +export interface SuccessfulResponse { + ok: true; + body: T; + /** + * @deprecated Use `rawResponse` instead + */ + headers?: Record; + rawResponse: RawResponse; +} + +export interface FailedResponse { + ok: false; + error: T; + rawResponse: RawResponse; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts new file mode 100644 index 000000000000..b9e40fb62cc4 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts @@ -0,0 +1,34 @@ +export type BinaryResponse = { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ + bodyUsed: Response["bodyUsed"]; + /** + * Returns a ReadableStream of the response body. + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) + */ + stream: () => Response["body"]; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ + arrayBuffer: () => ReturnType; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ + blob: () => ReturnType; + /** + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) + * Some versions of the Fetch API may not support this method. + */ + bytes?(): Promise; +}; + +export function getBinaryResponse(response: Response): BinaryResponse { + const binaryResponse: BinaryResponse = { + get bodyUsed() { + return response.bodyUsed; + }, + stream: () => response.body, + arrayBuffer: response.arrayBuffer.bind(response), + blob: response.blob.bind(response), + }; + if ("bytes" in response && typeof response.bytes === "function") { + binaryResponse.bytes = response.bytes.bind(response); + } + + return binaryResponse; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts new file mode 100644 index 000000000000..998d68f5c20c --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts @@ -0,0 +1,13 @@ +export type SecuritySchemeKey = string; +/** + * A collection of security schemes, where the key is the name of the security scheme and the value is the list of scopes required for that scheme. + * All schemes in the collection must be satisfied for authentication to be successful. + */ +export type SecuritySchemeCollection = Record; +export type AuthScope = string; +export type EndpointMetadata = { + /** + * An array of security scheme collections. Each collection represents an alternative way to authenticate. + */ + security?: SecuritySchemeCollection[]; +}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts new file mode 100644 index 000000000000..aad81f0d9040 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts @@ -0,0 +1,14 @@ +import type { EndpointMetadata } from "./EndpointMetadata.js"; +import type { Supplier } from "./Supplier.js"; + +type EndpointSupplierFn = (arg: { endpointMetadata?: EndpointMetadata }) => T | Promise; +export type EndpointSupplier = Supplier | EndpointSupplierFn; +export const EndpointSupplier = { + get: async (supplier: EndpointSupplier, arg: { endpointMetadata?: EndpointMetadata }): Promise => { + if (typeof supplier === "function") { + return (supplier as EndpointSupplierFn)(arg); + } else { + return supplier; + } + }, +}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts new file mode 100644 index 000000000000..928dfeaabae6 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts @@ -0,0 +1,404 @@ +import { toJson } from "../json.js"; +import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; +import type { APIResponse } from "./APIResponse.js"; +import { createRequestUrl } from "./createRequestUrl.js"; +import type { EndpointMetadata } from "./EndpointMetadata.js"; +import { EndpointSupplier } from "./EndpointSupplier.js"; +import { getErrorResponseBody } from "./getErrorResponseBody.js"; +import { getFetchFn } from "./getFetchFn.js"; +import { getRequestBody } from "./getRequestBody.js"; +import { getResponseBody } from "./getResponseBody.js"; +import { Headers } from "./Headers.js"; +import { makeRequest } from "./makeRequest.js"; +import { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; +import { requestWithRetries } from "./requestWithRetries.js"; + +export type FetchFunction = (args: Fetcher.Args) => Promise>; + +export declare namespace Fetcher { + export interface Args { + url: string; + method: string; + contentType?: string; + headers?: Record; + queryParameters?: Record; + body?: unknown; + timeoutMs?: number; + maxRetries?: number; + withCredentials?: boolean; + abortSignal?: AbortSignal; + requestType?: "json" | "file" | "bytes" | "form" | "other"; + responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer" | "binary-response"; + duplex?: "half"; + endpointMetadata?: EndpointMetadata; + fetchFn?: typeof fetch; + logging?: LogConfig | Logger; + } + + export type Error = FailedStatusCodeError | NonJsonError | BodyIsNullError | TimeoutError | UnknownError; + + export interface FailedStatusCodeError { + reason: "status-code"; + statusCode: number; + body: unknown; + } + + export interface NonJsonError { + reason: "non-json"; + statusCode: number; + rawBody: string; + } + + export interface BodyIsNullError { + reason: "body-is-null"; + statusCode: number; + } + + export interface TimeoutError { + reason: "timeout"; + cause?: unknown; + } + + export interface UnknownError { + reason: "unknown"; + errorMessage: string; + cause?: unknown; + } +} + +const SENSITIVE_HEADERS = new Set([ + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "cookie", + "set-cookie", + "proxy-authorization", + "proxy-authenticate", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token", +]); + +function redactHeaders(headers: Headers | Record): Record { + const filtered: Record = {}; + for (const [key, value] of headers instanceof Headers ? headers.entries() : Object.entries(headers)) { + if (SENSITIVE_HEADERS.has(key.toLowerCase())) { + filtered[key] = "[REDACTED]"; + } else { + filtered[key] = value; + } + } + return filtered; +} + +const SENSITIVE_QUERY_PARAMS = new Set([ + "api_key", + "api-key", + "apikey", + "token", + "access_token", + "access-token", + "auth_token", + "auth-token", + "password", + "passwd", + "secret", + "api_secret", + "api-secret", + "apisecret", + "key", + "session", + "session_id", + "session-id", +]); + +function redactQueryParameters(queryParameters?: Record): Record | undefined { + if (queryParameters == null) { + return queryParameters; + } + const redacted: Record = {}; + for (const [key, value] of Object.entries(queryParameters)) { + if (SENSITIVE_QUERY_PARAMS.has(key.toLowerCase())) { + redacted[key] = "[REDACTED]"; + } else { + redacted[key] = value; + } + } + return redacted; +} + +function redactUrl(url: string): string { + const protocolIndex = url.indexOf("://"); + if (protocolIndex === -1) return url; + + const afterProtocol = protocolIndex + 3; + + // Find the first delimiter that marks the end of the authority section + const pathStart = url.indexOf("/", afterProtocol); + let queryStart = url.indexOf("?", afterProtocol); + let fragmentStart = url.indexOf("#", afterProtocol); + + const firstDelimiter = Math.min( + pathStart === -1 ? url.length : pathStart, + queryStart === -1 ? url.length : queryStart, + fragmentStart === -1 ? url.length : fragmentStart, + ); + + // Find the LAST @ before the delimiter (handles multiple @ in credentials) + let atIndex = -1; + for (let i = afterProtocol; i < firstDelimiter; i++) { + if (url[i] === "@") { + atIndex = i; + } + } + + if (atIndex !== -1) { + url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`; + } + + // Recalculate queryStart since url might have changed + queryStart = url.indexOf("?"); + if (queryStart === -1) return url; + + fragmentStart = url.indexOf("#", queryStart); + const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length; + const queryString = url.slice(queryStart + 1, queryEnd); + + if (queryString.length === 0) return url; + + // FAST PATH: Quick check if any sensitive keywords present + // Using indexOf is faster than regex for simple substring matching + const lower = queryString.toLowerCase(); + const hasSensitive = + lower.includes("token") || + lower.includes("key") || + lower.includes("password") || + lower.includes("passwd") || + lower.includes("secret") || + lower.includes("session") || + lower.includes("auth"); + + if (!hasSensitive) { + return url; + } + + // SLOW PATH: Parse and redact + const redactedParams: string[] = []; + const params = queryString.split("&"); + + for (const param of params) { + const equalIndex = param.indexOf("="); + if (equalIndex === -1) { + redactedParams.push(param); + continue; + } + + const key = param.slice(0, equalIndex); + let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()); + + if (!shouldRedact && key.includes("%")) { + try { + const decodedKey = decodeURIComponent(key); + shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase()); + } catch {} + } + + redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param); + } + + return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd); +} + +async function getHeaders(args: Fetcher.Args): Promise { + const newHeaders: Headers = new Headers(); + + newHeaders.set( + "Accept", + args.responseType === "json" + ? "application/json" + : args.responseType === "text" + ? "text/plain" + : args.responseType === "sse" + ? "text/event-stream" + : "*/*", + ); + if (args.body !== undefined && args.contentType != null) { + newHeaders.set("Content-Type", args.contentType); + } + + if (args.headers == null) { + return newHeaders; + } + + for (const [key, value] of Object.entries(args.headers)) { + const result = await EndpointSupplier.get(value, { endpointMetadata: args.endpointMetadata ?? {} }); + if (typeof result === "string") { + newHeaders.set(key, result); + continue; + } + if (result == null) { + continue; + } + newHeaders.set(key, `${result}`); + } + return newHeaders; +} + +export async function fetcherImpl(args: Fetcher.Args): Promise> { + const url = createRequestUrl(args.url, args.queryParameters); + const requestBody: BodyInit | undefined = await getRequestBody({ + body: args.body, + type: args.requestType ?? "other", + }); + const fetchFn = args.fetchFn ?? (await getFetchFn()); + const headers = await getHeaders(args); + const logger = createLogger(args.logging); + + if (logger.isDebug()) { + const metadata = { + method: args.method, + url: redactUrl(url), + headers: redactHeaders(headers), + queryParameters: redactQueryParameters(args.queryParameters), + hasBody: requestBody != null, + }; + logger.debug("Making HTTP request", metadata); + } + + try { + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + url, + args.method, + headers, + requestBody, + args.timeoutMs, + args.abortSignal, + args.withCredentials, + args.duplex, + args.responseType === "streaming" || args.responseType === "sse", + ), + args.maxRetries, + ); + + if (response.status >= 200 && response.status < 400) { + if (logger.isDebug()) { + const metadata = { + method: args.method, + url: redactUrl(url), + statusCode: response.status, + responseHeaders: redactHeaders(response.headers), + }; + logger.debug("HTTP request succeeded", metadata); + } + const body = await getResponseBody(response, args.responseType); + return { + ok: true, + body: body as R, + headers: response.headers, + rawResponse: toRawResponse(response), + }; + } else { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + statusCode: response.status, + responseHeaders: redactHeaders(Object.fromEntries(response.headers.entries())), + }; + logger.error("HTTP request failed with error status", metadata); + } + return { + ok: false, + error: { + reason: "status-code", + statusCode: response.status, + body: await getErrorResponseBody(response), + }, + rawResponse: toRawResponse(response), + }; + } + } catch (error) { + if (args.abortSignal?.aborted) { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + }; + logger.error("HTTP request was aborted", metadata); + } + return { + ok: false, + error: { + reason: "unknown", + errorMessage: "The user aborted a request", + cause: error, + }, + rawResponse: abortRawResponse, + }; + } else if (error instanceof Error && error.name === "AbortError") { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + timeoutMs: args.timeoutMs, + }; + logger.error("HTTP request timed out", metadata); + } + return { + ok: false, + error: { + reason: "timeout", + cause: error, + }, + rawResponse: abortRawResponse, + }; + } else if (error instanceof Error) { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + errorMessage: error.message, + }; + logger.error("HTTP request failed with error", metadata); + } + return { + ok: false, + error: { + reason: "unknown", + errorMessage: error.message, + cause: error, + }, + rawResponse: unknownRawResponse, + }; + } + + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + error: toJson(error), + }; + logger.error("HTTP request failed with unknown error", metadata); + } + return { + ok: false, + error: { + reason: "unknown", + errorMessage: toJson(error), + cause: error, + }, + rawResponse: unknownRawResponse, + }; + } +} + +export const fetcher: FetchFunction = fetcherImpl; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts new file mode 100644 index 000000000000..af841aa24f55 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts @@ -0,0 +1,93 @@ +let Headers: typeof globalThis.Headers; + +if (typeof globalThis.Headers !== "undefined") { + Headers = globalThis.Headers; +} else { + Headers = class Headers implements Headers { + private headers: Map; + + constructor(init?: HeadersInit) { + this.headers = new Map(); + + if (init) { + if (init instanceof Headers) { + init.forEach((value, key) => this.append(key, value)); + } else if (Array.isArray(init)) { + for (const [key, value] of init) { + if (typeof key === "string" && typeof value === "string") { + this.append(key, value); + } else { + throw new TypeError("Each header entry must be a [string, string] tuple"); + } + } + } else { + for (const [key, value] of Object.entries(init)) { + if (typeof value === "string") { + this.append(key, value); + } else { + throw new TypeError("Header values must be strings"); + } + } + } + } + } + + append(name: string, value: string): void { + const key = name.toLowerCase(); + const existing = this.headers.get(key) || []; + this.headers.set(key, [...existing, value]); + } + + delete(name: string): void { + const key = name.toLowerCase(); + this.headers.delete(key); + } + + get(name: string): string | null { + const key = name.toLowerCase(); + const values = this.headers.get(key); + return values ? values.join(", ") : null; + } + + has(name: string): boolean { + const key = name.toLowerCase(); + return this.headers.has(key); + } + + set(name: string, value: string): void { + const key = name.toLowerCase(); + this.headers.set(key, [value]); + } + + forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: unknown): void { + const boundCallback = thisArg ? callbackfn.bind(thisArg) : callbackfn; + this.headers.forEach((values, key) => boundCallback(values.join(", "), key, this)); + } + + getSetCookie(): string[] { + return this.headers.get("set-cookie") || []; + } + + *entries(): HeadersIterator<[string, string]> { + for (const [key, values] of this.headers.entries()) { + yield [key, values.join(", ")]; + } + } + + *keys(): HeadersIterator { + yield* this.headers.keys(); + } + + *values(): HeadersIterator { + for (const values of this.headers.values()) { + yield values.join(", "); + } + } + + [Symbol.iterator](): HeadersIterator<[string, string]> { + return this.entries(); + } + }; +} + +export { Headers }; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts new file mode 100644 index 000000000000..692ca7d795f0 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts @@ -0,0 +1,116 @@ +import type { WithRawResponse } from "./RawResponse.js"; + +/** + * A promise that returns the parsed response and lets you retrieve the raw response too. + */ +export class HttpResponsePromise extends Promise { + private innerPromise: Promise>; + private unwrappedPromise: Promise | undefined; + + private constructor(promise: Promise>) { + // Initialize with a no-op to avoid premature parsing + super((resolve) => { + resolve(undefined as unknown as T); + }); + this.innerPromise = promise; + } + + /** + * Creates an `HttpResponsePromise` from a function that returns a promise. + * + * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. + * @param args - Arguments to pass to the function. + * @returns An `HttpResponsePromise` instance. + */ + public static fromFunction Promise>, T>( + fn: F, + ...args: Parameters + ): HttpResponsePromise { + return new HttpResponsePromise(fn(...args)); + } + + /** + * Creates a function that returns an `HttpResponsePromise` from a function that returns a promise. + * + * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. + * @returns A function that returns an `HttpResponsePromise` instance. + */ + public static interceptFunction< + F extends (...args: never[]) => Promise>, + T = Awaited>["data"], + >(fn: F): (...args: Parameters) => HttpResponsePromise { + return (...args: Parameters): HttpResponsePromise => { + return HttpResponsePromise.fromPromise(fn(...args)); + }; + } + + /** + * Creates an `HttpResponsePromise` from an existing promise. + * + * @param promise - A promise resolving to a `WithRawResponse` object. + * @returns An `HttpResponsePromise` instance. + */ + public static fromPromise(promise: Promise>): HttpResponsePromise { + return new HttpResponsePromise(promise); + } + + /** + * Creates an `HttpResponsePromise` from an executor function. + * + * @param executor - A function that takes resolve and reject callbacks to create a promise. + * @returns An `HttpResponsePromise` instance. + */ + public static fromExecutor( + executor: (resolve: (value: WithRawResponse) => void, reject: (reason?: unknown) => void) => void, + ): HttpResponsePromise { + const promise = new Promise>(executor); + return new HttpResponsePromise(promise); + } + + /** + * Creates an `HttpResponsePromise` from a resolved result. + * + * @param result - A `WithRawResponse` object to resolve immediately. + * @returns An `HttpResponsePromise` instance. + */ + public static fromResult(result: WithRawResponse): HttpResponsePromise { + const promise = Promise.resolve(result); + return new HttpResponsePromise(promise); + } + + private unwrap(): Promise { + if (!this.unwrappedPromise) { + this.unwrappedPromise = this.innerPromise.then(({ data }) => data); + } + return this.unwrappedPromise; + } + + /** @inheritdoc */ + public override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, + ): Promise { + return this.unwrap().then(onfulfilled, onrejected); + } + + /** @inheritdoc */ + public override catch( + onrejected?: ((reason: unknown) => TResult | PromiseLike) | null, + ): Promise { + return this.unwrap().catch(onrejected); + } + + /** @inheritdoc */ + public override finally(onfinally?: (() => void) | null): Promise { + return this.unwrap().finally(onfinally); + } + + /** + * Retrieves the data and raw response. + * + * @returns A promise resolving to a `WithRawResponse` object. + */ + public async withRawResponse(): Promise> { + return await this.innerPromise; + } +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts new file mode 100644 index 000000000000..37fb44e2aa99 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts @@ -0,0 +1,61 @@ +import { Headers } from "./Headers.js"; + +/** + * The raw response from the fetch call excluding the body. + */ +export type RawResponse = Omit< + { + [K in keyof Response as Response[K] extends Function ? never : K]: Response[K]; // strips out functions + }, + "ok" | "body" | "bodyUsed" +>; // strips out body and bodyUsed + +/** + * A raw response indicating that the request was aborted. + */ +export const abortRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 499, + statusText: "Client Closed Request", + type: "error", + url: "", +} as const; + +/** + * A raw response indicating an unknown error. + */ +export const unknownRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 0, + statusText: "Unknown Error", + type: "error", + url: "", +} as const; + +/** + * Converts a `RawResponse` object into a `RawResponse` by extracting its properties, + * excluding the `body` and `bodyUsed` fields. + * + * @param response - The `RawResponse` object to convert. + * @returns A `RawResponse` object containing the extracted properties of the input response. + */ +export function toRawResponse(response: Response): RawResponse { + return { + headers: response.headers, + redirected: response.redirected, + status: response.status, + statusText: response.statusText, + type: response.type, + url: response.url, + }; +} + +/** + * Creates a `RawResponse` from a standard `Response` object. + */ +export interface WithRawResponse { + readonly data: T; + readonly rawResponse: RawResponse; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts new file mode 100644 index 000000000000..867c931c02f4 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts @@ -0,0 +1,11 @@ +export type Supplier = T | Promise | (() => T | Promise); + +export const Supplier = { + get: async (supplier: Supplier): Promise => { + if (typeof supplier === "function") { + return (supplier as () => T)(); + } else { + return supplier; + } + }, +}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts new file mode 100644 index 000000000000..88e13265e112 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts @@ -0,0 +1,6 @@ +import { toQueryString } from "../url/qs.js"; + +export function createRequestUrl(baseUrl: string, queryParameters?: Record): string { + const queryString = toQueryString(queryParameters, { arrayFormat: "repeat" }); + return queryString ? `${baseUrl}?${queryString}` : baseUrl; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts new file mode 100644 index 000000000000..7cf4e623c2f5 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts @@ -0,0 +1,33 @@ +import { fromJson } from "../json.js"; +import { getResponseBody } from "./getResponseBody.js"; + +export async function getErrorResponseBody(response: Response): Promise { + let contentType = response.headers.get("Content-Type")?.toLowerCase(); + if (contentType == null || contentType.length === 0) { + return getResponseBody(response); + } + + if (contentType.indexOf(";") !== -1) { + contentType = contentType.split(";")[0]?.trim() ?? ""; + } + switch (contentType) { + case "application/hal+json": + case "application/json": + case "application/ld+json": + case "application/problem+json": + case "application/vnd.api+json": + case "text/json": { + const text = await response.text(); + return text.length > 0 ? fromJson(text) : undefined; + } + default: + if (contentType.startsWith("application/vnd.") && contentType.endsWith("+json")) { + const text = await response.text(); + return text.length > 0 ? fromJson(text) : undefined; + } + + // Fallback to plain text if content type is not recognized + // Even if no body is present, the response will be an empty string + return await response.text(); + } +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts new file mode 100644 index 000000000000..9f845b956392 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts @@ -0,0 +1,3 @@ +export async function getFetchFn(): Promise { + return fetch; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts new file mode 100644 index 000000000000..50f922b0e87f --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts @@ -0,0 +1,8 @@ +export function getHeader(headers: Record, header: string): string | undefined { + for (const [headerKey, headerValue] of Object.entries(headers)) { + if (headerKey.toLowerCase() === header.toLowerCase()) { + return headerValue; + } + } + return undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts new file mode 100644 index 000000000000..91d9d81f50e5 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts @@ -0,0 +1,20 @@ +import { toJson } from "../json.js"; +import { toQueryString } from "../url/qs.js"; + +export declare namespace GetRequestBody { + interface Args { + body: unknown; + type: "json" | "file" | "bytes" | "form" | "other"; + } +} + +export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { + if (type === "form") { + return toQueryString(body, { arrayFormat: "repeat", encode: true }); + } + if (type.includes("json")) { + return toJson(body); + } else { + return body as BodyInit; + } +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts new file mode 100644 index 000000000000..708d55728f2b --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts @@ -0,0 +1,58 @@ +import { fromJson } from "../json.js"; +import { getBinaryResponse } from "./BinaryResponse.js"; + +export async function getResponseBody(response: Response, responseType?: string): Promise { + switch (responseType) { + case "binary-response": + return getBinaryResponse(response); + case "blob": + return await response.blob(); + case "arrayBuffer": + return await response.arrayBuffer(); + case "sse": + if (response.body == null) { + return { + ok: false, + error: { + reason: "body-is-null", + statusCode: response.status, + }, + }; + } + return response.body; + case "streaming": + if (response.body == null) { + return { + ok: false, + error: { + reason: "body-is-null", + statusCode: response.status, + }, + }; + } + + return response.body; + + case "text": + return await response.text(); + } + + // if responseType is "json" or not specified, try to parse as JSON + const text = await response.text(); + if (text.length > 0) { + try { + const responseBody = fromJson(text); + return responseBody; + } catch (_err) { + return { + ok: false, + error: { + reason: "non-json", + statusCode: response.status, + rawBody: text, + }, + }; + } + } + return undefined; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/index.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/index.ts new file mode 100644 index 000000000000..bd5db362c778 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/index.ts @@ -0,0 +1,13 @@ +export type { APIResponse } from "./APIResponse.js"; +export type { BinaryResponse } from "./BinaryResponse.js"; +export type { EndpointMetadata } from "./EndpointMetadata.js"; +export { EndpointSupplier } from "./EndpointSupplier.js"; +export type { Fetcher, FetchFunction } from "./Fetcher.js"; +export { fetcher } from "./Fetcher.js"; +export { getHeader } from "./getHeader.js"; +export { HttpResponsePromise } from "./HttpResponsePromise.js"; +export type { PassthroughRequest } from "./makePassthroughRequest.js"; +export { makePassthroughRequest } from "./makePassthroughRequest.js"; +export type { RawResponse, WithRawResponse } from "./RawResponse.js"; +export { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; +export { Supplier } from "./Supplier.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts new file mode 100644 index 000000000000..f5ba761400f8 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts @@ -0,0 +1,189 @@ +import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; +import { join } from "../url/join.js"; +import { EndpointSupplier } from "./EndpointSupplier.js"; +import { getFetchFn } from "./getFetchFn.js"; +import { makeRequest } from "./makeRequest.js"; +import { requestWithRetries } from "./requestWithRetries.js"; +import { Supplier } from "./Supplier.js"; + +export declare namespace PassthroughRequest { + /** + * Per-request options that can override the SDK client defaults. + */ + export interface RequestOptions { + /** Override the default timeout for this request (in seconds). */ + timeoutInSeconds?: number; + /** Override the default number of retries for this request. */ + maxRetries?: number; + /** Additional headers to include in this request. */ + headers?: Record; + /** Abort signal for this request. */ + abortSignal?: AbortSignal; + } + + /** + * SDK client configuration used by the passthrough fetch method. + */ + export interface ClientOptions { + /** The base URL or environment for the client. */ + environment?: Supplier; + /** Override the base URL. */ + baseUrl?: Supplier; + /** Default headers to include in requests. */ + headers?: Record; + /** Default maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** Default number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A custom fetch function. */ + fetch?: typeof fetch; + /** Logging configuration. */ + logging?: LogConfig | Logger; + /** A function that returns auth headers. */ + getAuthHeaders?: () => Promise>; + } +} + +/** + * Makes a passthrough HTTP request using the SDK's configuration (auth, retry, logging, etc.) + * while mimicking the standard `fetch` API. + * + * @param input - The URL, path, or Request object. If a relative path, it will be resolved against the configured base URL. + * @param init - Standard RequestInit options (method, headers, body, signal, etc.) + * @param clientOptions - SDK client options (auth, default headers, logging, etc.) + * @param requestOptions - Per-request overrides (timeout, retries, extra headers, abort signal). + * @returns A standard Response object. + */ +export async function makePassthroughRequest( + input: Request | string | URL, + init: RequestInit | undefined, + clientOptions: PassthroughRequest.ClientOptions, + requestOptions?: PassthroughRequest.RequestOptions, +): Promise { + const logger = createLogger(clientOptions.logging); + + // Extract URL and default init properties from Request object if provided + let url: string; + let effectiveInit: RequestInit | undefined = init; + if (input instanceof Request) { + url = input.url; + // If no explicit init provided, extract properties from the Request object + if (init == null) { + effectiveInit = { + method: input.method, + headers: Object.fromEntries(input.headers.entries()), + body: input.body, + signal: input.signal, + credentials: input.credentials, + cache: input.cache as RequestCache, + redirect: input.redirect, + referrer: input.referrer, + integrity: input.integrity, + mode: input.mode, + }; + } + } else { + url = input instanceof URL ? input.toString() : input; + } + + // Resolve the base URL + const baseUrl = + (clientOptions.baseUrl != null ? await Supplier.get(clientOptions.baseUrl) : undefined) ?? + (clientOptions.environment != null ? await Supplier.get(clientOptions.environment) : undefined); + + // Determine the full URL + let fullUrl: string; + if (url.startsWith("http://") || url.startsWith("https://")) { + fullUrl = url; + } else if (baseUrl != null) { + fullUrl = join(baseUrl, url); + } else { + fullUrl = url; + } + + // Merge headers: SDK default headers -> auth headers -> user-provided headers + const mergedHeaders: Record = {}; + + // Apply SDK default headers (resolve suppliers) + if (clientOptions.headers != null) { + for (const [key, value] of Object.entries(clientOptions.headers)) { + const resolved = await EndpointSupplier.get(value, { endpointMetadata: {} }); + if (resolved != null) { + mergedHeaders[key.toLowerCase()] = `${resolved}`; + } + } + } + + // Apply auth headers + if (clientOptions.getAuthHeaders != null) { + const authHeaders = await clientOptions.getAuthHeaders(); + for (const [key, value] of Object.entries(authHeaders)) { + mergedHeaders[key.toLowerCase()] = value; + } + } + + // Apply user-provided headers from init + if (effectiveInit?.headers != null) { + const initHeaders = + effectiveInit.headers instanceof Headers + ? Object.fromEntries(effectiveInit.headers.entries()) + : Array.isArray(effectiveInit.headers) + ? Object.fromEntries(effectiveInit.headers) + : effectiveInit.headers; + for (const [key, value] of Object.entries(initHeaders)) { + if (value != null) { + mergedHeaders[key.toLowerCase()] = value; + } + } + } + + // Apply per-request option headers (highest priority) + if (requestOptions?.headers != null) { + for (const [key, value] of Object.entries(requestOptions.headers)) { + mergedHeaders[key.toLowerCase()] = value; + } + } + + const method = effectiveInit?.method ?? "GET"; + const body = effectiveInit?.body; + const timeoutInSeconds = requestOptions?.timeoutInSeconds ?? clientOptions.timeoutInSeconds; + const timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : undefined; + const maxRetries = requestOptions?.maxRetries ?? clientOptions.maxRetries; + const abortSignal = requestOptions?.abortSignal ?? effectiveInit?.signal ?? undefined; + const fetchFn = clientOptions.fetch ?? (await getFetchFn()); + + if (logger.isDebug()) { + logger.debug("Making passthrough HTTP request", { + method, + url: fullUrl, + hasBody: body != null, + }); + } + + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + fullUrl, + method, + mergedHeaders, + body ?? undefined, + timeoutMs, + abortSignal, + effectiveInit?.credentials === "include", + undefined, // duplex + false, // disableCache + ), + maxRetries, + ); + + if (logger.isDebug()) { + logger.debug("Passthrough HTTP request completed", { + method, + url: fullUrl, + statusCode: response.status, + }); + } + + return response; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts new file mode 100644 index 000000000000..360a86df40ad --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts @@ -0,0 +1,70 @@ +import { anySignal, getTimeoutSignal } from "./signals.js"; + +/** + * Cached result of checking whether the current runtime supports + * the `cache` option in `Request`. Some runtimes (e.g. Cloudflare Workers) + * throw a TypeError when this option is used. + */ +let _cacheNoStoreSupported: boolean | undefined; +export function isCacheNoStoreSupported(): boolean { + if (_cacheNoStoreSupported != null) { + return _cacheNoStoreSupported; + } + try { + new Request("http://localhost", { cache: "no-store" }); + _cacheNoStoreSupported = true; + } catch { + _cacheNoStoreSupported = false; + } + return _cacheNoStoreSupported; +} + +/** + * Reset the cached result of `isCacheNoStoreSupported`. Exposed for testing only. + */ +export function resetCacheNoStoreSupported(): void { + _cacheNoStoreSupported = undefined; +} + +export const makeRequest = async ( + fetchFn: (url: string, init: RequestInit) => Promise, + url: string, + method: string, + headers: Headers | Record, + requestBody: BodyInit | undefined, + timeoutMs?: number, + abortSignal?: AbortSignal, + withCredentials?: boolean, + duplex?: "half", + disableCache?: boolean, +): Promise => { + const signals: AbortSignal[] = []; + + let timeoutAbortId: ReturnType | undefined; + if (timeoutMs != null) { + const { signal, abortId } = getTimeoutSignal(timeoutMs); + timeoutAbortId = abortId; + signals.push(signal); + } + + if (abortSignal != null) { + signals.push(abortSignal); + } + const newSignals = anySignal(signals); + const response = await fetchFn(url, { + method: method, + headers, + body: requestBody, + signal: newSignals, + credentials: withCredentials ? "include" : undefined, + // @ts-ignore + duplex, + ...(disableCache && isCacheNoStoreSupported() ? { cache: "no-store" as RequestCache } : {}), + }); + + if (timeoutAbortId != null) { + clearTimeout(timeoutAbortId); + } + + return response; +}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts new file mode 100644 index 000000000000..1f689688c4b2 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts @@ -0,0 +1,64 @@ +const INITIAL_RETRY_DELAY = 1000; // in milliseconds +const MAX_RETRY_DELAY = 60000; // in milliseconds +const DEFAULT_MAX_RETRIES = 2; +const JITTER_FACTOR = 0.2; // 20% random jitter + +function addPositiveJitter(delay: number): number { + const jitterMultiplier = 1 + Math.random() * JITTER_FACTOR; + return delay * jitterMultiplier; +} + +function addSymmetricJitter(delay: number): number { + const jitterMultiplier = 1 + (Math.random() - 0.5) * JITTER_FACTOR; + return delay * jitterMultiplier; +} + +function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { + const retryAfter = response.headers.get("Retry-After"); + if (retryAfter) { + const retryAfterSeconds = parseInt(retryAfter, 10); + if (!Number.isNaN(retryAfterSeconds) && retryAfterSeconds > 0) { + return Math.min(retryAfterSeconds * 1000, MAX_RETRY_DELAY); + } + + const retryAfterDate = new Date(retryAfter); + if (!Number.isNaN(retryAfterDate.getTime())) { + const delay = retryAfterDate.getTime() - Date.now(); + if (delay > 0) { + return Math.min(Math.max(delay, 0), MAX_RETRY_DELAY); + } + } + } + + const rateLimitReset = response.headers.get("X-RateLimit-Reset"); + if (rateLimitReset) { + const resetTime = parseInt(rateLimitReset, 10); + if (!Number.isNaN(resetTime)) { + const delay = resetTime * 1000 - Date.now(); + if (delay > 0) { + return addPositiveJitter(Math.min(delay, MAX_RETRY_DELAY)); + } + } + } + + return addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** retryAttempt, MAX_RETRY_DELAY)); +} + +export async function requestWithRetries( + requestFn: () => Promise, + maxRetries: number = DEFAULT_MAX_RETRIES, +): Promise { + let response: Response = await requestFn(); + + for (let i = 0; i < maxRetries; ++i) { + if ([408, 429].includes(response.status) || response.status >= 500) { + const delay = getRetryDelayFromHeaders(response, i); + + await new Promise((resolve) => setTimeout(resolve, delay)); + response = await requestFn(); + } else { + break; + } + } + return response!; +} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts new file mode 100644 index 000000000000..7bd3757ec3a7 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts @@ -0,0 +1,26 @@ +const TIMEOUT = "timeout"; + +export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: ReturnType } { + const controller = new AbortController(); + const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); + return { signal: controller.signal, abortId }; +} + +export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { + const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args) as AbortSignal[]; + + const controller = new AbortController(); + + for (const signal of signals) { + if (signal.aborted) { + controller.abort((signal as any)?.reason); + break; + } + + signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { + signal: controller.signal, + }); + } + + return controller.signal; +} diff --git a/seed/ts-sdk/allof-inline/src/core/headers.ts b/seed/ts-sdk/allof-inline/src/core/headers.ts new file mode 100644 index 000000000000..be45c4552a35 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/headers.ts @@ -0,0 +1,33 @@ +export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { + const result: Record = {}; + + for (const [key, value] of headersArray + .filter((headers) => headers != null) + .flatMap((headers) => Object.entries(headers))) { + const insensitiveKey = key.toLowerCase(); + if (value != null) { + result[insensitiveKey] = value; + } else if (insensitiveKey in result) { + delete result[insensitiveKey]; + } + } + + return result; +} + +export function mergeOnlyDefinedHeaders( + ...headersArray: (Record | null | undefined)[] +): Record { + const result: Record = {}; + + for (const [key, value] of headersArray + .filter((headers) => headers != null) + .flatMap((headers) => Object.entries(headers))) { + const insensitiveKey = key.toLowerCase(); + if (value != null) { + result[insensitiveKey] = value; + } + } + + return result; +} diff --git a/seed/ts-sdk/allof-inline/src/core/index.ts b/seed/ts-sdk/allof-inline/src/core/index.ts new file mode 100644 index 000000000000..afa8351fcf85 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/index.ts @@ -0,0 +1,4 @@ +export * from "./fetcher/index.js"; +export * as logging from "./logging/index.js"; +export * from "./runtime/index.js"; +export * as url from "./url/index.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/json.ts b/seed/ts-sdk/allof-inline/src/core/json.ts new file mode 100644 index 000000000000..c052f3249f4f --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/json.ts @@ -0,0 +1,27 @@ +/** + * Serialize a value to JSON + * @param value A JavaScript value, usually an object or array, to be converted. + * @param replacer A function that transforms the results. + * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. + * @returns JSON string + */ +export const toJson = ( + value: unknown, + replacer?: (this: unknown, key: string, value: unknown) => unknown, + space?: string | number, +): string => { + return JSON.stringify(value, replacer, space); +}; + +/** + * Parse JSON string to object, array, or other type + * @param text A valid JSON string. + * @param reviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is. + * @returns Parsed object, array, or other type + */ +export function fromJson( + text: string, + reviver?: (this: unknown, key: string, value: unknown) => unknown, +): T { + return JSON.parse(text, reviver); +} diff --git a/seed/ts-sdk/allof-inline/src/core/logging/exports.ts b/seed/ts-sdk/allof-inline/src/core/logging/exports.ts new file mode 100644 index 000000000000..88f6c00db0cf --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/logging/exports.ts @@ -0,0 +1,19 @@ +import * as logger from "./logger.js"; + +export namespace logging { + /** + * Configuration for logger instances. + */ + export type LogConfig = logger.LogConfig; + export type LogLevel = logger.LogLevel; + export const LogLevel: typeof logger.LogLevel = logger.LogLevel; + export type ILogger = logger.ILogger; + /** + * Console logger implementation that outputs to the console. + */ + export type ConsoleLogger = logger.ConsoleLogger; + /** + * Console logger implementation that outputs to the console. + */ + export const ConsoleLogger: typeof logger.ConsoleLogger = logger.ConsoleLogger; +} diff --git a/seed/ts-sdk/allof-inline/src/core/logging/index.ts b/seed/ts-sdk/allof-inline/src/core/logging/index.ts new file mode 100644 index 000000000000..d81cc32c40f9 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/logging/index.ts @@ -0,0 +1 @@ +export * from "./logger.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/logging/logger.ts b/seed/ts-sdk/allof-inline/src/core/logging/logger.ts new file mode 100644 index 000000000000..a3f3673cda93 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/logging/logger.ts @@ -0,0 +1,203 @@ +export const LogLevel = { + Debug: "debug", + Info: "info", + Warn: "warn", + Error: "error", +} as const; +export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; +const logLevelMap: Record = { + [LogLevel.Debug]: 1, + [LogLevel.Info]: 2, + [LogLevel.Warn]: 3, + [LogLevel.Error]: 4, +}; + +export interface ILogger { + /** + * Logs a debug message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + debug(message: string, ...args: unknown[]): void; + /** + * Logs an info message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + info(message: string, ...args: unknown[]): void; + /** + * Logs a warning message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + warn(message: string, ...args: unknown[]): void; + /** + * Logs an error message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + error(message: string, ...args: unknown[]): void; +} + +/** + * Configuration for logger initialization. + */ +export interface LogConfig { + /** + * Minimum log level to output. + * @default LogLevel.Info + */ + level?: LogLevel; + /** + * Logger implementation to use. + * @default new ConsoleLogger() + */ + logger?: ILogger; + /** + * Whether logging should be silenced. + * @default true + */ + silent?: boolean; +} + +/** + * Default console-based logger implementation. + */ +export class ConsoleLogger implements ILogger { + debug(message: string, ...args: unknown[]): void { + console.debug(message, ...args); + } + info(message: string, ...args: unknown[]): void { + console.info(message, ...args); + } + warn(message: string, ...args: unknown[]): void { + console.warn(message, ...args); + } + error(message: string, ...args: unknown[]): void { + console.error(message, ...args); + } +} + +/** + * Logger class that provides level-based logging functionality. + */ +export class Logger { + private readonly level: number; + private readonly logger: ILogger; + private readonly silent: boolean; + + /** + * Creates a new logger instance. + * @param config - Logger configuration + */ + constructor(config: Required) { + this.level = logLevelMap[config.level]; + this.logger = config.logger; + this.silent = config.silent; + } + + /** + * Checks if a log level should be output based on configuration. + * @param level - The log level to check + * @returns True if the level should be logged + */ + public shouldLog(level: LogLevel): boolean { + return !this.silent && this.level <= logLevelMap[level]; + } + + /** + * Checks if debug logging is enabled. + * @returns True if debug logs should be output + */ + public isDebug(): boolean { + return this.shouldLog(LogLevel.Debug); + } + + /** + * Logs a debug message if debug logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public debug(message: string, ...args: unknown[]): void { + if (this.isDebug()) { + this.logger.debug(message, ...args); + } + } + + /** + * Checks if info logging is enabled. + * @returns True if info logs should be output + */ + public isInfo(): boolean { + return this.shouldLog(LogLevel.Info); + } + + /** + * Logs an info message if info logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public info(message: string, ...args: unknown[]): void { + if (this.isInfo()) { + this.logger.info(message, ...args); + } + } + + /** + * Checks if warning logging is enabled. + * @returns True if warning logs should be output + */ + public isWarn(): boolean { + return this.shouldLog(LogLevel.Warn); + } + + /** + * Logs a warning message if warning logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public warn(message: string, ...args: unknown[]): void { + if (this.isWarn()) { + this.logger.warn(message, ...args); + } + } + + /** + * Checks if error logging is enabled. + * @returns True if error logs should be output + */ + public isError(): boolean { + return this.shouldLog(LogLevel.Error); + } + + /** + * Logs an error message if error logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public error(message: string, ...args: unknown[]): void { + if (this.isError()) { + this.logger.error(message, ...args); + } + } +} + +export function createLogger(config?: LogConfig | Logger): Logger { + if (config == null) { + return defaultLogger; + } + if (config instanceof Logger) { + return config; + } + config = config ?? {}; + config.level ??= LogLevel.Info; + config.logger ??= new ConsoleLogger(); + config.silent ??= true; + return new Logger(config as Required); +} + +const defaultLogger: Logger = new Logger({ + level: LogLevel.Info, + logger: new ConsoleLogger(), + silent: true, +}); diff --git a/seed/ts-sdk/allof-inline/src/core/runtime/index.ts b/seed/ts-sdk/allof-inline/src/core/runtime/index.ts new file mode 100644 index 000000000000..cfab23f9a834 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/runtime/index.ts @@ -0,0 +1 @@ +export { RUNTIME } from "./runtime.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts b/seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts new file mode 100644 index 000000000000..e6e66b2a7bce --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts @@ -0,0 +1,134 @@ +interface DenoGlobal { + version: { + deno: string; + }; +} + +interface BunGlobal { + version: string; +} + +declare const Deno: DenoGlobal | undefined; +declare const Bun: BunGlobal | undefined; +declare const EdgeRuntime: string | undefined; +declare const self: typeof globalThis.self & { + importScripts?: unknown; +}; + +/** + * A constant that indicates which environment and version the SDK is running in. + */ +export const RUNTIME: Runtime = evaluateRuntime(); + +export interface Runtime { + type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd" | "edge-runtime"; + version?: string; + parsedVersion?: number; +} + +function evaluateRuntime(): Runtime { + /** + * A constant that indicates whether the environment the code is running is a Web Browser. + */ + const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; + if (isBrowser) { + return { + type: "browser", + version: window.navigator.userAgent, + }; + } + + /** + * A constant that indicates whether the environment the code is running is Cloudflare. + * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent + */ + const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; + if (isCloudflare) { + return { + type: "workerd", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Edge Runtime. + * https://vercel.com/docs/functions/runtimes/edge-runtime#check-if-you're-running-on-the-edge-runtime + */ + const isEdgeRuntime = typeof EdgeRuntime === "string"; + if (isEdgeRuntime) { + return { + type: "edge-runtime", + }; + } + + /** + * A constant that indicates whether the environment the code is running is a Web Worker. + */ + const isWebWorker = + typeof self === "object" && + typeof self?.importScripts === "function" && + (self.constructor?.name === "DedicatedWorkerGlobalScope" || + self.constructor?.name === "ServiceWorkerGlobalScope" || + self.constructor?.name === "SharedWorkerGlobalScope"); + if (isWebWorker) { + return { + type: "web-worker", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Deno. + * FYI Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions + */ + const isDeno = + typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; + if (isDeno) { + return { + type: "deno", + version: Deno.version.deno, + }; + } + + /** + * A constant that indicates whether the environment the code is running is Bun.sh. + */ + const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; + if (isBun) { + return { + type: "bun", + version: Bun.version, + }; + } + + /** + * A constant that indicates whether the environment the code is running is in React-Native. + * This check should come before Node.js detection since React Native may have a process polyfill. + * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js + */ + const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; + if (isReactNative) { + return { + type: "react-native", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Node.JS. + * + * We assign `process` to a local variable first to avoid being flagged by + * bundlers that perform static analysis on `process.versions` (e.g. Next.js + * Edge Runtime warns about Node.js APIs even when they are guarded). + */ + const _process = typeof process !== "undefined" ? process : undefined; + const isNode = typeof _process !== "undefined" && typeof _process.versions?.node === "string"; + if (isNode) { + return { + type: "node", + version: _process.versions.node, + parsedVersion: Number(_process.versions.node.split(".")[0]), + }; + } + + return { + type: "unknown", + }; +} diff --git a/seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts b/seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts new file mode 100644 index 000000000000..19b901244218 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts @@ -0,0 +1,18 @@ +export function encodePathParam(param: unknown): string { + if (param === null) { + return "null"; + } + const typeofParam = typeof param; + switch (typeofParam) { + case "undefined": + return "undefined"; + case "string": + case "number": + case "boolean": + break; + default: + param = String(param); + break; + } + return encodeURIComponent(param as string | number | boolean); +} diff --git a/seed/ts-sdk/allof-inline/src/core/url/index.ts b/seed/ts-sdk/allof-inline/src/core/url/index.ts new file mode 100644 index 000000000000..f2e0fa2d2221 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/url/index.ts @@ -0,0 +1,3 @@ +export { encodePathParam } from "./encodePathParam.js"; +export { join } from "./join.js"; +export { toQueryString } from "./qs.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/url/join.ts b/seed/ts-sdk/allof-inline/src/core/url/join.ts new file mode 100644 index 000000000000..7ca7daef094d --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/url/join.ts @@ -0,0 +1,79 @@ +export function join(base: string, ...segments: string[]): string { + if (!base) { + return ""; + } + + if (segments.length === 0) { + return base; + } + + if (base.includes("://")) { + let url: URL; + try { + url = new URL(base); + } catch { + return joinPath(base, ...segments); + } + + const lastSegment = segments[segments.length - 1]; + const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); + + for (const segment of segments) { + const cleanSegment = trimSlashes(segment); + if (cleanSegment) { + url.pathname = joinPathSegments(url.pathname, cleanSegment); + } + } + + if (shouldPreserveTrailingSlash && !url.pathname.endsWith("/")) { + url.pathname += "/"; + } + + return url.toString(); + } + + return joinPath(base, ...segments); +} + +function joinPath(base: string, ...segments: string[]): string { + if (segments.length === 0) { + return base; + } + + let result = base; + + const lastSegment = segments[segments.length - 1]; + const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); + + for (const segment of segments) { + const cleanSegment = trimSlashes(segment); + if (cleanSegment) { + result = joinPathSegments(result, cleanSegment); + } + } + + if (shouldPreserveTrailingSlash && !result.endsWith("/")) { + result += "/"; + } + + return result; +} + +function joinPathSegments(left: string, right: string): string { + if (left.endsWith("/")) { + return left + right; + } + return `${left}/${right}`; +} + +function trimSlashes(str: string): string { + if (!str) return str; + + let start = 0; + let end = str.length; + + if (str.startsWith("/")) start = 1; + if (str.endsWith("/")) end = str.length - 1; + + return start === 0 && end === str.length ? str : str.slice(start, end); +} diff --git a/seed/ts-sdk/allof-inline/src/core/url/qs.ts b/seed/ts-sdk/allof-inline/src/core/url/qs.ts new file mode 100644 index 000000000000..13e89be9d9a6 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/core/url/qs.ts @@ -0,0 +1,74 @@ +interface QueryStringOptions { + arrayFormat?: "indices" | "repeat"; + encode?: boolean; +} + +const defaultQsOptions: Required = { + arrayFormat: "indices", + encode: true, +} as const; + +function encodeValue(value: unknown, shouldEncode: boolean): string { + if (value === undefined) { + return ""; + } + if (value === null) { + return ""; + } + const stringValue = String(value); + return shouldEncode ? encodeURIComponent(stringValue) : stringValue; +} + +function stringifyObject(obj: Record, prefix = "", options: Required): string[] { + const parts: string[] = []; + + for (const [key, value] of Object.entries(obj)) { + const fullKey = prefix ? `${prefix}[${key}]` : key; + + if (value === undefined) { + continue; + } + + if (Array.isArray(value)) { + if (value.length === 0) { + continue; + } + for (let i = 0; i < value.length; i++) { + const item = value[i]; + if (item === undefined) { + continue; + } + if (typeof item === "object" && !Array.isArray(item) && item !== null) { + const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; + parts.push(...stringifyObject(item as Record, arrayKey, options)); + } else { + const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; + const encodedKey = options.encode ? encodeURIComponent(arrayKey) : arrayKey; + parts.push(`${encodedKey}=${encodeValue(item, options.encode)}`); + } + } + } else if (typeof value === "object" && value !== null) { + if (Object.keys(value as Record).length === 0) { + continue; + } + parts.push(...stringifyObject(value as Record, fullKey, options)); + } else { + const encodedKey = options.encode ? encodeURIComponent(fullKey) : fullKey; + parts.push(`${encodedKey}=${encodeValue(value, options.encode)}`); + } + } + + return parts; +} + +export function toQueryString(obj: unknown, options?: QueryStringOptions): string { + if (obj == null || typeof obj !== "object") { + return ""; + } + + const parts = stringifyObject(obj as Record, "", { + ...defaultQsOptions, + ...options, + }); + return parts.join("&"); +} diff --git a/seed/ts-sdk/allof-inline/src/environments.ts b/seed/ts-sdk/allof-inline/src/environments.ts new file mode 100644 index 000000000000..92d0fc94dd2c --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/environments.ts @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +export const SeedApiEnvironment = { + Default: "https://api.example.com", +} as const; + +export type SeedApiEnvironment = typeof SeedApiEnvironment.Default; diff --git a/seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts b/seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts new file mode 100644 index 000000000000..ec2bc570e2a7 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as core from "../core/index.js"; +import { toJson } from "../core/json.js"; + +export class SeedApiError extends Error { + public readonly statusCode?: number; + public readonly body?: unknown; + public readonly rawResponse?: core.RawResponse; + public readonly cause?: unknown; + + constructor({ + message, + statusCode, + body, + rawResponse, + cause, + }: { + message?: string; + statusCode?: number; + body?: unknown; + rawResponse?: core.RawResponse; + cause?: unknown; + }) { + super(buildMessage({ message, statusCode, body })); + Object.setPrototypeOf(this, new.target.prototype); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + + this.name = this.constructor.name; + this.statusCode = statusCode; + this.body = body; + this.rawResponse = rawResponse; + if (cause != null) { + this.cause = cause; + } + } +} + +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { + const lines: string[] = []; + if (message != null) { + lines.push(message); + } + + if (statusCode != null) { + lines.push(`Status code: ${statusCode.toString()}`); + } + + if (body != null) { + lines.push(`Body: ${toJson(body, undefined, 2)}`); + } + + return lines.join("\n"); +} diff --git a/seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts b/seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts new file mode 100644 index 000000000000..f8f6a5f95430 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts @@ -0,0 +1,18 @@ +// This file was auto-generated by Fern from our API Definition. + +export class SeedApiTimeoutError extends Error { + public readonly cause?: unknown; + + constructor(message: string, opts?: { cause?: unknown }) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + + this.name = this.constructor.name; + if (opts?.cause != null) { + this.cause = opts.cause; + } + } +} diff --git a/seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts new file mode 100644 index 000000000000..27d1ebec132d --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts @@ -0,0 +1,40 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as core from "../core/index.js"; +import * as errors from "./index.js"; + +export function handleNonStatusCodeError( + error: core.Fetcher.Error, + rawResponse: core.RawResponse, + method: string, + path: string, +): never { + switch (error.reason) { + case "non-json": + throw new errors.SeedApiError({ + statusCode: error.statusCode, + body: error.rawBody, + rawResponse: rawResponse, + }); + case "body-is-null": + throw new errors.SeedApiError({ + statusCode: error.statusCode, + rawResponse: rawResponse, + }); + case "timeout": + throw new errors.SeedApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`, { + cause: error.cause, + }); + case "unknown": + throw new errors.SeedApiError({ + message: error.errorMessage, + rawResponse: rawResponse, + cause: error.cause, + }); + default: + throw new errors.SeedApiError({ + message: "Unknown error", + rawResponse: rawResponse, + }); + } +} diff --git a/seed/ts-sdk/allof-inline/src/errors/index.ts b/seed/ts-sdk/allof-inline/src/errors/index.ts new file mode 100644 index 000000000000..09e82b954c26 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/errors/index.ts @@ -0,0 +1,2 @@ +export { SeedApiError } from "./SeedApiError.js"; +export { SeedApiTimeoutError } from "./SeedApiTimeoutError.js"; diff --git a/seed/ts-sdk/allof-inline/src/exports.ts b/seed/ts-sdk/allof-inline/src/exports.ts new file mode 100644 index 000000000000..7b70ee14fc02 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/exports.ts @@ -0,0 +1 @@ +export * from "./core/exports.js"; diff --git a/seed/ts-sdk/allof-inline/src/index.ts b/seed/ts-sdk/allof-inline/src/index.ts new file mode 100644 index 000000000000..a11386c163bd --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/index.ts @@ -0,0 +1,6 @@ +export * as SeedApi from "./api/index.js"; +export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; +export { SeedApiClient } from "./Client.js"; +export { SeedApiEnvironment } from "./environments.js"; +export { SeedApiError, SeedApiTimeoutError } from "./errors/index.js"; +export * from "./exports.js"; diff --git a/seed/ts-sdk/allof-inline/src/version.ts b/seed/ts-sdk/allof-inline/src/version.ts new file mode 100644 index 000000000000..b643a3e3ea27 --- /dev/null +++ b/seed/ts-sdk/allof-inline/src/version.ts @@ -0,0 +1 @@ +export const SDK_VERSION = "0.0.1"; diff --git a/seed/ts-sdk/allof-inline/tests/custom.test.ts b/seed/ts-sdk/allof-inline/tests/custom.test.ts new file mode 100644 index 000000000000..7f5e031c8396 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/custom.test.ts @@ -0,0 +1,13 @@ +/** + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ +describe("test", () => { + it("default", () => { + expect(true).toBe(true); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts b/seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts new file mode 100644 index 000000000000..954872157d52 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts @@ -0,0 +1,29 @@ +import type { RequestHandlerOptions } from "msw"; +import type { SetupServer } from "msw/node"; + +import { mockEndpointBuilder } from "./mockEndpointBuilder"; + +export interface MockServerOptions { + baseUrl: string; + server: SetupServer; +} + +export class MockServer { + private readonly server: SetupServer; + public readonly baseUrl: string; + + constructor({ baseUrl, server }: MockServerOptions) { + this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl; + this.server = server; + } + + public mockEndpoint(options?: RequestHandlerOptions): ReturnType { + const builder = mockEndpointBuilder({ + once: options?.once ?? true, + onBuild: (handler) => { + this.server.use(handler); + }, + }).baseUrl(this.baseUrl); + return builder; + } +} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts b/seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts new file mode 100644 index 000000000000..d7d891a2d80b --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts @@ -0,0 +1,106 @@ +import { setupServer } from "msw/node"; + +import { fromJson, toJson } from "../../src/core/json"; +import { MockServer } from "./MockServer"; +import { randomBaseUrl } from "./randomBaseUrl"; + +const mswServer = setupServer(); +interface MockServerOptions { + baseUrl?: string; +} + +async function formatHttpRequest(request: Request, id?: string): Promise { + try { + const clone = request.clone(); + const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); + + let body = ""; + try { + const contentType = clone.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = toJson(fromJson(await clone.text()), undefined, 2); + } else if (clone.body) { + body = await clone.text(); + } + } catch (_e) { + body = "(unable to parse body)"; + } + + const title = id ? `### Request ${id} ###\n` : ""; + const firstLine = `${title}${request.method} ${request.url.toString()} HTTP/1.1`; + + return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; + } catch (e) { + return `Error formatting request: ${e}`; + } +} + +async function formatHttpResponse(response: Response, id?: string): Promise { + try { + const clone = response.clone(); + const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); + + let body = ""; + try { + const contentType = clone.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = toJson(fromJson(await clone.text()), undefined, 2); + } else if (clone.body) { + body = await clone.text(); + } + } catch (_e) { + body = "(unable to parse body)"; + } + + const title = id ? `### Response for ${id} ###\n` : ""; + const firstLine = `${title}HTTP/1.1 ${response.status} ${response.statusText}`; + + return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; + } catch (e) { + return `Error formatting response: ${e}`; + } +} + +class MockServerPool { + private servers: MockServer[] = []; + + public createServer(options?: Partial): MockServer { + const baseUrl = options?.baseUrl || randomBaseUrl(); + const server = new MockServer({ baseUrl, server: mswServer }); + this.servers.push(server); + return server; + } + + public getServers(): MockServer[] { + return [...this.servers]; + } + + public listen(): void { + const onUnhandledRequest = process.env.LOG_LEVEL === "debug" ? "warn" : "bypass"; + mswServer.listen({ onUnhandledRequest }); + + if (process.env.LOG_LEVEL === "debug") { + mswServer.events.on("request:start", async ({ request, requestId }) => { + const formattedRequest = await formatHttpRequest(request, requestId); + console.debug(`request:start\n${formattedRequest}`); + }); + + mswServer.events.on("request:unhandled", async ({ request, requestId }) => { + const formattedRequest = await formatHttpRequest(request, requestId); + console.debug(`request:unhandled\n${formattedRequest}`); + }); + + mswServer.events.on("response:mocked", async ({ request, response, requestId }) => { + const formattedResponse = await formatHttpResponse(response, requestId); + console.debug(`response:mocked\n${formattedResponse}`); + }); + } + } + + public close(): void { + this.servers = []; + mswServer.close(); + } +} + +export const mockServerPool: MockServerPool = new MockServerPool(); diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts b/seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts new file mode 100644 index 000000000000..3e8540a3ba5a --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts @@ -0,0 +1,234 @@ +import { type DefaultBodyType, type HttpHandler, HttpResponse, type HttpResponseResolver, http } from "msw"; + +import { url } from "../../src/core"; +import { toJson } from "../../src/core/json"; +import { type WithFormUrlEncodedOptions, withFormUrlEncoded } from "./withFormUrlEncoded"; +import { withHeaders } from "./withHeaders"; +import { type WithJsonOptions, withJson } from "./withJson"; + +type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; + +interface MethodStage { + baseUrl(baseUrl: string): MethodStage; + all(path: string): RequestHeadersStage; + get(path: string): RequestHeadersStage; + post(path: string): RequestHeadersStage; + put(path: string): RequestHeadersStage; + delete(path: string): RequestHeadersStage; + patch(path: string): RequestHeadersStage; + options(path: string): RequestHeadersStage; + head(path: string): RequestHeadersStage; +} + +interface RequestHeadersStage extends RequestBodyStage, ResponseStage { + header(name: string, value: string): RequestHeadersStage; + headers(headers: Record): RequestBodyStage; +} + +interface RequestBodyStage extends ResponseStage { + jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage; + formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage; +} + +interface ResponseStage { + respondWith(): ResponseStatusStage; +} +interface ResponseStatusStage { + statusCode(statusCode: number): ResponseHeaderStage; +} + +interface ResponseHeaderStage extends ResponseBodyStage, BuildStage { + header(name: string, value: string): ResponseHeaderStage; + headers(headers: Record): ResponseHeaderStage; +} + +interface ResponseBodyStage { + jsonBody(body: unknown): BuildStage; + sseBody(body: string): BuildStage; +} + +interface BuildStage { + build(): HttpHandler; +} + +export interface HttpHandlerBuilderOptions { + onBuild?: (handler: HttpHandler) => void; + once?: boolean; +} + +class RequestBuilder implements MethodStage, RequestHeadersStage, RequestBodyStage, ResponseStage { + private method: HttpMethod = "get"; + private _baseUrl: string = ""; + private path: string = "/"; + private readonly predicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[] = []; + private readonly handlerOptions?: HttpHandlerBuilderOptions; + + constructor(options?: HttpHandlerBuilderOptions) { + this.handlerOptions = options; + } + + baseUrl(baseUrl: string): MethodStage { + this._baseUrl = baseUrl; + return this; + } + + all(path: string): RequestHeadersStage { + this.method = "all"; + this.path = path; + return this; + } + + get(path: string): RequestHeadersStage { + this.method = "get"; + this.path = path; + return this; + } + + post(path: string): RequestHeadersStage { + this.method = "post"; + this.path = path; + return this; + } + + put(path: string): RequestHeadersStage { + this.method = "put"; + this.path = path; + return this; + } + + delete(path: string): RequestHeadersStage { + this.method = "delete"; + this.path = path; + return this; + } + + patch(path: string): RequestHeadersStage { + this.method = "patch"; + this.path = path; + return this; + } + + options(path: string): RequestHeadersStage { + this.method = "options"; + this.path = path; + return this; + } + + head(path: string): RequestHeadersStage { + this.method = "head"; + this.path = path; + return this; + } + + header(name: string, value: string): RequestHeadersStage { + this.predicates.push((resolver) => withHeaders({ [name]: value }, resolver)); + return this; + } + + headers(headers: Record): RequestBodyStage { + this.predicates.push((resolver) => withHeaders(headers, resolver)); + return this; + } + + jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage { + if (body === undefined) { + throw new Error("Undefined is not valid JSON. Do not call jsonBody if you want an empty body."); + } + this.predicates.push((resolver) => withJson(body, resolver, options)); + return this; + } + + formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage { + if (body === undefined) { + throw new Error( + "Undefined is not valid for form-urlencoded. Do not call formUrlEncodedBody if you want an empty body.", + ); + } + this.predicates.push((resolver) => withFormUrlEncoded(body, resolver, options)); + return this; + } + + respondWith(): ResponseStatusStage { + return new ResponseBuilder(this.method, this.buildUrl(), this.predicates, this.handlerOptions); + } + + private buildUrl(): string { + return url.join(this._baseUrl, this.path); + } +} + +class ResponseBuilder implements ResponseStatusStage, ResponseHeaderStage, ResponseBodyStage, BuildStage { + private readonly method: HttpMethod; + private readonly url: string; + private readonly requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[]; + private readonly handlerOptions?: HttpHandlerBuilderOptions; + + private responseStatusCode: number = 200; + private responseHeaders: Record = {}; + private responseBody: DefaultBodyType = undefined; + + constructor( + method: HttpMethod, + url: string, + requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[], + options?: HttpHandlerBuilderOptions, + ) { + this.method = method; + this.url = url; + this.requestPredicates = requestPredicates; + this.handlerOptions = options; + } + + public statusCode(code: number): ResponseHeaderStage { + this.responseStatusCode = code; + return this; + } + + public header(name: string, value: string): ResponseHeaderStage { + this.responseHeaders[name] = value; + return this; + } + + public headers(headers: Record): ResponseHeaderStage { + this.responseHeaders = { ...this.responseHeaders, ...headers }; + return this; + } + + public jsonBody(body: unknown): BuildStage { + if (body === undefined) { + throw new Error("Undefined is not valid JSON. Do not call jsonBody if you expect an empty body."); + } + this.responseBody = toJson(body); + return this; + } + + public sseBody(body: string): BuildStage { + this.responseHeaders["Content-Type"] = "text/event-stream"; + this.responseBody = body; + return this; + } + + public build(): HttpHandler { + const responseResolver: HttpResponseResolver = () => { + const response = new HttpResponse(this.responseBody, { + status: this.responseStatusCode, + headers: this.responseHeaders, + }); + // if no Content-Type header is set, delete the default text content type that is set + if (Object.keys(this.responseHeaders).some((key) => key.toLowerCase() === "content-type") === false) { + response.headers.delete("Content-Type"); + } + return response; + }; + + const finalResolver = this.requestPredicates.reduceRight((acc, predicate) => predicate(acc), responseResolver); + + const handler = http[this.method](this.url, finalResolver, this.handlerOptions); + this.handlerOptions?.onBuild?.(handler); + return handler; + } +} + +export function mockEndpointBuilder(options?: HttpHandlerBuilderOptions): MethodStage { + return new RequestBuilder(options); +} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts b/seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts new file mode 100644 index 000000000000..031aa6408aca --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts @@ -0,0 +1,4 @@ +export function randomBaseUrl(): string { + const randomString = Math.random().toString(36).substring(2, 15); + return `http://${randomString}.localhost`; +} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/setup.ts b/seed/ts-sdk/allof-inline/tests/mock-server/setup.ts new file mode 100644 index 000000000000..aeb3a95af7dc --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/setup.ts @@ -0,0 +1,10 @@ +import { afterAll, beforeAll } from "vitest"; + +import { mockServerPool } from "./MockServerPool"; + +beforeAll(() => { + mockServerPool.listen(); +}); +afterAll(() => { + mockServerPool.close(); +}); diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts b/seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts new file mode 100644 index 000000000000..2b23448e3102 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts @@ -0,0 +1,104 @@ +import { type HttpResponseResolver, passthrough } from "msw"; + +import { toJson } from "../../src/core/json"; + +export interface WithFormUrlEncodedOptions { + /** + * List of field names to ignore when comparing request bodies. + * This is useful for pagination cursor fields that change between requests. + */ + ignoredFields?: string[]; +} + +/** + * Creates a request matcher that validates if the request form-urlencoded body exactly matches the expected object + * @param expectedBody - The exact body object to match against + * @param resolver - Response resolver to execute if body matches + * @param options - Optional configuration including fields to ignore + */ +export function withFormUrlEncoded( + expectedBody: unknown, + resolver: HttpResponseResolver, + options?: WithFormUrlEncodedOptions, +): HttpResponseResolver { + const ignoredFields = options?.ignoredFields ?? []; + return async (args) => { + const { request } = args; + + let clonedRequest: Request; + let bodyText: string | undefined; + let actualBody: Record; + try { + clonedRequest = request.clone(); + bodyText = await clonedRequest.text(); + if (bodyText === "") { + // Empty body is valid if expected body is also empty + const isExpectedEmpty = + expectedBody != null && + typeof expectedBody === "object" && + Object.keys(expectedBody as Record).length === 0; + if (!isExpectedEmpty) { + console.error("Request body is empty, expected a form-urlencoded body."); + return passthrough(); + } + actualBody = {}; + } else { + const params = new URLSearchParams(bodyText); + actualBody = {}; + for (const [key, value] of params.entries()) { + actualBody[key] = value; + } + } + } catch (error) { + console.error(`Error processing form-urlencoded request body:\n\tError: ${error}\n\tBody: ${bodyText}`); + return passthrough(); + } + + const mismatches = findMismatches(actualBody, expectedBody); + const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); + if (filteredMismatches.length > 0) { + console.error("Form-urlencoded body mismatch:", toJson(mismatches, undefined, 2)); + return passthrough(); + } + + return resolver(args); + }; +} + +function findMismatches(actual: any, expected: any): Record { + const mismatches: Record = {}; + + if (typeof actual !== typeof expected) { + return { value: { actual, expected } }; + } + + if (typeof actual !== "object" || actual === null || expected === null) { + if (actual !== expected) { + return { value: { actual, expected } }; + } + return {}; + } + + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + + const allKeys = new Set([...actualKeys, ...expectedKeys]); + + for (const key of allKeys) { + if (!expectedKeys.includes(key)) { + if (actual[key] === undefined) { + continue; + } + mismatches[key] = { actual: actual[key], expected: undefined }; + } else if (!actualKeys.includes(key)) { + if (expected[key] === undefined) { + continue; + } + mismatches[key] = { actual: undefined, expected: expected[key] }; + } else if (actual[key] !== expected[key]) { + mismatches[key] = { actual: actual[key], expected: expected[key] }; + } + } + + return mismatches; +} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts b/seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts new file mode 100644 index 000000000000..6599d2b4a92d --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts @@ -0,0 +1,70 @@ +import { type HttpResponseResolver, passthrough } from "msw"; + +/** + * Creates a request matcher that validates if request headers match specified criteria + * @param expectedHeaders - Headers to match against + * @param resolver - Response resolver to execute if headers match + */ +export function withHeaders( + expectedHeaders: Record boolean)>, + resolver: HttpResponseResolver, +): HttpResponseResolver { + return (args) => { + const { request } = args; + const { headers } = request; + + const mismatches: Record< + string, + { actual: string | null; expected: string | RegExp | ((value: string) => boolean) } + > = {}; + + for (const [key, expectedValue] of Object.entries(expectedHeaders)) { + const actualValue = headers.get(key); + + if (actualValue === null) { + mismatches[key] = { actual: null, expected: expectedValue }; + continue; + } + + if (typeof expectedValue === "function") { + if (!expectedValue(actualValue)) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } else if (expectedValue instanceof RegExp) { + if (!expectedValue.test(actualValue)) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } else if (expectedValue !== actualValue) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } + + if (Object.keys(mismatches).length > 0) { + const formattedMismatches = formatHeaderMismatches(mismatches); + console.error("Header mismatch:", formattedMismatches); + return passthrough(); + } + + return resolver(args); + }; +} + +function formatHeaderMismatches( + mismatches: Record boolean) }>, +): Record { + const formatted: Record = {}; + + for (const [key, { actual, expected }] of Object.entries(mismatches)) { + formatted[key] = { + actual, + expected: + expected instanceof RegExp + ? expected.toString() + : typeof expected === "function" + ? "[Function]" + : expected, + }; + } + + return formatted; +} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts b/seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts new file mode 100644 index 000000000000..3e8800a0c374 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts @@ -0,0 +1,173 @@ +import { type HttpResponseResolver, passthrough } from "msw"; + +import { fromJson, toJson } from "../../src/core/json"; + +export interface WithJsonOptions { + /** + * List of field names to ignore when comparing request bodies. + * This is useful for pagination cursor fields that change between requests. + */ + ignoredFields?: string[]; +} + +/** + * Creates a request matcher that validates if the request JSON body exactly matches the expected object + * @param expectedBody - The exact body object to match against + * @param resolver - Response resolver to execute if body matches + * @param options - Optional configuration including fields to ignore + */ +export function withJson( + expectedBody: unknown, + resolver: HttpResponseResolver, + options?: WithJsonOptions, +): HttpResponseResolver { + const ignoredFields = options?.ignoredFields ?? []; + return async (args) => { + const { request } = args; + + let clonedRequest: Request; + let bodyText: string | undefined; + let actualBody: unknown; + try { + clonedRequest = request.clone(); + bodyText = await clonedRequest.text(); + if (bodyText === "") { + console.error("Request body is empty, expected a JSON object."); + return passthrough(); + } + actualBody = fromJson(bodyText); + } catch (error) { + console.error(`Error processing request body:\n\tError: ${error}\n\tBody: ${bodyText}`); + return passthrough(); + } + + const mismatches = findMismatches(actualBody, expectedBody); + const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); + if (filteredMismatches.length > 0) { + console.error("JSON body mismatch:", toJson(mismatches, undefined, 2)); + return passthrough(); + } + + return resolver(args); + }; +} + +function findMismatches(actual: any, expected: any): Record { + const mismatches: Record = {}; + + if (typeof actual !== typeof expected) { + if (areEquivalent(actual, expected)) { + return {}; + } + return { value: { actual, expected } }; + } + + if (typeof actual !== "object" || actual === null || expected === null) { + if (actual !== expected) { + if (areEquivalent(actual, expected)) { + return {}; + } + return { value: { actual, expected } }; + } + return {}; + } + + if (Array.isArray(actual) && Array.isArray(expected)) { + if (actual.length !== expected.length) { + return { length: { actual: actual.length, expected: expected.length } }; + } + + const arrayMismatches: Record = {}; + for (let i = 0; i < actual.length; i++) { + const itemMismatches = findMismatches(actual[i], expected[i]); + if (Object.keys(itemMismatches).length > 0) { + for (const [mismatchKey, mismatchValue] of Object.entries(itemMismatches)) { + arrayMismatches[`[${i}]${mismatchKey === "value" ? "" : `.${mismatchKey}`}`] = mismatchValue; + } + } + } + return arrayMismatches; + } + + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + + const allKeys = new Set([...actualKeys, ...expectedKeys]); + + for (const key of allKeys) { + if (!expectedKeys.includes(key)) { + if (actual[key] === undefined) { + continue; // Skip undefined values in actual + } + mismatches[key] = { actual: actual[key], expected: undefined }; + } else if (!actualKeys.includes(key)) { + if (expected[key] === undefined) { + continue; // Skip undefined values in expected + } + mismatches[key] = { actual: undefined, expected: expected[key] }; + } else if ( + typeof actual[key] === "object" && + actual[key] !== null && + typeof expected[key] === "object" && + expected[key] !== null + ) { + const nestedMismatches = findMismatches(actual[key], expected[key]); + if (Object.keys(nestedMismatches).length > 0) { + for (const [nestedKey, nestedValue] of Object.entries(nestedMismatches)) { + mismatches[`${key}${nestedKey === "value" ? "" : `.${nestedKey}`}`] = nestedValue; + } + } + } else if (actual[key] !== expected[key]) { + if (areEquivalent(actual[key], expected[key])) { + continue; + } + mismatches[key] = { actual: actual[key], expected: expected[key] }; + } + } + + return mismatches; +} + +function areEquivalent(actual: unknown, expected: unknown): boolean { + if (actual === expected) { + return true; + } + if (isEquivalentBigInt(actual, expected)) { + return true; + } + if (isEquivalentDatetime(actual, expected)) { + return true; + } + return false; +} + +function isEquivalentBigInt(actual: unknown, expected: unknown) { + if (typeof actual === "number") { + actual = BigInt(actual); + } + if (typeof expected === "number") { + expected = BigInt(expected); + } + if (typeof actual === "bigint" && typeof expected === "bigint") { + return actual === expected; + } + return false; +} + +function isEquivalentDatetime(str1: unknown, str2: unknown): boolean { + if (typeof str1 !== "string" || typeof str2 !== "string") { + return false; + } + const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/; + if (!isoDatePattern.test(str1) || !isoDatePattern.test(str2)) { + return false; + } + + try { + const date1 = new Date(str1).getTime(); + const date2 = new Date(str2).getTime(); + return date1 === date2; + } catch { + return false; + } +} diff --git a/seed/ts-sdk/allof-inline/tests/setup.ts b/seed/ts-sdk/allof-inline/tests/setup.ts new file mode 100644 index 000000000000..a5651f81ba10 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/setup.ts @@ -0,0 +1,80 @@ +import { expect } from "vitest"; + +interface CustomMatchers { + toContainHeaders(expectedHeaders: Record): R; +} + +declare module "vitest" { + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} +} + +expect.extend({ + toContainHeaders(actual: unknown, expectedHeaders: Record) { + const isHeaders = actual instanceof Headers; + const isPlainObject = typeof actual === "object" && actual !== null && !Array.isArray(actual); + + if (!isHeaders && !isPlainObject) { + throw new TypeError("Received value must be an instance of Headers or a plain object!"); + } + + if (typeof expectedHeaders !== "object" || expectedHeaders === null || Array.isArray(expectedHeaders)) { + throw new TypeError("Expected headers must be a plain object!"); + } + + const missingHeaders: string[] = []; + const mismatchedHeaders: Array<{ key: string; expected: string; actual: string | null }> = []; + + for (const [key, value] of Object.entries(expectedHeaders)) { + let actualValue: string | null = null; + + if (isHeaders) { + // Headers.get() is already case-insensitive + actualValue = (actual as Headers).get(key); + } else { + // For plain objects, do case-insensitive lookup + const actualObj = actual as Record; + const lowerKey = key.toLowerCase(); + const foundKey = Object.keys(actualObj).find((k) => k.toLowerCase() === lowerKey); + actualValue = foundKey ? actualObj[foundKey] : null; + } + + if (actualValue === null || actualValue === undefined) { + missingHeaders.push(key); + } else if (actualValue !== value) { + mismatchedHeaders.push({ key, expected: value, actual: actualValue }); + } + } + + const pass = missingHeaders.length === 0 && mismatchedHeaders.length === 0; + + const actualType = isHeaders ? "Headers" : "object"; + + if (pass) { + return { + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, + pass: true, + }; + } else { + const messages: string[] = []; + + if (missingHeaders.length > 0) { + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); + } + + if (mismatchedHeaders.length > 0) { + const mismatches = mismatchedHeaders.map( + ({ key, expected, actual }) => + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, + ); + messages.push(mismatches.join("\n")); + } + + return { + message: () => + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, + pass: false, + }; + } + }, +}); diff --git a/seed/ts-sdk/allof-inline/tests/tsconfig.json b/seed/ts-sdk/allof-inline/tests/tsconfig.json new file mode 100644 index 000000000000..ac39744de7b2 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": null, + "rootDir": "..", + "types": ["vitest/globals"] + }, + "include": ["../src", "../tests"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts new file mode 100644 index 000000000000..6c17624228bb --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts @@ -0,0 +1,262 @@ +import fs from "fs"; +import { join } from "path"; +import stream from "stream"; +import type { BinaryResponse } from "../../../src/core"; +import { type Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +describe("Test fetcherImpl", () => { + it("should handle successful request", async () => { + const mockArgs: Fetcher.Args = { + url: "https://httpbin.org/post", + method: "POST", + headers: { "X-Test": "x-test-header" }, + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + maxRetries: 0, + responseType: "json", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + }), + ); + + const result = await fetcherImpl(mockArgs); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + + expect(global.fetch).toHaveBeenCalledWith( + "https://httpbin.org/post", + expect.objectContaining({ + method: "POST", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + body: JSON.stringify({ data: "test" }), + }), + ); + }); + + it("should send octet stream", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "POST", + headers: { "X-Test": "x-test-header" }, + contentType: "application/octet-stream", + requestType: "bytes", + maxRetries: 0, + responseType: "json", + body: fs.createReadStream(join(__dirname, "test-file.txt")), + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + }), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "POST", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + body: expect.any(fs.ReadStream), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + }); + + it("should receive file as stream", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.stream).toBe("function"); + const stream = body.stream(); + expect(stream).toBeInstanceOf(ReadableStream); + const readableStream = stream as ReadableStream; + const reader = readableStream.getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(value); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as blob", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.blob).toBe("function"); + const blob = await body.blob(); + expect(blob).toBeInstanceOf(Blob); + const reader = blob.stream().getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(value); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as arraybuffer", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.arrayBuffer).toBe("function"); + const arrayBuffer = await body.arrayBuffer(); + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(new Uint8Array(arrayBuffer)); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as bytes", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.bytes).toBe("function"); + if (!body.bytes) { + return; + } + const bytes = await body.bytes(); + expect(bytes).toBeInstanceOf(Uint8Array); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(bytes); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts new file mode 100644 index 000000000000..2ec008e581d8 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts @@ -0,0 +1,143 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { HttpResponsePromise } from "../../../src/core/fetcher/HttpResponsePromise"; +import type { RawResponse, WithRawResponse } from "../../../src/core/fetcher/RawResponse"; + +describe("HttpResponsePromise", () => { + const mockRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 200, + statusText: "OK", + type: "basic" as ResponseType, + url: "https://example.com", + }; + const mockData = { id: "123", name: "test" }; + const mockWithRawResponse: WithRawResponse = { + data: mockData, + rawResponse: mockRawResponse, + }; + + describe("fromFunction", () => { + it("should create an HttpResponsePromise from a function", async () => { + const mockFn = vi + .fn<(arg1: string, arg2: string) => Promise>>() + .mockResolvedValue(mockWithRawResponse); + + const responsePromise = HttpResponsePromise.fromFunction(mockFn, "arg1", "arg2"); + + const result = await responsePromise; + expect(result).toEqual(mockData); + expect(mockFn).toHaveBeenCalledWith("arg1", "arg2"); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromPromise", () => { + it("should create an HttpResponsePromise from a promise", async () => { + const promise = Promise.resolve(mockWithRawResponse); + + const responsePromise = HttpResponsePromise.fromPromise(promise); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromExecutor", () => { + it("should create an HttpResponsePromise from an executor function", async () => { + const responsePromise = HttpResponsePromise.fromExecutor((resolve) => { + resolve(mockWithRawResponse); + }); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromResult", () => { + it("should create an HttpResponsePromise from a result", async () => { + const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("Promise methods", () => { + let responsePromise: HttpResponsePromise; + + beforeEach(() => { + responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + }); + + it("should support then() method", async () => { + const result = await responsePromise.then((data) => ({ + ...data, + modified: true, + })); + + expect(result).toEqual({ + ...mockData, + modified: true, + }); + }); + + it("should support catch() method", async () => { + const errorResponsePromise = HttpResponsePromise.fromExecutor((_, reject) => { + reject(new Error("Test error")); + }); + + const catchSpy = vi.fn(); + await errorResponsePromise.catch(catchSpy); + + expect(catchSpy).toHaveBeenCalled(); + const error = catchSpy.mock.calls[0]?.[0]; + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe("Test error"); + }); + + it("should support finally() method", async () => { + const finallySpy = vi.fn(); + await responsePromise.finally(finallySpy); + + expect(finallySpy).toHaveBeenCalled(); + }); + }); + + describe("withRawResponse", () => { + it("should return both data and raw response", async () => { + const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + + const result = await responsePromise.withRawResponse(); + + expect(result).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts new file mode 100644 index 000000000000..375ee3f38064 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; + +import { toRawResponse } from "../../../src/core/fetcher/RawResponse"; + +describe("RawResponse", () => { + describe("toRawResponse", () => { + it("should convert Response to RawResponse by removing body, bodyUsed, and ok properties", () => { + const mockHeaders = new Headers({ "content-type": "application/json" }); + const mockResponse = { + body: "test body", + bodyUsed: false, + ok: true, + headers: mockHeaders, + redirected: false, + status: 200, + statusText: "OK", + type: "basic" as ResponseType, + url: "https://example.com", + }; + + const result = toRawResponse(mockResponse as unknown as Response); + + expect("body" in result).toBe(false); + expect("bodyUsed" in result).toBe(false); + expect("ok" in result).toBe(false); + expect(result.headers).toBe(mockHeaders); + expect(result.redirected).toBe(false); + expect(result.status).toBe(200); + expect(result.statusText).toBe("OK"); + expect(result.type).toBe("basic"); + expect(result.url).toBe("https://example.com"); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts new file mode 100644 index 000000000000..a92f1b5e81d1 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts @@ -0,0 +1,163 @@ +import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; + +describe("Test createRequestUrl", () => { + const BASE_URL = "https://api.example.com"; + + interface TestCase { + description: string; + baseUrl: string; + queryParams?: Record; + expected: string; + } + + const testCases: TestCase[] = [ + { + description: "should return the base URL when no query parameters are provided", + baseUrl: BASE_URL, + expected: BASE_URL, + }, + { + description: "should append simple query parameters", + baseUrl: BASE_URL, + queryParams: { key: "value", another: "param" }, + expected: "https://api.example.com?key=value&another=param", + }, + { + description: "should handle array query parameters", + baseUrl: BASE_URL, + queryParams: { items: ["a", "b", "c"] }, + expected: "https://api.example.com?items=a&items=b&items=c", + }, + { + description: "should handle object query parameters", + baseUrl: BASE_URL, + queryParams: { filter: { name: "John", age: 30 } }, + expected: "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30", + }, + { + description: "should handle mixed types of query parameters", + baseUrl: BASE_URL, + queryParams: { + simple: "value", + array: ["x", "y"], + object: { key: "value" }, + }, + expected: "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value", + }, + { + description: "should handle empty query parameters object", + baseUrl: BASE_URL, + queryParams: {}, + expected: BASE_URL, + }, + { + description: "should encode special characters in query parameters", + baseUrl: BASE_URL, + queryParams: { special: "a&b=c d" }, + expected: "https://api.example.com?special=a%26b%3Dc%20d", + }, + { + description: "should handle numeric values", + baseUrl: BASE_URL, + queryParams: { count: 42, price: 19.99, active: 1, inactive: 0 }, + expected: "https://api.example.com?count=42&price=19.99&active=1&inactive=0", + }, + { + description: "should handle boolean values", + baseUrl: BASE_URL, + queryParams: { enabled: true, disabled: false }, + expected: "https://api.example.com?enabled=true&disabled=false", + }, + { + description: "should handle null and undefined values", + baseUrl: BASE_URL, + queryParams: { + valid: "value", + nullValue: null, + undefinedValue: undefined, + emptyString: "", + }, + expected: "https://api.example.com?valid=value&nullValue=&emptyString=", + }, + { + description: "should handle deeply nested objects", + baseUrl: BASE_URL, + queryParams: { + user: { + profile: { + name: "John", + settings: { theme: "dark" }, + }, + }, + }, + expected: + "https://api.example.com?user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", + }, + { + description: "should handle arrays of objects", + baseUrl: BASE_URL, + queryParams: { + users: [ + { name: "John", age: 30 }, + { name: "Jane", age: 25 }, + ], + }, + expected: + "https://api.example.com?users%5Bname%5D=John&users%5Bage%5D=30&users%5Bname%5D=Jane&users%5Bage%5D=25", + }, + { + description: "should handle mixed arrays", + baseUrl: BASE_URL, + queryParams: { + mixed: ["string", 42, true, { key: "value" }], + }, + expected: "https://api.example.com?mixed=string&mixed=42&mixed=true&mixed%5Bkey%5D=value", + }, + { + description: "should handle empty arrays", + baseUrl: BASE_URL, + queryParams: { emptyArray: [] }, + expected: BASE_URL, + }, + { + description: "should handle empty objects", + baseUrl: BASE_URL, + queryParams: { emptyObject: {} }, + expected: BASE_URL, + }, + { + description: "should handle special characters in keys", + baseUrl: BASE_URL, + queryParams: { "key with spaces": "value", "key[with]brackets": "value" }, + expected: "https://api.example.com?key%20with%20spaces=value&key%5Bwith%5Dbrackets=value", + }, + { + description: "should handle URL with existing query parameters", + baseUrl: "https://api.example.com?existing=param", + queryParams: { new: "value" }, + expected: "https://api.example.com?existing=param?new=value", + }, + { + description: "should handle complex nested structures", + baseUrl: BASE_URL, + queryParams: { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }, + expected: + "https://api.example.com?filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + }, + ]; + + testCases.forEach(({ description, baseUrl, queryParams, expected }) => { + it(description, () => { + expect(createRequestUrl(baseUrl, queryParams)).toBe(expected); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts new file mode 100644 index 000000000000..8a6c3a57e211 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts @@ -0,0 +1,129 @@ +import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; +import { RUNTIME } from "../../../src/core/runtime"; + +describe("Test getRequestBody", () => { + interface TestCase { + description: string; + input: any; + type: "json" | "form" | "file" | "bytes" | "other"; + expected: any; + skipCondition?: () => boolean; + } + + const testCases: TestCase[] = [ + { + description: "should stringify body if not FormData in Node environment", + input: { key: "value" }, + type: "json", + expected: '{"key":"value"}', + skipCondition: () => RUNTIME.type !== "node", + }, + { + description: "should stringify body if not FormData in browser environment", + input: { key: "value" }, + type: "json", + expected: '{"key":"value"}', + skipCondition: () => RUNTIME.type !== "browser", + }, + { + description: "should return the Uint8Array", + input: new Uint8Array([1, 2, 3]), + type: "bytes", + expected: new Uint8Array([1, 2, 3]), + }, + { + description: "should serialize objects for form-urlencoded content type", + input: { username: "johndoe", email: "john@example.com" }, + type: "form", + expected: "username=johndoe&email=john%40example.com", + }, + { + description: "should serialize complex nested objects and arrays for form-urlencoded content type", + input: { + user: { + profile: { + name: "John Doe", + settings: { + theme: "dark", + notifications: true, + }, + }, + tags: ["admin", "user"], + contacts: [ + { type: "email", value: "john@example.com" }, + { type: "phone", value: "+1234567890" }, + ], + }, + filters: { + status: ["active", "pending"], + metadata: { + created: "2024-01-01", + categories: ["electronics", "books"], + }, + }, + preferences: ["notifications", "updates"], + }, + type: "form", + expected: + "user%5Bprofile%5D%5Bname%5D=John%20Doe&" + + "user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark&" + + "user%5Bprofile%5D%5Bsettings%5D%5Bnotifications%5D=true&" + + "user%5Btags%5D=admin&" + + "user%5Btags%5D=user&" + + "user%5Bcontacts%5D%5Btype%5D=email&" + + "user%5Bcontacts%5D%5Bvalue%5D=john%40example.com&" + + "user%5Bcontacts%5D%5Btype%5D=phone&" + + "user%5Bcontacts%5D%5Bvalue%5D=%2B1234567890&" + + "filters%5Bstatus%5D=active&" + + "filters%5Bstatus%5D=pending&" + + "filters%5Bmetadata%5D%5Bcreated%5D=2024-01-01&" + + "filters%5Bmetadata%5D%5Bcategories%5D=electronics&" + + "filters%5Bmetadata%5D%5Bcategories%5D=books&" + + "preferences=notifications&" + + "preferences=updates", + }, + { + description: "should return the input for pre-serialized form-urlencoded strings", + input: "key=value&another=param", + type: "other", + expected: "key=value&another=param", + }, + { + description: "should JSON stringify objects", + input: { key: "value" }, + type: "json", + expected: '{"key":"value"}', + }, + ]; + + testCases.forEach(({ description, input, type, expected, skipCondition }) => { + it(description, async () => { + if (skipCondition?.()) { + return; + } + + const result = await getRequestBody({ + body: input, + type, + }); + + if (input instanceof Uint8Array) { + expect(result).toBe(input); + } else { + expect(result).toBe(expected); + } + }); + }); + + it("should return FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const formData = new FormData(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts new file mode 100644 index 000000000000..ad6be7fc2c9b --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts @@ -0,0 +1,97 @@ +import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; + +import { RUNTIME } from "../../../src/core/runtime"; + +describe("Test getResponseBody", () => { + interface SimpleTestCase { + description: string; + responseData: string | Record; + responseType?: "blob" | "sse" | "streaming" | "text"; + expected: any; + skipCondition?: () => boolean; + } + + const simpleTestCases: SimpleTestCase[] = [ + { + description: "should handle text response type", + responseData: "test text", + responseType: "text", + expected: "test text", + }, + { + description: "should handle JSON response", + responseData: { key: "value" }, + expected: { key: "value" }, + }, + { + description: "should handle empty response", + responseData: "", + expected: undefined, + }, + { + description: "should handle non-JSON response", + responseData: "invalid json", + expected: { + ok: false, + error: { + reason: "non-json", + statusCode: 200, + rawBody: "invalid json", + }, + }, + }, + ]; + + simpleTestCases.forEach(({ description, responseData, responseType, expected, skipCondition }) => { + it(description, async () => { + if (skipCondition?.()) { + return; + } + + const mockResponse = new Response( + typeof responseData === "string" ? responseData : JSON.stringify(responseData), + ); + const result = await getResponseBody(mockResponse, responseType); + expect(result).toEqual(expected); + }); + }); + + it("should handle blob response type", async () => { + const mockBlob = new Blob(["test"], { type: "text/plain" }); + const mockResponse = new Response(mockBlob); + const result = await getResponseBody(mockResponse, "blob"); + // @ts-expect-error + expect(result.constructor.name).toBe("Blob"); + }); + + it("should handle sse response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "sse"); + expect(result).toBe(mockStream); + } + }); + + it("should handle streaming response type", async () => { + const encoder = new TextEncoder(); + const testData = "test stream data"; + const mockStream = new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(testData)); + controller.close(); + }, + }); + + const mockResponse = new Response(mockStream); + const result = (await getResponseBody(mockResponse, "streaming")) as ReadableStream; + + expect(result).toBeInstanceOf(ReadableStream); + + const reader = result.getReader(); + const decoder = new TextDecoder(); + const { value } = await reader.read(); + const streamContent = decoder.decode(value); + expect(streamContent).toBe(testData); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts new file mode 100644 index 000000000000..366c9b6ced61 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts @@ -0,0 +1,517 @@ +import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +function createMockLogger() { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(data), { + status, + statusText, + }), + ); +} + +function mockErrorResponse(data: unknown = { error: "Error" }, status = 404, statusText = "Not Found") { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(data), { + status, + statusText, + }), + ); +} + +describe("Fetcher Logging Integration", () => { + describe("Request Logging", () => { + it("should log successful request at debug level", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + headers: { "Content-Type": "application/json" }, + body: { test: "data" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "POST", + url: "https://example.com/api", + headers: expect.toContainHeaders({ + "Content-Type": "application/json", + }), + hasBody: true, + }), + ); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + method: "POST", + url: "https://example.com/api", + statusCode: 200, + }), + ); + }); + + it("should not log debug messages at info level for successful requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "info", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + }); + + it("should log request with body flag", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + hasBody: true, + }), + ); + }); + + it("should log request without body flag", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + hasBody: false, + }), + ); + }); + + it("should not log when silent mode is enabled", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: true, + }, + }); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); + + it("should not log when no logging config is provided", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + }); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + }); + }); + + describe("Error Logging", () => { + it("should log 4xx errors at error level", async () => { + const mockLogger = createMockLogger(); + mockErrorResponse({ error: "Not found" }, 404, "Not Found"); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + statusCode: 404, + }), + ); + }); + + it("should log 5xx errors at error level", async () => { + const mockLogger = createMockLogger(); + mockErrorResponse({ error: "Internal error" }, 500, "Internal Server Error"); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + statusCode: 500, + }), + ); + }); + + it("should log aborted request errors", async () => { + const mockLogger = createMockLogger(); + + const abortController = new AbortController(); + abortController.abort(); + + global.fetch = vi.fn().mockRejectedValue(new Error("Aborted")); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + abortSignal: abortController.signal, + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request was aborted", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + }), + ); + }); + + it("should log timeout errors", async () => { + const mockLogger = createMockLogger(); + + const timeoutError = new Error("Request timeout"); + timeoutError.name = "AbortError"; + + global.fetch = vi.fn().mockRejectedValue(timeoutError); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request timed out", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + timeoutMs: undefined, + }), + ); + }); + + it("should log unknown errors", async () => { + const mockLogger = createMockLogger(); + + const unknownError = new Error("Unknown error"); + + global.fetch = vi.fn().mockRejectedValue(unknownError); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + errorMessage: "Unknown error", + }), + ); + }); + }); + + describe("Logging with Redaction", () => { + it("should redact sensitive data in error logs", async () => { + const mockLogger = createMockLogger(); + mockErrorResponse({ error: "Unauthorized" }, 401, "Unauthorized"); + + await fetcherImpl({ + url: "https://example.com/api?api_key=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + url: "https://example.com/api?api_key=[REDACTED]", + }), + ); + }); + }); + + describe("Different HTTP Methods", () => { + it("should log GET requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "GET", + }), + ); + }); + + it("should log POST requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse({ data: "test" }, 201, "Created"); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "POST", + }), + ); + }); + + it("should log PUT requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "PUT", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "PUT", + }), + ); + }); + + it("should log DELETE requests", async () => { + const mockLogger = createMockLogger(); + global.fetch = vi.fn().mockResolvedValue( + new Response(null, { + status: 200, + statusText: "OK", + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "DELETE", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "DELETE", + }), + ); + }); + }); + + describe("Status Code Logging", () => { + it("should log 2xx success status codes", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse({ data: "test" }, 201, "Created"); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + statusCode: 201, + }), + ); + }); + + it("should log 3xx redirect status codes as success", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse({ data: "test" }, 301, "Moved Permanently"); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + statusCode: 301, + }), + ); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts new file mode 100644 index 000000000000..1850d1fda959 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts @@ -0,0 +1,398 @@ +import type { Mock } from "vitest"; +import { makePassthroughRequest } from "../../../src/core/fetcher/makePassthroughRequest"; + +describe("makePassthroughRequest", () => { + let mockFetch: Mock; + + beforeEach(() => { + mockFetch = vi.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 200 })); + }); + + describe("URL resolution", () => { + it("should use absolute URL directly", async () => { + await makePassthroughRequest("https://api.example.com/v1/users", undefined, { + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/users"); + }); + + it("should resolve relative path against baseUrl", async () => { + await makePassthroughRequest("/v1/users", undefined, { + baseUrl: "https://api.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/users"); + }); + + it("should resolve relative path against environment when baseUrl is not set", async () => { + await makePassthroughRequest("/v1/users", undefined, { + environment: "https://env.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://env.example.com/v1/users"); + }); + + it("should prefer baseUrl over environment", async () => { + await makePassthroughRequest("/v1/users", undefined, { + baseUrl: "https://base.example.com", + environment: "https://env.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://base.example.com/v1/users"); + }); + + it("should pass relative URL through as-is when no baseUrl or environment", async () => { + await makePassthroughRequest("/v1/users", undefined, { + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("/v1/users"); + }); + + it("should resolve baseUrl supplier", async () => { + await makePassthroughRequest("/v1/users", undefined, { + baseUrl: () => "https://dynamic.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://dynamic.example.com/v1/users"); + }); + + it("should ignore absolute URL even when baseUrl is set", async () => { + await makePassthroughRequest("https://other.example.com/path", undefined, { + baseUrl: "https://base.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://other.example.com/path"); + }); + + it("should accept a URL object", async () => { + await makePassthroughRequest(new URL("https://api.example.com/v1/users"), undefined, { + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/users"); + }); + }); + + describe("header merge order", () => { + it("should merge headers in correct priority: SDK defaults < auth < init < requestOptions", async () => { + await makePassthroughRequest( + "https://api.example.com", + { + headers: { "X-Custom": "from-init", Authorization: "from-init" }, + }, + { + headers: { + "X-Custom": "from-sdk", + "X-SDK-Only": "sdk-value", + Authorization: "from-sdk", + }, + getAuthHeaders: async () => ({ + Authorization: "Bearer auth-token", + "X-Auth-Only": "auth-value", + }), + fetch: mockFetch, + }, + { + headers: { Authorization: "from-request-options" }, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + const headers = calledOptions.headers; + + // requestOptions.headers wins for Authorization (highest priority) + expect(headers.authorization).toBe("from-request-options"); + // init.headers wins over SDK defaults for X-Custom + expect(headers["x-custom"]).toBe("from-init"); + // SDK-only header is preserved + expect(headers["x-sdk-only"]).toBe("sdk-value"); + // Auth-only header is preserved + expect(headers["x-auth-only"]).toBe("auth-value"); + }); + + it("should lowercase all header keys", async () => { + await makePassthroughRequest( + "https://api.example.com", + { + headers: { "Content-Type": "application/json" }, + }, + { + headers: { "X-Fern-Language": "JavaScript" }, + fetch: mockFetch, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + const headers = calledOptions.headers; + expect(headers["content-type"]).toBe("application/json"); + expect(headers["x-fern-language"]).toBe("JavaScript"); + expect(headers["Content-Type"]).toBeUndefined(); + expect(headers["X-Fern-Language"]).toBeUndefined(); + }); + + it("should handle Headers object in init", async () => { + const initHeaders = new Headers(); + initHeaders.set("X-From-Headers-Object", "value"); + await makePassthroughRequest("https://api.example.com", { headers: initHeaders }, { fetch: mockFetch }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-from-headers-object"]).toBe("value"); + }); + + it("should handle array-style headers in init", async () => { + await makePassthroughRequest( + "https://api.example.com", + { headers: [["X-Array-Header", "array-value"]] }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-array-header"]).toBe("array-value"); + }); + + it("should skip null SDK default header values", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + headers: { "X-Present": "value", "X-Null": null }, + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-present"]).toBe("value"); + expect(calledOptions.headers["x-null"]).toBeUndefined(); + }); + }); + + describe("auth headers", () => { + it("should include auth headers when getAuthHeaders is provided", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBe("Bearer my-token"); + }); + + it("should work without auth headers", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBeUndefined(); + }); + + it("should allow init headers to override auth headers", async () => { + await makePassthroughRequest( + "https://api.example.com", + { headers: { Authorization: "Bearer override" } }, + { + getAuthHeaders: async () => ({ Authorization: "Bearer sdk-auth" }), + fetch: mockFetch, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBe("Bearer override"); + }); + }); + + describe("method and body", () => { + it("should default to GET when no method specified", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.method).toBe("GET"); + }); + + it("should use the method from init", async () => { + await makePassthroughRequest( + "https://api.example.com", + { method: "POST", body: JSON.stringify({ key: "value" }) }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.method).toBe("POST"); + expect(calledOptions.body).toBe(JSON.stringify({ key: "value" })); + }); + + it("should pass body as undefined when not provided", async () => { + await makePassthroughRequest("https://api.example.com", { method: "GET" }, { fetch: mockFetch }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.body).toBeUndefined(); + }); + }); + + describe("timeout and retries", () => { + it("should use requestOptions timeout over client timeout", async () => { + await makePassthroughRequest( + "https://api.example.com", + undefined, + { timeoutInSeconds: 30, fetch: mockFetch }, + { timeoutInSeconds: 10 }, + ); + // The timeout is passed to makeRequest which converts to ms + // We verify via the signal timing behavior (indirectly tested through makeRequest) + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("should use client timeout when requestOptions timeout is not set", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + timeoutInSeconds: 30, + fetch: mockFetch, + }); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("should use requestOptions maxRetries over client maxRetries", async () => { + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + await makePassthroughRequest( + "https://api.example.com", + undefined, + { maxRetries: 5, fetch: mockFetch }, + { maxRetries: 1 }, + ); + // 1 initial + 1 retry = 2 calls + expect(mockFetch).toHaveBeenCalledTimes(2); + + vi.restoreAllMocks(); + }); + }); + + describe("abort signal", () => { + it("should use requestOptions.abortSignal over init.signal", async () => { + const initController = new AbortController(); + const requestController = new AbortController(); + + await makePassthroughRequest( + "https://api.example.com", + { signal: initController.signal }, + { fetch: mockFetch }, + { abortSignal: requestController.signal }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + // The signal passed to makeRequest is combined with timeout signal via anySignal, + // but the requestOptions.abortSignal should be the one that's used (not init.signal) + expect(calledOptions.signal).toBeDefined(); + }); + + it("should use init.signal when requestOptions.abortSignal is not set", async () => { + const initController = new AbortController(); + + await makePassthroughRequest( + "https://api.example.com", + { signal: initController.signal }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.signal).toBeDefined(); + }); + }); + + describe("credentials", () => { + it("should pass credentials include when set", async () => { + await makePassthroughRequest("https://api.example.com", { credentials: "include" }, { fetch: mockFetch }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.credentials).toBe("include"); + }); + + it("should not pass credentials when not set to include", async () => { + await makePassthroughRequest( + "https://api.example.com", + { credentials: "same-origin" }, + { + fetch: mockFetch, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.credentials).toBeUndefined(); + }); + }); + + describe("response", () => { + it("should return the Response object from fetch", async () => { + const mockResponse = new Response(JSON.stringify({ data: "test" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + mockFetch.mockResolvedValue(mockResponse); + + const response = await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + expect(response).toBe(mockResponse); + expect(response.status).toBe(200); + }); + + it("should return error responses without throwing", async () => { + const errorResponse = new Response("Not Found", { status: 404 }); + mockFetch.mockResolvedValue(errorResponse); + + const response = await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + expect(response.status).toBe(404); + }); + }); + + describe("Request object input", () => { + it("should extract URL from Request object", async () => { + const request = new Request("https://api.example.com/v1/resource", { method: "POST" }); + await makePassthroughRequest(request, undefined, { + fetch: mockFetch, + }); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/resource"); + expect(calledOptions.method).toBe("POST"); + }); + + it("should extract headers from Request object when no init provided", async () => { + const request = new Request("https://api.example.com", { + headers: { "X-From-Request": "request-value" }, + }); + await makePassthroughRequest(request, undefined, { + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-from-request"]).toBe("request-value"); + }); + + it("should use explicit init over Request object properties", async () => { + const request = new Request("https://api.example.com", { + method: "POST", + headers: { "X-From-Request": "request-value" }, + }); + await makePassthroughRequest( + request, + { method: "PUT", headers: { "X-From-Init": "init-value" } }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.method).toBe("PUT"); + expect(calledOptions.headers["x-from-init"]).toBe("init-value"); + // Request headers should NOT be present since explicit init was provided + expect(calledOptions.headers["x-from-request"]).toBeUndefined(); + }); + }); + + describe("SDK default header suppliers", () => { + it("should resolve supplier functions for SDK default headers", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + headers: { + "X-Static": "static-value", + "X-Dynamic": () => "dynamic-value", + }, + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-static"]).toBe("static-value"); + expect(calledOptions.headers["x-dynamic"]).toBe("dynamic-value"); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts new file mode 100644 index 000000000000..bde194554dd8 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts @@ -0,0 +1,158 @@ +import type { Mock } from "vitest"; +import { + isCacheNoStoreSupported, + makeRequest, + resetCacheNoStoreSupported, +} from "../../../src/core/fetcher/makeRequest"; + +describe("Test makeRequest", () => { + const mockPostUrl = "https://httpbin.org/post"; + const mockGetUrl = "https://httpbin.org/get"; + const mockHeaders = { "Content-Type": "application/json" }; + const mockBody = JSON.stringify({ key: "value" }); + + let mockFetch: Mock; + + beforeEach(() => { + mockFetch = vi.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); + resetCacheNoStoreSupported(); + }); + + it("should handle POST request correctly", async () => { + const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockPostUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "POST", + headers: mockHeaders, + body: mockBody, + credentials: undefined, + }), + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should handle GET request correctly", async () => { + const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockGetUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "GET", + headers: mockHeaders, + body: undefined, + credentials: undefined, + }), + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should not include cache option when disableCache is not set", async () => { + await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBeUndefined(); + }); + + it("should not include cache option when disableCache is false", async () => { + await makeRequest( + mockFetch, + mockGetUrl, + "GET", + mockHeaders, + undefined, + undefined, + undefined, + undefined, + undefined, + false, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBeUndefined(); + }); + + it("should include cache: no-store when disableCache is true and runtime supports it", async () => { + // In Node.js test environment, Request supports the cache option + expect(isCacheNoStoreSupported()).toBe(true); + await makeRequest( + mockFetch, + mockGetUrl, + "GET", + mockHeaders, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBe("no-store"); + }); + + it("should cache the result of isCacheNoStoreSupported", () => { + const first = isCacheNoStoreSupported(); + const second = isCacheNoStoreSupported(); + expect(first).toBe(second); + }); + + it("should reset cache detection state with resetCacheNoStoreSupported", () => { + // First call caches the result + const first = isCacheNoStoreSupported(); + expect(first).toBe(true); + + // Reset clears the cache + resetCacheNoStoreSupported(); + + // After reset, it should re-detect (and still return true in Node.js) + const second = isCacheNoStoreSupported(); + expect(second).toBe(true); + }); + + it("should not include cache option when runtime does not support it (e.g. Cloudflare Workers)", async () => { + // Mock Request constructor to throw when cache option is passed, + // simulating runtimes like Cloudflare Workers + const OriginalRequest = globalThis.Request; + globalThis.Request = class MockRequest { + constructor(_url: string, init?: RequestInit) { + if (init?.cache != null) { + throw new TypeError("The 'cache' field on 'RequestInitializerDict' is not implemented."); + } + } + } as unknown as typeof Request; + + try { + // Reset so the detection runs fresh with the mocked Request + resetCacheNoStoreSupported(); + expect(isCacheNoStoreSupported()).toBe(false); + + await makeRequest( + mockFetch, + mockGetUrl, + "GET", + mockHeaders, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBeUndefined(); + } finally { + // Restore original Request + globalThis.Request = OriginalRequest; + resetCacheNoStoreSupported(); + } + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts new file mode 100644 index 000000000000..d599376b9bcf --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts @@ -0,0 +1,1115 @@ +import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +function createMockLogger() { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(data), { + status, + statusText, + }), + ); +} + +describe("Redacting Logic", () => { + describe("Header Redaction", () => { + it("should redact authorization header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { Authorization: "Bearer secret-token-12345" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + Authorization: "[REDACTED]", + }), + }), + ); + }); + + it("should redact api-key header (case-insensitive)", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "X-API-KEY": "secret-api-key" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "X-API-KEY": "[REDACTED]", + }), + }), + ); + }); + + it("should redact cookie header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { Cookie: "session=abc123; token=xyz789" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + Cookie: "[REDACTED]", + }), + }), + ); + }); + + it("should redact x-auth-token header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "x-auth-token": "auth-token-12345" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "x-auth-token": "[REDACTED]", + }), + }), + ); + }); + + it("should redact proxy-authorization header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "Proxy-Authorization": "Basic credentials" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "Proxy-Authorization": "[REDACTED]", + }), + }), + ); + }); + + it("should redact x-csrf-token header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "X-CSRF-Token": "csrf-token-abc" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "X-CSRF-Token": "[REDACTED]", + }), + }), + ); + }); + + it("should redact www-authenticate header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "WWW-Authenticate": "Bearer realm=example" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "WWW-Authenticate": "[REDACTED]", + }), + }), + ); + }); + + it("should redact x-session-token header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "X-Session-Token": "session-token-xyz" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "X-Session-Token": "[REDACTED]", + }), + }), + ); + }); + + it("should not redact non-sensitive headers", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { + "Content-Type": "application/json", + "User-Agent": "Test/1.0", + Accept: "application/json", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "Content-Type": "application/json", + "User-Agent": "Test/1.0", + Accept: "application/json", + }), + }), + ); + }); + + it("should redact multiple sensitive headers at once", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { + Authorization: "Bearer token", + "X-API-Key": "api-key", + Cookie: "session=123", + "Content-Type": "application/json", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + Authorization: "[REDACTED]", + "X-API-Key": "[REDACTED]", + Cookie: "[REDACTED]", + "Content-Type": "application/json", + }), + }), + ); + }); + }); + + describe("Response Header Redaction", () => { + it("should redact Set-Cookie in response headers", async () => { + const mockLogger = createMockLogger(); + + const mockHeaders = new Headers(); + mockHeaders.set("Set-Cookie", "session=abc123; HttpOnly; Secure"); + mockHeaders.set("Content-Type", "application/json"); + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + headers: mockHeaders, + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + responseHeaders: expect.toContainHeaders({ + "set-cookie": "[REDACTED]", + "content-type": "application/json", + }), + }), + ); + }); + + it("should redact authorization in response headers", async () => { + const mockLogger = createMockLogger(); + + const mockHeaders = new Headers(); + mockHeaders.set("Authorization", "Bearer token-123"); + mockHeaders.set("Content-Type", "application/json"); + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + headers: mockHeaders, + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + responseHeaders: expect.toContainHeaders({ + authorization: "[REDACTED]", + "content-type": "application/json", + }), + }), + ); + }); + + it("should redact response headers in error responses", async () => { + const mockLogger = createMockLogger(); + + const mockHeaders = new Headers(); + mockHeaders.set("WWW-Authenticate", "Bearer realm=example"); + mockHeaders.set("Content-Type", "application/json"); + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + statusText: "Unauthorized", + headers: mockHeaders, + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + responseHeaders: expect.toContainHeaders({ + "www-authenticate": "[REDACTED]", + "content-type": "application/json", + }), + }), + ); + }); + }); + + describe("Query Parameter Redaction", () => { + it("should redact api_key query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { api_key: "secret-key" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + api_key: "[REDACTED]", + }), + }), + ); + }); + + it("should redact token query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { token: "secret-token" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + token: "[REDACTED]", + }), + }), + ); + }); + + it("should redact access_token query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { access_token: "secret-access-token" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + access_token: "[REDACTED]", + }), + }), + ); + }); + + it("should redact password query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { password: "secret-password" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + password: "[REDACTED]", + }), + }), + ); + }); + + it("should redact secret query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { secret: "secret-value" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + secret: "[REDACTED]", + }), + }), + ); + }); + + it("should redact session_id query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { session_id: "session-123" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + session_id: "[REDACTED]", + }), + }), + ); + }); + + it("should not redact non-sensitive query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { + page: "1", + limit: "10", + sort: "name", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + page: "1", + limit: "10", + sort: "name", + }), + }), + ); + }); + + it("should not redact parameters containing 'auth' substring like 'author'", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { + author: "john", + authenticate: "false", + authorization_level: "user", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + author: "john", + authenticate: "false", + authorization_level: "user", + }), + }), + ); + }); + + it("should handle undefined query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: undefined, + }), + ); + }); + + it("should redact case-insensitive query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { API_KEY: "secret-key", Token: "secret-token" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + API_KEY: "[REDACTED]", + Token: "[REDACTED]", + }), + }), + ); + }); + }); + + describe("URL Redaction", () => { + it("should redact credentials in URL", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user:password@example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@example.com/api", + }), + ); + }); + + it("should redact api_key in query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?api_key=secret-key&page=1", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?api_key=[REDACTED]&page=1", + }), + ); + }); + + it("should redact token in query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?token=secret-token", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?token=[REDACTED]", + }), + ); + }); + + it("should redact password in query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?username=user&password=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?username=user&password=[REDACTED]", + }), + ); + }); + + it("should not redact non-sensitive query strings", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?page=1&limit=10&sort=name", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?page=1&limit=10&sort=name", + }), + ); + }); + + it("should not redact URL parameters containing 'auth' substring like 'author'", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?author=john&authenticate=false&page=1", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?author=john&authenticate=false&page=1", + }), + ); + }); + + it("should handle URL with fragment", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?token=secret#section", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?token=[REDACTED]#section", + }), + ); + }); + + it("should redact URL-encoded query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?api%5Fkey=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?api%5Fkey=[REDACTED]", + }), + ); + }); + + it("should handle URL without query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api", + }), + ); + }); + + it("should handle empty query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?", + }), + ); + }); + + it("should redact multiple sensitive parameters in URL", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?api_key=secret1&token=secret2&page=1", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?api_key=[REDACTED]&token=[REDACTED]&page=1", + }), + ); + }); + + it("should redact both credentials and query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user:pass@example.com/api?token=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@example.com/api?token=[REDACTED]", + }), + ); + }); + + it("should use fast path for URLs without sensitive keywords", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", + }), + ); + }); + + it("should handle query parameter without value", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?flag&token=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?flag&token=[REDACTED]", + }), + ); + }); + + it("should handle URL with multiple @ symbols in credentials", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user@example.com:pass@host.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@host.com/api", + }), + ); + }); + + it("should handle URL with @ in query parameter but not in credentials", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?email=user@example.com", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?email=user@example.com", + }), + ); + }); + + it("should handle URL with both credentials and @ in path", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user:pass@example.com/users/@username", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@example.com/users/@username", + }), + ); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts new file mode 100644 index 000000000000..d22661367f4e --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts @@ -0,0 +1,230 @@ +import type { Mock, MockInstance } from "vitest"; +import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; + +describe("requestWithRetries", () => { + let mockFetch: Mock; + let originalMathRandom: typeof Math.random; + let setTimeoutSpy: MockInstance; + + beforeEach(() => { + mockFetch = vi.fn(); + originalMathRandom = Math.random; + + Math.random = vi.fn(() => 0.5); + + vi.useFakeTimers({ + toFake: [ + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "Date", + "performance", + "requestAnimationFrame", + "cancelAnimationFrame", + "requestIdleCallback", + "cancelIdleCallback", + ], + }); + }); + + afterEach(() => { + Math.random = originalMathRandom; + vi.clearAllMocks(); + vi.clearAllTimers(); + }); + + it("should retry on retryable status codes", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const retryableStatuses = [408, 429, 500, 502]; + let callCount = 0; + + mockFetch.mockImplementation(async () => { + if (callCount < retryableStatuses.length) { + return new Response("", { status: retryableStatuses[callCount++] }); + } + return new Response("", { status: 200 }); + }); + + const responsePromise = requestWithRetries(() => mockFetch(), retryableStatuses.length); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(retryableStatuses.length + 1); + expect(response.status).toBe(200); + }); + + it("should respect maxRetries limit", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const maxRetries = 2; + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + expect(response.status).toBe(500); + }); + + it("should not retry on success status codes", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const successStatuses = [200, 201, 202]; + + for (const status of successStatuses) { + mockFetch.mockReset(); + setTimeoutSpy.mockClear(); + mockFetch.mockResolvedValueOnce(new Response("", { status })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + await vi.runAllTimersAsync(); + await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(setTimeoutSpy).not.toHaveBeenCalled(); + } + }); + + interface RetryHeaderTestCase { + description: string; + headerName: string; + headerValue: string | (() => string); + expectedDelayMin: number; + expectedDelayMax: number; + } + + const retryHeaderTests: RetryHeaderTestCase[] = [ + { + description: "should respect retry-after header with seconds value", + headerName: "retry-after", + headerValue: "5", + expectedDelayMin: 4000, + expectedDelayMax: 6000, + }, + { + description: "should respect retry-after header with HTTP date value", + headerName: "retry-after", + headerValue: () => new Date(Date.now() + 3000).toUTCString(), + expectedDelayMin: 2000, + expectedDelayMax: 4000, + }, + { + description: "should respect x-ratelimit-reset header", + headerName: "x-ratelimit-reset", + headerValue: () => Math.floor((Date.now() + 4000) / 1000).toString(), + expectedDelayMin: 3000, + expectedDelayMax: 6000, + }, + ]; + + retryHeaderTests.forEach(({ description, headerName, headerValue, expectedDelayMin, expectedDelayMax }) => { + it(description, async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const value = typeof headerValue === "function" ? headerValue() : headerValue; + mockFetch + .mockResolvedValueOnce( + new Response("", { + status: 429, + headers: new Headers({ [headerName]: value }), + }), + ) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 1); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), expect.any(Number)); + const actualDelay = setTimeoutSpy.mock.calls[0][1]; + expect(actualDelay).toBeGreaterThan(expectedDelayMin); + expect(actualDelay).toBeLessThan(expectedDelayMax); + expect(response.status).toBe(200); + }); + }); + + it("should apply correct exponential backoff with jitter", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + const maxRetries = 3; + const expectedDelays = [1000, 2000, 4000]; + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await vi.runAllTimersAsync(); + await responsePromise; + + expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length); + + expectedDelays.forEach((delay, index) => { + expect(setTimeoutSpy).toHaveBeenNthCalledWith(index + 1, expect.any(Function), delay); + }); + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + }); + + it("should handle concurrent retries independently", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const promise1 = requestWithRetries(() => mockFetch(), 1); + const promise2 = requestWithRetries(() => mockFetch(), 1); + + await vi.runAllTimersAsync(); + const [response1, response2] = await Promise.all([promise1, promise2]); + + expect(response1.status).toBe(200); + expect(response2.status).toBe(200); + }); + + it("should cap delay at MAX_RETRY_DELAY for large header values", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch + .mockResolvedValueOnce( + new Response("", { + status: 429, + headers: new Headers({ "retry-after": "120" }), // 120 seconds = 120000ms > MAX_RETRY_DELAY (60000ms) + }), + ) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 1); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 60000); + expect(response.status).toBe(200); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts new file mode 100644 index 000000000000..d7b6d1e63caa --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts @@ -0,0 +1,69 @@ +import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; + +describe("Test getTimeoutSignal", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should return an object with signal and abortId", () => { + const { signal, abortId } = getTimeoutSignal(1000); + + expect(signal).toBeDefined(); + expect(abortId).toBeDefined(); + expect(signal).toBeInstanceOf(AbortSignal); + expect(signal.aborted).toBe(false); + }); + + it("should create a signal that aborts after the specified timeout", () => { + const timeoutMs = 5000; + const { signal } = getTimeoutSignal(timeoutMs); + + expect(signal.aborted).toBe(false); + + vi.advanceTimersByTime(timeoutMs - 1); + expect(signal.aborted).toBe(false); + + vi.advanceTimersByTime(1); + expect(signal.aborted).toBe(true); + }); +}); + +describe("Test anySignal", () => { + it("should return an AbortSignal", () => { + const signal = anySignal(new AbortController().signal); + expect(signal).toBeInstanceOf(AbortSignal); + }); + + it("should abort when any of the input signals is aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal(controller1.signal, controller2.signal); + + expect(signal.aborted).toBe(false); + controller1.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should handle an array of signals", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal([controller1.signal, controller2.signal]); + + expect(signal.aborted).toBe(false); + controller2.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should abort immediately if one of the input signals is already aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + controller1.abort(); + + const signal = anySignal(controller1.signal, controller2.signal); + expect(signal.aborted).toBe(true); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt b/seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt new file mode 100644 index 000000000000..c66d471e359c --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt @@ -0,0 +1 @@ +This is a test file! diff --git a/seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts b/seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts new file mode 100644 index 000000000000..2e0b5fe5040c --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts @@ -0,0 +1,454 @@ +import { ConsoleLogger, createLogger, Logger, LogLevel } from "../../../src/core/logging/logger"; + +function createMockLogger() { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +describe("Logger", () => { + describe("LogLevel", () => { + it("should have correct log levels", () => { + expect(LogLevel.Debug).toBe("debug"); + expect(LogLevel.Info).toBe("info"); + expect(LogLevel.Warn).toBe("warn"); + expect(LogLevel.Error).toBe("error"); + }); + }); + + describe("ConsoleLogger", () => { + let consoleLogger: ConsoleLogger; + let consoleSpy: { + debug: ReturnType; + info: ReturnType; + warn: ReturnType; + error: ReturnType; + }; + + beforeEach(() => { + consoleLogger = new ConsoleLogger(); + consoleSpy = { + debug: vi.spyOn(console, "debug").mockImplementation(() => {}), + info: vi.spyOn(console, "info").mockImplementation(() => {}), + warn: vi.spyOn(console, "warn").mockImplementation(() => {}), + error: vi.spyOn(console, "error").mockImplementation(() => {}), + }; + }); + + afterEach(() => { + consoleSpy.debug.mockRestore(); + consoleSpy.info.mockRestore(); + consoleSpy.warn.mockRestore(); + consoleSpy.error.mockRestore(); + }); + + it("should log debug messages", () => { + consoleLogger.debug("debug message", { data: "test" }); + expect(consoleSpy.debug).toHaveBeenCalledWith("debug message", { data: "test" }); + }); + + it("should log info messages", () => { + consoleLogger.info("info message", { data: "test" }); + expect(consoleSpy.info).toHaveBeenCalledWith("info message", { data: "test" }); + }); + + it("should log warn messages", () => { + consoleLogger.warn("warn message", { data: "test" }); + expect(consoleSpy.warn).toHaveBeenCalledWith("warn message", { data: "test" }); + }); + + it("should log error messages", () => { + consoleLogger.error("error message", { data: "test" }); + expect(consoleSpy.error).toHaveBeenCalledWith("error message", { data: "test" }); + }); + + it("should handle multiple arguments", () => { + consoleLogger.debug("message", "arg1", "arg2", { key: "value" }); + expect(consoleSpy.debug).toHaveBeenCalledWith("message", "arg1", "arg2", { key: "value" }); + }); + }); + + describe("Logger with level filtering", () => { + let mockLogger: { + debug: ReturnType; + info: ReturnType; + warn: ReturnType; + error: ReturnType; + }; + + beforeEach(() => { + mockLogger = createMockLogger(); + }); + + describe("Debug level", () => { + it("should log all levels when set to debug", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).toHaveBeenCalledWith("debug"); + expect(mockLogger.info).toHaveBeenCalledWith("info"); + expect(mockLogger.warn).toHaveBeenCalledWith("warn"); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(true); + expect(logger.isInfo()).toBe(true); + expect(logger.isWarn()).toBe(true); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Info level", () => { + it("should log info, warn, and error when set to info", () => { + const logger = new Logger({ + level: LogLevel.Info, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).toHaveBeenCalledWith("info"); + expect(mockLogger.warn).toHaveBeenCalledWith("warn"); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Info, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(true); + expect(logger.isWarn()).toBe(true); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Warn level", () => { + it("should log warn and error when set to warn", () => { + const logger = new Logger({ + level: LogLevel.Warn, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalledWith("warn"); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Warn, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(false); + expect(logger.isWarn()).toBe(true); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Error level", () => { + it("should only log error when set to error", () => { + const logger = new Logger({ + level: LogLevel.Error, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Error, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(false); + expect(logger.isWarn()).toBe(false); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Silent mode", () => { + it("should not log anything when silent is true", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: true, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); + + it("should report all level checks as false when silent", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: true, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(false); + expect(logger.isWarn()).toBe(false); + expect(logger.isError()).toBe(false); + }); + }); + + describe("shouldLog", () => { + it("should correctly determine if level should be logged", () => { + const logger = new Logger({ + level: LogLevel.Info, + logger: mockLogger, + silent: false, + }); + + expect(logger.shouldLog(LogLevel.Debug)).toBe(false); + expect(logger.shouldLog(LogLevel.Info)).toBe(true); + expect(logger.shouldLog(LogLevel.Warn)).toBe(true); + expect(logger.shouldLog(LogLevel.Error)).toBe(true); + }); + + it("should return false for all levels when silent", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: true, + }); + + expect(logger.shouldLog(LogLevel.Debug)).toBe(false); + expect(logger.shouldLog(LogLevel.Info)).toBe(false); + expect(logger.shouldLog(LogLevel.Warn)).toBe(false); + expect(logger.shouldLog(LogLevel.Error)).toBe(false); + }); + }); + + describe("Multiple arguments", () => { + it("should pass multiple arguments to logger", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("message", "arg1", { key: "value" }, 123); + expect(mockLogger.debug).toHaveBeenCalledWith("message", "arg1", { key: "value" }, 123); + }); + }); + }); + + describe("createLogger", () => { + it("should return default logger when no config provided", () => { + const logger = createLogger(); + expect(logger).toBeInstanceOf(Logger); + }); + + it("should return same logger instance when Logger is passed", () => { + const customLogger = new Logger({ + level: LogLevel.Debug, + logger: new ConsoleLogger(), + silent: false, + }); + + const result = createLogger(customLogger); + expect(result).toBe(customLogger); + }); + + it("should create logger with custom config", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + level: LogLevel.Warn, + logger: mockLogger, + silent: false, + }); + + expect(logger).toBeInstanceOf(Logger); + logger.warn("test"); + expect(mockLogger.warn).toHaveBeenCalledWith("test"); + }); + + it("should use default values for missing config", () => { + const logger = createLogger({}); + expect(logger).toBeInstanceOf(Logger); + }); + + it("should override default level", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("test"); + expect(mockLogger.debug).toHaveBeenCalledWith("test"); + }); + + it("should override default silent mode", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + logger: mockLogger, + silent: false, + }); + + logger.info("test"); + expect(mockLogger.info).toHaveBeenCalledWith("test"); + }); + + it("should use provided logger implementation", () => { + const customLogger = createMockLogger(); + + const logger = createLogger({ + logger: customLogger, + level: LogLevel.Debug, + silent: false, + }); + + logger.debug("test"); + expect(customLogger.debug).toHaveBeenCalledWith("test"); + }); + + it("should default to silent: true", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + logger: mockLogger, + level: LogLevel.Debug, + }); + + logger.debug("test"); + expect(mockLogger.debug).not.toHaveBeenCalled(); + }); + }); + + describe("Default logger", () => { + it("should have silent: true by default", () => { + const logger = createLogger(); + expect(logger.shouldLog(LogLevel.Info)).toBe(false); + }); + + it("should not log when using default logger", () => { + const logger = createLogger(); + + logger.info("test"); + expect(logger.isInfo()).toBe(false); + }); + }); + + describe("Edge cases", () => { + it("should handle empty message", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug(""); + expect(mockLogger.debug).toHaveBeenCalledWith(""); + }); + + it("should handle no arguments", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("message"); + expect(mockLogger.debug).toHaveBeenCalledWith("message"); + }); + + it("should handle complex objects", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + const complexObject = { + nested: { key: "value" }, + array: [1, 2, 3], + fn: () => "test", + }; + + logger.debug("message", complexObject); + expect(mockLogger.debug).toHaveBeenCalledWith("message", complexObject); + }); + + it("should handle errors as arguments", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Error, + logger: mockLogger, + silent: false, + }); + + const error = new Error("Test error"); + logger.error("Error occurred", error); + expect(mockLogger.error).toHaveBeenCalledWith("Error occurred", error); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts b/seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts new file mode 100644 index 000000000000..123488f084ea --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts @@ -0,0 +1,284 @@ +import { join } from "../../../src/core/url/index"; + +describe("join", () => { + interface TestCase { + description: string; + base: string; + segments: string[]; + expected: string; + } + + describe("basic functionality", () => { + const basicTests: TestCase[] = [ + { description: "should return empty string for empty base", base: "", segments: [], expected: "" }, + { + description: "should return empty string for empty base with path", + base: "", + segments: ["path"], + expected: "", + }, + { + description: "should handle single segment", + base: "base", + segments: ["segment"], + expected: "base/segment", + }, + { + description: "should handle single segment with trailing slash on base", + base: "base/", + segments: ["segment"], + expected: "base/segment", + }, + { + description: "should handle single segment with leading slash", + base: "base", + segments: ["/segment"], + expected: "base/segment", + }, + { + description: "should handle single segment with both slashes", + base: "base/", + segments: ["/segment"], + expected: "base/segment", + }, + { + description: "should handle multiple segments", + base: "base", + segments: ["path1", "path2", "path3"], + expected: "base/path1/path2/path3", + }, + { + description: "should handle multiple segments with slashes", + base: "base/", + segments: ["/path1/", "/path2/", "/path3/"], + expected: "base/path1/path2/path3/", + }, + ]; + + basicTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("URL handling", () => { + const urlTests: TestCase[] = [ + { + description: "should handle absolute URLs", + base: "https://example.com", + segments: ["api", "v1"], + expected: "https://example.com/api/v1", + }, + { + description: "should handle absolute URLs with slashes", + base: "https://example.com/", + segments: ["/api/", "/v1/"], + expected: "https://example.com/api/v1/", + }, + { + description: "should handle absolute URLs with base path", + base: "https://example.com/base", + segments: ["api", "v1"], + expected: "https://example.com/base/api/v1", + }, + { + description: "should preserve URL query parameters", + base: "https://example.com?query=1", + segments: ["api"], + expected: "https://example.com/api?query=1", + }, + { + description: "should preserve URL fragments", + base: "https://example.com#fragment", + segments: ["api"], + expected: "https://example.com/api#fragment", + }, + { + description: "should preserve URL query and fragments", + base: "https://example.com?query=1#fragment", + segments: ["api"], + expected: "https://example.com/api?query=1#fragment", + }, + { + description: "should handle http protocol", + base: "http://example.com", + segments: ["api"], + expected: "http://example.com/api", + }, + { + description: "should handle ftp protocol", + base: "ftp://example.com", + segments: ["files"], + expected: "ftp://example.com/files", + }, + { + description: "should handle ws protocol", + base: "ws://example.com", + segments: ["socket"], + expected: "ws://example.com/socket", + }, + { + description: "should fallback to path joining for malformed URLs", + base: "not-a-url://", + segments: ["path"], + expected: "not-a-url:///path", + }, + ]; + + urlTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("edge cases", () => { + const edgeCaseTests: TestCase[] = [ + { + description: "should handle empty segments", + base: "base", + segments: ["", "path"], + expected: "base/path", + }, + { + description: "should handle null segments", + base: "base", + segments: [null as any, "path"], + expected: "base/path", + }, + { + description: "should handle undefined segments", + base: "base", + segments: [undefined as any, "path"], + expected: "base/path", + }, + { + description: "should handle segments with only single slash", + base: "base", + segments: ["/", "path"], + expected: "base/path", + }, + { + description: "should handle segments with only double slash", + base: "base", + segments: ["//", "path"], + expected: "base/path", + }, + { + description: "should handle base paths with trailing slashes", + base: "base/", + segments: ["path"], + expected: "base/path", + }, + { + description: "should handle complex nested paths", + base: "api/v1/", + segments: ["/users/", "/123/", "/profile"], + expected: "api/v1/users/123/profile", + }, + ]; + + edgeCaseTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("real-world scenarios", () => { + const realWorldTests: TestCase[] = [ + { + description: "should handle API endpoint construction", + base: "https://api.example.com/v1", + segments: ["users", "123", "posts"], + expected: "https://api.example.com/v1/users/123/posts", + }, + { + description: "should handle file path construction", + base: "/var/www", + segments: ["html", "assets", "images"], + expected: "/var/www/html/assets/images", + }, + { + description: "should handle relative path construction", + base: "../parent", + segments: ["child", "grandchild"], + expected: "../parent/child/grandchild", + }, + { + description: "should handle Windows-style paths", + base: "C:\\Users", + segments: ["Documents", "file.txt"], + expected: "C:\\Users/Documents/file.txt", + }, + ]; + + realWorldTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("performance scenarios", () => { + it("should handle many segments efficiently", () => { + const segments = Array(100).fill("segment"); + const result = join("base", ...segments); + expect(result).toBe(`base/${segments.join("/")}`); + }); + + it("should handle long URLs", () => { + const longPath = "a".repeat(1000); + expect(join("https://example.com", longPath)).toBe(`https://example.com/${longPath}`); + }); + }); + + describe("trailing slash preservation", () => { + const trailingSlashTests: TestCase[] = [ + { + description: + "should preserve trailing slash on final result when base has trailing slash and no segments", + base: "https://api.example.com/", + segments: [], + expected: "https://api.example.com/", + }, + { + description: "should preserve trailing slash on v1 path", + base: "https://api.example.com/v1/", + segments: [], + expected: "https://api.example.com/v1/", + }, + { + description: "should preserve trailing slash when last segment has trailing slash", + base: "https://api.example.com", + segments: ["users/"], + expected: "https://api.example.com/users/", + }, + { + description: "should preserve trailing slash with relative path", + base: "api/v1", + segments: ["users/"], + expected: "api/v1/users/", + }, + { + description: "should preserve trailing slash with multiple segments", + base: "https://api.example.com", + segments: ["v1", "collections/"], + expected: "https://api.example.com/v1/collections/", + }, + { + description: "should preserve trailing slash with base path", + base: "base", + segments: ["path1", "path2/"], + expected: "base/path1/path2/", + }, + ]; + + trailingSlashTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts b/seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts new file mode 100644 index 000000000000..42cdffb9e5ea --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts @@ -0,0 +1,278 @@ +import { toQueryString } from "../../../src/core/url/index"; + +describe("Test qs toQueryString", () => { + interface BasicTestCase { + description: string; + input: any; + expected: string; + } + + describe("Basic functionality", () => { + const basicTests: BasicTestCase[] = [ + { description: "should return empty string for null", input: null, expected: "" }, + { description: "should return empty string for undefined", input: undefined, expected: "" }, + { description: "should return empty string for string primitive", input: "hello", expected: "" }, + { description: "should return empty string for number primitive", input: 42, expected: "" }, + { description: "should return empty string for true boolean", input: true, expected: "" }, + { description: "should return empty string for false boolean", input: false, expected: "" }, + { description: "should handle empty objects", input: {}, expected: "" }, + { + description: "should handle simple key-value pairs", + input: { name: "John", age: 30 }, + expected: "name=John&age=30", + }, + ]; + + basicTests.forEach(({ description, input, expected }) => { + it(description, () => { + expect(toQueryString(input)).toBe(expected); + }); + }); + }); + + describe("Array handling", () => { + interface ArrayTestCase { + description: string; + input: any; + options?: { arrayFormat?: "repeat" | "indices" }; + expected: string; + } + + const arrayTests: ArrayTestCase[] = [ + { + description: "should handle arrays with indices format (default)", + input: { items: ["a", "b", "c"] }, + expected: "items%5B0%5D=a&items%5B1%5D=b&items%5B2%5D=c", + }, + { + description: "should handle arrays with repeat format", + input: { items: ["a", "b", "c"] }, + options: { arrayFormat: "repeat" }, + expected: "items=a&items=b&items=c", + }, + { + description: "should handle empty arrays", + input: { items: [] }, + expected: "", + }, + { + description: "should handle arrays with mixed types", + input: { mixed: ["string", 42, true, false] }, + expected: "mixed%5B0%5D=string&mixed%5B1%5D=42&mixed%5B2%5D=true&mixed%5B3%5D=false", + }, + { + description: "should handle arrays with objects", + input: { users: [{ name: "John" }, { name: "Jane" }] }, + expected: "users%5B0%5D%5Bname%5D=John&users%5B1%5D%5Bname%5D=Jane", + }, + { + description: "should handle arrays with objects in repeat format", + input: { users: [{ name: "John" }, { name: "Jane" }] }, + options: { arrayFormat: "repeat" }, + expected: "users%5Bname%5D=John&users%5Bname%5D=Jane", + }, + ]; + + arrayTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); + + describe("Nested objects", () => { + const nestedTests: BasicTestCase[] = [ + { + description: "should handle nested objects", + input: { user: { name: "John", age: 30 } }, + expected: "user%5Bname%5D=John&user%5Bage%5D=30", + }, + { + description: "should handle deeply nested objects", + input: { user: { profile: { name: "John", settings: { theme: "dark" } } } }, + expected: "user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", + }, + { + description: "should handle empty nested objects", + input: { user: {} }, + expected: "", + }, + ]; + + nestedTests.forEach(({ description, input, expected }) => { + it(description, () => { + expect(toQueryString(input)).toBe(expected); + }); + }); + }); + + describe("Encoding", () => { + interface EncodingTestCase { + description: string; + input: any; + options?: { encode?: boolean }; + expected: string; + } + + const encodingTests: EncodingTestCase[] = [ + { + description: "should encode by default", + input: { name: "John Doe", email: "john@example.com" }, + expected: "name=John%20Doe&email=john%40example.com", + }, + { + description: "should not encode when encode is false", + input: { name: "John Doe", email: "john@example.com" }, + options: { encode: false }, + expected: "name=John Doe&email=john@example.com", + }, + { + description: "should encode special characters in keys", + input: { "user name": "John", "email[primary]": "john@example.com" }, + expected: "user%20name=John&email%5Bprimary%5D=john%40example.com", + }, + { + description: "should not encode special characters in keys when encode is false", + input: { "user name": "John", "email[primary]": "john@example.com" }, + options: { encode: false }, + expected: "user name=John&email[primary]=john@example.com", + }, + ]; + + encodingTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); + + describe("Mixed scenarios", () => { + interface MixedTestCase { + description: string; + input: any; + options?: { arrayFormat?: "repeat" | "indices" }; + expected: string; + } + + const mixedTests: MixedTestCase[] = [ + { + description: "should handle complex nested structures", + input: { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }, + expected: + "filters%5Bstatus%5D%5B0%5D=active&filters%5Bstatus%5D%5B1%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D%5B0%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D%5B1%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + }, + { + description: "should handle complex nested structures with repeat format", + input: { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }, + options: { arrayFormat: "repeat" }, + expected: + "filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + }, + { + description: "should handle arrays with null/undefined values", + input: { items: ["a", null, "c", undefined, "e"] }, + expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e", + }, + { + description: "should handle objects with null/undefined values", + input: { name: "John", age: null, email: undefined, active: true }, + expected: "name=John&age=&active=true", + }, + ]; + + mixedTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); + + describe("Edge cases", () => { + const edgeCaseTests: BasicTestCase[] = [ + { + description: "should handle numeric keys", + input: { "0": "zero", "1": "one" }, + expected: "0=zero&1=one", + }, + { + description: "should handle boolean values in objects", + input: { enabled: true, disabled: false }, + expected: "enabled=true&disabled=false", + }, + { + description: "should handle empty strings", + input: { name: "", description: "test" }, + expected: "name=&description=test", + }, + { + description: "should handle zero values", + input: { count: 0, price: 0.0 }, + expected: "count=0&price=0", + }, + { + description: "should handle arrays with empty strings", + input: { items: ["a", "", "c"] }, + expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c", + }, + ]; + + edgeCaseTests.forEach(({ description, input, expected }) => { + it(description, () => { + expect(toQueryString(input)).toBe(expected); + }); + }); + }); + + describe("Options combinations", () => { + interface OptionsTestCase { + description: string; + input: any; + options?: { arrayFormat?: "repeat" | "indices"; encode?: boolean }; + expected: string; + } + + const optionsTests: OptionsTestCase[] = [ + { + description: "should respect both arrayFormat and encode options", + input: { items: ["a & b", "c & d"] }, + options: { arrayFormat: "repeat", encode: false }, + expected: "items=a & b&items=c & d", + }, + { + description: "should use default options when none provided", + input: { items: ["a", "b"] }, + expected: "items%5B0%5D=a&items%5B1%5D=b", + }, + { + description: "should merge provided options with defaults", + input: { items: ["a", "b"], name: "John Doe" }, + options: { encode: false }, + expected: "items[0]=a&items[1]=b&name=John Doe", + }, + ]; + + optionsTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tests/wire/.gitkeep b/seed/ts-sdk/allof-inline/tests/wire/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/ts-sdk/allof-inline/tests/wire/main.test.ts b/seed/ts-sdk/allof-inline/tests/wire/main.test.ts new file mode 100644 index 000000000000..322f94682c9e --- /dev/null +++ b/seed/ts-sdk/allof-inline/tests/wire/main.test.ts @@ -0,0 +1,91 @@ +// This file was auto-generated by Fern from our API Definition. + +import { SeedApiClient } from "../../src/Client"; +import { mockServerPool } from "../mock-server/MockServerPool"; + +describe("SeedApiClient", () => { + test("searchRuleTypes", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { + paging: { next: "next", previous: "previous" }, + results: [{ id: "id", name: "name", description: "description" }], + }; + + server.mockEndpoint().get("/rule-types").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.searchRuleTypes(); + expect(response).toEqual(rawResponseBody); + }); + + test("createRule", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + const rawRequestBody = { name: "name", executionContext: "prod" }; + const rawResponseBody = { + createdBy: "createdBy", + createdDateTime: "2024-01-15T09:30:00Z", + modifiedBy: "modifiedBy", + modifiedDateTime: "2024-01-15T09:30:00Z", + id: "id", + name: "name", + status: "active", + executionContext: "prod", + }; + + server + .mockEndpoint() + .post("/rules") + .jsonBody(rawRequestBody) + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.createRule({ + name: "name", + executionContext: "prod", + }); + expect(response).toEqual(rawResponseBody); + }); + + test("listUsers", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { + paging: { next: "next", previous: "previous" }, + results: [{ id: "id", email: "email" }], + }; + + server.mockEndpoint().get("/users").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.listUsers(); + expect(response).toEqual(rawResponseBody); + }); + + test("getEntity", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { id: "id", name: "name", summary: "summary", status: "active" }; + + server.mockEndpoint().get("/entities").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.getEntity(); + expect(response).toEqual(rawResponseBody); + }); + + test("getOrganization", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { id: "id", metadata: { region: "region", domain: "domain" }, name: "name" }; + + server.mockEndpoint().get("/organizations").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.getOrganization(); + expect(response).toEqual(rawResponseBody); + }); +}); diff --git a/seed/ts-sdk/allof-inline/tsconfig.base.json b/seed/ts-sdk/allof-inline/tsconfig.base.json new file mode 100644 index 000000000000..93a92c0630b5 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "extendedDiagnostics": true, + "strict": true, + "target": "ES6", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "isolatedModules": true, + "isolatedDeclarations": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof-inline/tsconfig.cjs.json b/seed/ts-sdk/allof-inline/tsconfig.cjs.json new file mode 100644 index 000000000000..5c11446f5984 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "dist/cjs" + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof-inline/tsconfig.esm.json b/seed/ts-sdk/allof-inline/tsconfig.esm.json new file mode 100644 index 000000000000..6ce909748b2c --- /dev/null +++ b/seed/ts-sdk/allof-inline/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/esm", + "verbatimModuleSyntax": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof-inline/tsconfig.json b/seed/ts-sdk/allof-inline/tsconfig.json new file mode 100644 index 000000000000..d77fdf00d259 --- /dev/null +++ b/seed/ts-sdk/allof-inline/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.cjs.json" +} diff --git a/seed/ts-sdk/allof-inline/vitest.config.mts b/seed/ts-sdk/allof-inline/vitest.config.mts new file mode 100644 index 000000000000..0dee5a752d39 --- /dev/null +++ b/seed/ts-sdk/allof-inline/vitest.config.mts @@ -0,0 +1,32 @@ +import { defineConfig } from "vitest/config"; +export default defineConfig({ + test: { + typecheck: { + enabled: true, + tsconfig: "./tests/tsconfig.json", + }, + projects: [ + { + test: { + globals: true, + name: "unit", + environment: "node", + root: "./tests", + include: ["**/*.test.{js,ts,jsx,tsx}"], + exclude: ["wire/**"], + setupFiles: ["./setup.ts"], + }, + }, + { + test: { + globals: true, + name: "wire", + environment: "node", + root: "./tests/wire", + setupFiles: ["../setup.ts", "../mock-server/setup.ts"], + }, + }, + ], + passWithNoTests: true, + }, +}); diff --git a/seed/ts-sdk/allof/.fern/metadata.json b/seed/ts-sdk/allof/.fern/metadata.json new file mode 100644 index 000000000000..9da357e1fedb --- /dev/null +++ b/seed/ts-sdk/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-typescript-sdk", + "generatorVersion": "latest", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} diff --git a/seed/ts-sdk/allof/.github/workflows/ci.yml b/seed/ts-sdk/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..93fba226cb67 --- /dev/null +++ b/seed/ts-sdk/allof/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Set up node + uses: actions/setup-node@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Compile + run: pnpm build + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Set up node + uses: actions/setup-node@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Test + run: pnpm test diff --git a/seed/ts-sdk/allof/.gitignore b/seed/ts-sdk/allof/.gitignore new file mode 100644 index 000000000000..72271e049c02 --- /dev/null +++ b/seed/ts-sdk/allof/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store +/dist \ No newline at end of file diff --git a/seed/ts-sdk/allof/CONTRIBUTING.md b/seed/ts-sdk/allof/CONTRIBUTING.md new file mode 100644 index 000000000000..fe5bc2f77e0b --- /dev/null +++ b/seed/ts-sdk/allof/CONTRIBUTING.md @@ -0,0 +1,133 @@ +# Contributing + +Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project. + +## Getting Started + +### Prerequisites + +- Node.js 20 or higher +- pnpm package manager + +### Installation + +Install the project dependencies: + +```bash +pnpm install +``` + +### Building + +Build the project: + +```bash +pnpm build +``` + +### Testing + +Run the test suite: + +```bash +pnpm test +``` + +Run specific test types: +- `pnpm test:unit` - Run unit tests +- `pnpm test:wire` - Run wire/integration tests + +### Linting and Formatting + +Check code style: + +```bash +pnpm run lint +pnpm run format:check +``` + +Fix code style issues: + +```bash +pnpm run lint:fix +pnpm run format:fix +``` + +Or use the combined check command: + +```bash +pnpm run check:fix +``` + +## About Generated Code + +**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated. + +### Generated Files + +The following directories contain generated code: +- `src/api/` - API client classes and types +- `src/serialization/` - Serialization/deserialization logic +- Most TypeScript files in `src/` + +### How to Customize + +If you need to customize the SDK, you have two options: + +#### Option 1: Use `.fernignore` + +For custom code that should persist across SDK regenerations: + +1. Create a `.fernignore` file in the project root +2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax) +3. Add your custom code to those files + +Files listed in `.fernignore` will not be overwritten when the SDK is regenerated. + +For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code). + +#### Option 2: Contribute to the Generator + +If you want to change how code is generated for all users of this SDK: + +1. The TypeScript SDK generator lives in the [Fern repository](https://github.com/fern-api/fern) +2. Generator code is located at `generators/typescript/sdk/` +3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md) +4. Submit a pull request with your changes to the generator + +This approach is best for: +- Bug fixes in generated code +- New features that would benefit all users +- Improvements to code generation patterns + +## Making Changes + +### Workflow + +1. Create a new branch for your changes +2. Make your modifications +3. Run tests to ensure nothing breaks: `pnpm test` +4. Run linting and formatting: `pnpm run check:fix` +5. Build the project: `pnpm build` +6. Commit your changes with a clear commit message +7. Push your branch and create a pull request + +### Commit Messages + +Write clear, descriptive commit messages that explain what changed and why. + +### Code Style + +This project uses automated code formatting and linting. Run `pnpm run check:fix` before committing to ensure your code meets the project's style guidelines. + +## Questions or Issues? + +If you have questions or run into issues: + +1. Check the [Fern documentation](https://buildwithfern.com) +2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues) +3. Open a new issue if your question hasn't been addressed + +## License + +By contributing to this project, you agree that your contributions will be licensed under the same license as the project. diff --git a/seed/ts-sdk/allof/README.md b/seed/ts-sdk/allof/README.md new file mode 100644 index 000000000000..e8c268db5356 --- /dev/null +++ b/seed/ts-sdk/allof/README.md @@ -0,0 +1,292 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) +[![npm shield](https://img.shields.io/npm/v/@fern/allof)](https://www.npmjs.com/package/@fern/allof) + +The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Environments](#environments) +- [Request and Response Types](#request-and-response-types) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Aborting Requests](#aborting-requests) + - [Access Raw Response Data](#access-raw-response-data) + - [Logging](#logging) + - [Custom Fetch](#custom-fetch) + - [Runtime Compatibility](#runtime-compatibility) +- [Contributing](#contributing) + +## Installation + +```sh +npm i -s @fern/allof +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedApiClient } from "@fern/allof"; + +const client = new SeedApiClient; +await client.createRule({ + name: "name", + executionContext: "prod" +}); +``` + +## Environments + +This SDK allows you to configure different environments for API requests. + +```typescript +import { SeedApiClient, SeedApiEnvironment } from "@fern/allof"; + +const client = new SeedApiClient({ + environment: SeedApiEnvironment.Default, +}); +``` + +## Request and Response Types + +The SDK exports all request and response types as TypeScript interfaces. Simply import them with the +following namespace: + +```typescript +import { SeedApi } from "@fern/allof"; + +const request: SeedApi.SearchRuleTypesRequest = { + ... +}; +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedApiError } from "@fern/allof"; + +try { + await client.createRule(...); +} catch (err) { + if (err instanceof SeedApiError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + console.log(err.rawResponse); + } +} +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `headers` request option. + +```typescript +import { SeedApiClient } from "@fern/allof"; + +const client = new SeedApiClient({ + ... + headers: { + 'X-Custom-Header': 'custom value' + } +}); + +const response = await client.createRule(..., { + headers: { + 'X-Custom-Header': 'custom value' + } +}); +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. + +```typescript +const response = await client.createRule(..., { + queryParams: { + 'customQueryParamKey': 'custom query param value' + } +}); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.createRule(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.createRule(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.createRule(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. +The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. + +```typescript +const { data, rawResponse } = await client.createRule(...).withRawResponse(); + +console.log(data); +console.log(rawResponse.headers['X-My-Header']); +``` + +### Logging + +The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. + +```typescript +import { SeedApiClient, logging } from "@fern/allof"; + +const client = new SeedApiClient({ + ... + logging: { + level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info + logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger + silent: false, // defaults to true, set to false to enable logging + } +}); +``` +The `logging` object can have the following properties: +- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. +- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. +- `silent`: Whether to silence the logger. Defaults to `true`. + +The `level` property can be one of the following values: +- `logging.LogLevel.Debug` +- `logging.LogLevel.Info` +- `logging.LogLevel.Warn` +- `logging.LogLevel.Error` + +To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. + +
+Custom logger examples + +Here's an example using the popular `winston` logging library. +```ts +import winston from 'winston'; + +const winstonLogger = winston.createLogger({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => winstonLogger.debug(msg, ...args), + info: (msg, ...args) => winstonLogger.info(msg, ...args), + warn: (msg, ...args) => winstonLogger.warn(msg, ...args), + error: (msg, ...args) => winstonLogger.error(msg, ...args), +}; +``` + +Here's an example using the popular `pino` logging library. + +```ts +import pino from 'pino'; + +const pinoLogger = pino({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => pinoLogger.debug(args, msg), + info: (msg, ...args) => pinoLogger.info(args, msg), + warn: (msg, ...args) => pinoLogger.warn(args, msg), + error: (msg, ...args) => pinoLogger.error(args, msg), +}; +``` +
+ + +### Custom Fetch + +The SDK provides a low-level `fetch` method for making custom HTTP requests while still +benefiting from SDK-level configuration like authentication, retries, timeouts, and logging. +This is useful for calling API endpoints not yet supported in the SDK. + +```typescript +const response = await client.fetch("/v1/custom/endpoint", { + method: "GET", +}, { + timeoutInSeconds: 30, + maxRetries: 3, + headers: { + "X-Custom-Header": "custom-value", + }, +}); + +const data = await response.json(); +``` + +### Runtime Compatibility + + +The SDK works in the following runtimes: + + + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/allof/biome.json b/seed/ts-sdk/allof/biome.json new file mode 100644 index 000000000000..6b89164f9f99 --- /dev/null +++ b/seed/ts-sdk/allof/biome.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", + "root": true, + "vcs": { + "enabled": false + }, + "files": { + "ignoreUnknown": true, + "includes": [ + "**", + "!!dist", + "!!**/dist", + "!!lib", + "!!**/lib", + "!!_tmp_*", + "!!**/_tmp_*", + "!!*.tmp", + "!!**/*.tmp", + "!!.tmp/", + "!!**/.tmp/", + "!!*.log", + "!!**/*.log", + "!!**/.DS_Store", + "!!**/Thumbs.db" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 120 + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "rules": { + "style": { + "useNodejsImportProtocol": "off" + }, + "suspicious": { + "noAssignInExpressions": "warn", + "noUselessEscapeInString": { + "level": "warn", + "fix": "none", + "options": {} + }, + "noThenProperty": "warn", + "useIterableCallbackReturn": "warn", + "noShadowRestrictedNames": "warn", + "noTsIgnore": { + "level": "warn", + "fix": "none", + "options": {} + }, + "noConfusingVoidType": { + "level": "warn", + "fix": "none", + "options": {} + } + } + } + } +} diff --git a/seed/ts-sdk/allof/package.json b/seed/ts-sdk/allof/package.json new file mode 100644 index 000000000000..b0d091c7b2b1 --- /dev/null +++ b/seed/ts-sdk/allof/package.json @@ -0,0 +1,69 @@ +{ + "name": "@fern/allof", + "version": "0.0.1", + "private": false, + "repository": { + "type": "git", + "url": "git+https://github.com/allof/fern.git" + }, + "type": "commonjs", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.mjs", + "types": "./dist/cjs/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.mts", + "default": "./dist/esm/index.mjs" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + }, + "default": "./dist/cjs/index.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "reference.md", + "README.md", + "LICENSE" + ], + "scripts": { + "format": "biome format --write --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "format:check": "biome format --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "lint": "biome lint --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "lint:fix": "biome lint --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "check": "biome check --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "check:fix": "biome check --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", + "build": "pnpm build:cjs && pnpm build:esm", + "build:cjs": "tsc --project ./tsconfig.cjs.json", + "build:esm": "tsc --project ./tsconfig.esm.json && node scripts/rename-to-esm-files.js dist/esm", + "test": "vitest", + "test:unit": "vitest --project unit", + "test:wire": "vitest --project wire" + }, + "dependencies": {}, + "devDependencies": { + "webpack": "^5.105.4", + "ts-loader": "^9.5.4", + "vitest": "^4.1.1", + "msw": "2.11.2", + "@types/node": "^18.19.70", + "typescript": "~5.9.3", + "@biomejs/biome": "2.4.10" + }, + "browser": { + "fs": false, + "os": false, + "path": false, + "stream": false, + "crypto": false + }, + "packageManager": "pnpm@10.33.0", + "engines": { + "node": ">=18.0.0" + }, + "sideEffects": false +} diff --git a/seed/ts-sdk/allof/pnpm-workspace.yaml b/seed/ts-sdk/allof/pnpm-workspace.yaml new file mode 100644 index 000000000000..6e4c395107df --- /dev/null +++ b/seed/ts-sdk/allof/pnpm-workspace.yaml @@ -0,0 +1 @@ +packages: ['.'] \ No newline at end of file diff --git a/seed/ts-sdk/allof/reference.md b/seed/ts-sdk/allof/reference.md new file mode 100644 index 000000000000..6d75591df79d --- /dev/null +++ b/seed/ts-sdk/allof/reference.md @@ -0,0 +1,225 @@ +# Reference +
client.searchRuleTypes({ ...params }) -> SeedApi.RuleTypeSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.searchRuleTypes(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SeedApi.SearchRuleTypesRequest` + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.createRule({ ...params }) -> SeedApi.RuleResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.createRule({ + name: "name", + executionContext: "prod" +}); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SeedApi.RuleCreateRequest` + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.listUsers() -> SeedApi.UserSearchResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.listUsers(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.getEntity() -> SeedApi.CombinedEntity +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.getEntity(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ +
client.getOrganization() -> SeedApi.Organization +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.getOrganization(); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `SeedApiClient.RequestOptions` + +
+
+
+
+ + +
+
+
+ diff --git a/seed/ts-sdk/allof/scripts/rename-to-esm-files.js b/seed/ts-sdk/allof/scripts/rename-to-esm-files.js new file mode 100644 index 000000000000..dc1df1cbbacb --- /dev/null +++ b/seed/ts-sdk/allof/scripts/rename-to-esm-files.js @@ -0,0 +1,123 @@ +#!/usr/bin/env node + +const fs = require("fs").promises; +const path = require("path"); + +const extensionMap = { + ".js": ".mjs", + ".d.ts": ".d.mts", +}; +const oldExtensions = Object.keys(extensionMap); + +async function findFiles(rootPath) { + const files = []; + + async function scan(directory) { + const entries = await fs.readdir(directory, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + if (entry.name !== "node_modules" && !entry.name.startsWith(".")) { + await scan(fullPath); + } + } else if (entry.isFile()) { + if (oldExtensions.some((ext) => entry.name.endsWith(ext))) { + files.push(fullPath); + } + } + } + } + + await scan(rootPath); + return files; +} + +async function updateFiles(files) { + const updatedFiles = []; + for (const file of files) { + const updated = await updateFileContents(file); + updatedFiles.push(updated); + } + + console.log(`Updated imports in ${updatedFiles.length} files.`); +} + +async function updateFileContents(file) { + const content = await fs.readFile(file, "utf8"); + + let newContent = content; + // Update each extension type defined in the map + for (const [oldExt, newExt] of Object.entries(extensionMap)) { + // Handle static imports/exports + const staticRegex = new RegExp(`(import|export)(.+from\\s+['"])(\\.\\.?\\/[^'"]+)(\\${oldExt})(['"])`, "g"); + newContent = newContent.replace(staticRegex, `$1$2$3${newExt}$5`); + + // Handle dynamic imports (yield import, await import, regular import()) + const dynamicRegex = new RegExp( + `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, + "g", + ); + newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); + } + + if (content !== newContent) { + await fs.writeFile(file, newContent, "utf8"); + return true; + } + return false; +} + +async function renameFiles(files) { + let counter = 0; + for (const file of files) { + const ext = oldExtensions.find((ext) => file.endsWith(ext)); + const newExt = extensionMap[ext]; + + if (newExt) { + const newPath = file.slice(0, -ext.length) + newExt; + await fs.rename(file, newPath); + counter++; + } + } + + console.log(`Renamed ${counter} files.`); +} + +async function main() { + try { + const targetDir = process.argv[2]; + if (!targetDir) { + console.error("Please provide a target directory"); + process.exit(1); + } + + const targetPath = path.resolve(targetDir); + const targetStats = await fs.stat(targetPath); + + if (!targetStats.isDirectory()) { + console.error("The provided path is not a directory"); + process.exit(1); + } + + console.log(`Scanning directory: ${targetDir}`); + + const files = await findFiles(targetDir); + + if (files.length === 0) { + console.log("No matching files found."); + process.exit(0); + } + + console.log(`Found ${files.length} files.`); + await updateFiles(files); + await renameFiles(files); + console.log("\nDone!"); + } catch (error) { + console.error("An error occurred:", error.message); + process.exit(1); + } +} + +main(); diff --git a/seed/ts-sdk/allof/snippet.json b/seed/ts-sdk/allof/snippet.json new file mode 100644 index 000000000000..77c90740f6d5 --- /dev/null +++ b/seed/ts-sdk/allof/snippet.json @@ -0,0 +1,60 @@ +{ + "endpoints": [ + { + "id": { + "path": "/rule-types", + "method": "GET", + "identifier_override": "endpoint_.searchRuleTypes" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.searchRuleTypes();\n" + } + }, + { + "id": { + "path": "/rules", + "method": "POST", + "identifier_override": "endpoint_.createRule" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.createRule({\n name: \"name\",\n executionContext: \"prod\"\n});\n" + } + }, + { + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_.listUsers" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.listUsers();\n" + } + }, + { + "id": { + "path": "/entities", + "method": "GET", + "identifier_override": "endpoint_.getEntity" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.getEntity();\n" + } + }, + { + "id": { + "path": "/organizations", + "method": "GET", + "identifier_override": "endpoint_.getOrganization" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.getOrganization();\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/ts-sdk/allof/src/BaseClient.ts b/seed/ts-sdk/allof/src/BaseClient.ts new file mode 100644 index 000000000000..ad5cd965175e --- /dev/null +++ b/seed/ts-sdk/allof/src/BaseClient.ts @@ -0,0 +1,60 @@ +// This file was auto-generated by Fern from our API Definition. + +import { mergeHeaders } from "./core/headers.js"; +import * as core from "./core/index.js"; +import type * as environments from "./environments.js"; + +export interface BaseClientOptions { + environment?: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | null | undefined>; + /** The default maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The default number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** Provide a custom fetch implementation. Useful for platforms that don't have a built-in fetch or need a custom implementation. */ + fetch?: typeof fetch; + /** Configure logging for the client. */ + logging?: core.logging.LogConfig | core.logging.Logger; +} + +export interface BaseRequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | null | undefined>; +} + +export type NormalizedClientOptions = T & { + logging: core.logging.Logger; +}; + +export function normalizeClientOptions( + options: T, +): NormalizedClientOptions { + const headers = mergeHeaders( + { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/allof", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/allof/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + options?.headers, + ); + + return { + ...options, + logging: core.logging.createLogger(options?.logging), + headers, + } as NormalizedClientOptions; +} diff --git a/seed/ts-sdk/allof/src/Client.ts b/seed/ts-sdk/allof/src/Client.ts new file mode 100644 index 000000000000..01e5b4de5533 --- /dev/null +++ b/seed/ts-sdk/allof/src/Client.ts @@ -0,0 +1,303 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "./api/index.js"; +import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; +import { type NormalizedClientOptions, normalizeClientOptions } from "./BaseClient.js"; +import { mergeHeaders } from "./core/headers.js"; +import * as core from "./core/index.js"; +import * as environments from "./environments.js"; +import { handleNonStatusCodeError } from "./errors/handleNonStatusCodeError.js"; +import * as errors from "./errors/index.js"; + +export declare namespace SeedApiClient { + export type Options = BaseClientOptions; + + export interface RequestOptions extends BaseRequestOptions {} +} + +export class SeedApiClient { + protected readonly _options: NormalizedClientOptions; + + constructor(options: SeedApiClient.Options = {}) { + this._options = normalizeClientOptions(options); + } + + /** + * @param {SeedApi.SearchRuleTypesRequest} request + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.searchRuleTypes() + */ + public searchRuleTypes( + request: SeedApi.SearchRuleTypesRequest = {}, + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__searchRuleTypes(request, requestOptions)); + } + + private async __searchRuleTypes( + request: SeedApi.SearchRuleTypesRequest = {}, + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const { query } = request; + const _queryParams: Record = { + query, + }; + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "rule-types", + ), + method: "GET", + headers: _headers, + queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.RuleTypeSearchResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/rule-types"); + } + + /** + * @param {SeedApi.RuleCreateRequest} request + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.createRule({ + * name: "name", + * executionContext: "prod" + * }) + */ + public createRule( + request: SeedApi.RuleCreateRequest, + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__createRule(request, requestOptions)); + } + + private async __createRule( + request: SeedApi.RuleCreateRequest, + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "rules", + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.RuleResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/rules"); + } + + /** + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.listUsers() + */ + public listUsers( + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__listUsers(requestOptions)); + } + + private async __listUsers( + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "users", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.UserSearchResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/users"); + } + + /** + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.getEntity() + */ + public getEntity(requestOptions?: SeedApiClient.RequestOptions): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getEntity(requestOptions)); + } + + private async __getEntity( + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "entities", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.CombinedEntity, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/entities"); + } + + /** + * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.getOrganization() + */ + public getOrganization( + requestOptions?: SeedApiClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getOrganization(requestOptions)); + } + + private async __getOrganization( + requestOptions?: SeedApiClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SeedApiEnvironment.Default, + "organizations", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as SeedApi.Organization, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/organizations"); + } + + /** + * Make a passthrough request using the SDK's configured auth, retry, logging, etc. + * This is useful for making requests to endpoints not yet supported in the SDK. + * The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL. + * + * @param {Request | string | URL} input - The URL, path, or Request object. + * @param {RequestInit} init - Standard fetch RequestInit options. + * @param {core.PassthroughRequest.RequestOptions} requestOptions - Per-request overrides (timeout, retries, headers, abort signal). + * @returns {Promise} A standard Response object. + */ + public async fetch( + input: Request | string | URL, + init?: RequestInit, + requestOptions?: core.PassthroughRequest.RequestOptions, + ): Promise { + return core.makePassthroughRequest( + input, + init, + { + baseUrl: this._options.baseUrl ?? this._options.environment, + headers: this._options.headers, + timeoutInSeconds: this._options.timeoutInSeconds, + maxRetries: this._options.maxRetries, + fetch: this._options.fetch, + logging: this._options.logging, + }, + requestOptions, + ); + } +} diff --git a/seed/ts-sdk/allof/src/api/client/index.ts b/seed/ts-sdk/allof/src/api/client/index.ts new file mode 100644 index 000000000000..195f9aa8a846 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/client/index.ts @@ -0,0 +1 @@ +export * from "./requests/index.js"; diff --git a/seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts b/seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts new file mode 100644 index 000000000000..bb42c4d24ac9 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../../index.js"; + +/** + * @example + * { + * name: "name", + * executionContext: "prod" + * } + */ +export interface RuleCreateRequest { + name: string; + executionContext: SeedApi.RuleExecutionContext; +} diff --git a/seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts b/seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts new file mode 100644 index 000000000000..502888d9c4e3 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * @example + * {} + */ +export interface SearchRuleTypesRequest { + query?: string; +} diff --git a/seed/ts-sdk/allof/src/api/client/requests/index.ts b/seed/ts-sdk/allof/src/api/client/requests/index.ts new file mode 100644 index 000000000000..07aecd81dd45 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/client/requests/index.ts @@ -0,0 +1,2 @@ +export type { RuleCreateRequest } from "./RuleCreateRequest.js"; +export type { SearchRuleTypesRequest } from "./SearchRuleTypesRequest.js"; diff --git a/seed/ts-sdk/allof/src/api/index.ts b/seed/ts-sdk/allof/src/api/index.ts new file mode 100644 index 000000000000..d9adb1af9a93 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/index.ts @@ -0,0 +1,2 @@ +export * from "./client/index.js"; +export * from "./types/index.js"; diff --git a/seed/ts-sdk/allof/src/api/types/AuditInfo.ts b/seed/ts-sdk/allof/src/api/types/AuditInfo.ts new file mode 100644 index 000000000000..535489319123 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/AuditInfo.ts @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * Common audit metadata. + */ +export interface AuditInfo { + /** The user who created this resource. */ + createdBy?: string | undefined; + /** When this resource was created. */ + createdDateTime?: string | undefined; + /** The user who last modified this resource. */ + modifiedBy?: string | undefined; + /** When this resource was last modified. */ + modifiedDateTime?: string | undefined; +} diff --git a/seed/ts-sdk/allof/src/api/types/BaseOrg.ts b/seed/ts-sdk/allof/src/api/types/BaseOrg.ts new file mode 100644 index 000000000000..eec0dea9a386 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/BaseOrg.ts @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface BaseOrg { + id: string; + metadata?: BaseOrg.Metadata | undefined; +} + +export namespace BaseOrg { + export interface Metadata { + /** Deployment region from BaseOrg. */ + region: string; + /** Subscription tier. */ + tier?: string | undefined; + } +} diff --git a/seed/ts-sdk/allof/src/api/types/CombinedEntity.ts b/seed/ts-sdk/allof/src/api/types/CombinedEntity.ts new file mode 100644 index 000000000000..7cb0214b4706 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/CombinedEntity.ts @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface CombinedEntity { + status: CombinedEntity.Status; + /** Unique identifier. */ + id: string; + /** Display name from Identifiable. */ + name?: string | undefined; + /** A short summary. */ + summary?: string | undefined; +} + +export namespace CombinedEntity { + export const Status = { + Active: "active", + Archived: "archived", + } as const; + export type Status = (typeof Status)[keyof typeof Status]; +} diff --git a/seed/ts-sdk/allof/src/api/types/Describable.ts b/seed/ts-sdk/allof/src/api/types/Describable.ts new file mode 100644 index 000000000000..b5c82cac5a67 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/Describable.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface Describable { + /** Display name from Describable. */ + name?: string | undefined; + /** A short summary. */ + summary?: string | undefined; +} diff --git a/seed/ts-sdk/allof/src/api/types/DetailedOrg.ts b/seed/ts-sdk/allof/src/api/types/DetailedOrg.ts new file mode 100644 index 000000000000..84e0ef063cab --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/DetailedOrg.ts @@ -0,0 +1,14 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface DetailedOrg { + metadata?: DetailedOrg.Metadata | undefined; +} + +export namespace DetailedOrg { + export interface Metadata { + /** Deployment region from DetailedOrg. */ + region: string; + /** Custom domain name. */ + domain?: string | undefined; + } +} diff --git a/seed/ts-sdk/allof/src/api/types/Identifiable.ts b/seed/ts-sdk/allof/src/api/types/Identifiable.ts new file mode 100644 index 000000000000..65d2053f6cb8 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/Identifiable.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface Identifiable { + /** Unique identifier. */ + id: string; + /** Display name from Identifiable. */ + name?: string | undefined; +} diff --git a/seed/ts-sdk/allof/src/api/types/Organization.ts b/seed/ts-sdk/allof/src/api/types/Organization.ts new file mode 100644 index 000000000000..21138b82840f --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/Organization.ts @@ -0,0 +1,16 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface Organization { + name: string; + id: string; + metadata?: Organization.Metadata | undefined; +} + +export namespace Organization { + export interface Metadata { + /** Deployment region from BaseOrg. */ + region: string; + /** Subscription tier. */ + tier?: string | undefined; + } +} diff --git a/seed/ts-sdk/allof/src/api/types/PaginatedResult.ts b/seed/ts-sdk/allof/src/api/types/PaginatedResult.ts new file mode 100644 index 000000000000..ed373200a8e1 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/PaginatedResult.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface PaginatedResult { + paging: SeedApi.PagingCursors; + /** Current page of results from the requested resource. */ + results: unknown[]; +} diff --git a/seed/ts-sdk/allof/src/api/types/PagingCursors.ts b/seed/ts-sdk/allof/src/api/types/PagingCursors.ts new file mode 100644 index 000000000000..2ff3fa532101 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/PagingCursors.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface PagingCursors { + /** Cursor for the next page of results. */ + next: string; + /** Cursor for the previous page of results. */ + previous?: string | undefined; +} diff --git a/seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts b/seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts new file mode 100644 index 000000000000..fe794a8856da --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +/** Execution environment for a rule. */ +export const RuleExecutionContext = { + Prod: "prod", + Staging: "staging", + Dev: "dev", +} as const; +export type RuleExecutionContext = (typeof RuleExecutionContext)[keyof typeof RuleExecutionContext]; diff --git a/seed/ts-sdk/allof/src/api/types/RuleResponse.ts b/seed/ts-sdk/allof/src/api/types/RuleResponse.ts new file mode 100644 index 000000000000..79865f6c0808 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/RuleResponse.ts @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface RuleResponse extends SeedApi.AuditInfo { + id: string; + name: string; + status: RuleResponse.Status; + executionContext?: SeedApi.RuleExecutionContext | undefined; +} + +export namespace RuleResponse { + export const Status = { + Active: "active", + Inactive: "inactive", + Draft: "draft", + } as const; + export type Status = (typeof Status)[keyof typeof Status]; +} diff --git a/seed/ts-sdk/allof/src/api/types/RuleType.ts b/seed/ts-sdk/allof/src/api/types/RuleType.ts new file mode 100644 index 000000000000..ac2bde7133b2 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/RuleType.ts @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface RuleType { + id: string; + name: string; + description?: string | undefined; +} diff --git a/seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts b/seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts new file mode 100644 index 000000000000..fef8e24a64cc --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface RuleTypeSearchResponse { + /** Current page of results from the requested resource. */ + results?: SeedApi.RuleType[] | undefined; + paging: SeedApi.PagingCursors; +} diff --git a/seed/ts-sdk/allof/src/api/types/User.ts b/seed/ts-sdk/allof/src/api/types/User.ts new file mode 100644 index 000000000000..7d0e30eaf136 --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/User.ts @@ -0,0 +1,6 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface User { + id: string; + email: string; +} diff --git a/seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts b/seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts new file mode 100644 index 000000000000..d9c018237c2c --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as SeedApi from "../index.js"; + +export interface UserSearchResponse { + /** Current page of results from the requested resource. */ + results?: SeedApi.User[] | undefined; + paging: SeedApi.PagingCursors; +} diff --git a/seed/ts-sdk/allof/src/api/types/index.ts b/seed/ts-sdk/allof/src/api/types/index.ts new file mode 100644 index 000000000000..ae8a133ce81f --- /dev/null +++ b/seed/ts-sdk/allof/src/api/types/index.ts @@ -0,0 +1,15 @@ +export * from "./AuditInfo.js"; +export * from "./BaseOrg.js"; +export * from "./CombinedEntity.js"; +export * from "./Describable.js"; +export * from "./DetailedOrg.js"; +export * from "./Identifiable.js"; +export * from "./Organization.js"; +export * from "./PaginatedResult.js"; +export * from "./PagingCursors.js"; +export * from "./RuleExecutionContext.js"; +export * from "./RuleResponse.js"; +export * from "./RuleType.js"; +export * from "./RuleTypeSearchResponse.js"; +export * from "./User.js"; +export * from "./UserSearchResponse.js"; diff --git a/seed/ts-sdk/allof/src/core/exports.ts b/seed/ts-sdk/allof/src/core/exports.ts new file mode 100644 index 000000000000..69296d7100d6 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/exports.ts @@ -0,0 +1 @@ +export * from "./logging/exports.js"; diff --git a/seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts b/seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts new file mode 100644 index 000000000000..97ab83c2b195 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts @@ -0,0 +1,23 @@ +import type { RawResponse } from "./RawResponse.js"; + +/** + * The response of an API call. + * It is a successful response or a failed response. + */ +export type APIResponse = SuccessfulResponse | FailedResponse; + +export interface SuccessfulResponse { + ok: true; + body: T; + /** + * @deprecated Use `rawResponse` instead + */ + headers?: Record; + rawResponse: RawResponse; +} + +export interface FailedResponse { + ok: false; + error: T; + rawResponse: RawResponse; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts b/seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts new file mode 100644 index 000000000000..b9e40fb62cc4 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts @@ -0,0 +1,34 @@ +export type BinaryResponse = { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ + bodyUsed: Response["bodyUsed"]; + /** + * Returns a ReadableStream of the response body. + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) + */ + stream: () => Response["body"]; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ + arrayBuffer: () => ReturnType; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ + blob: () => ReturnType; + /** + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) + * Some versions of the Fetch API may not support this method. + */ + bytes?(): Promise; +}; + +export function getBinaryResponse(response: Response): BinaryResponse { + const binaryResponse: BinaryResponse = { + get bodyUsed() { + return response.bodyUsed; + }, + stream: () => response.body, + arrayBuffer: response.arrayBuffer.bind(response), + blob: response.blob.bind(response), + }; + if ("bytes" in response && typeof response.bytes === "function") { + binaryResponse.bytes = response.bytes.bind(response); + } + + return binaryResponse; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts b/seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts new file mode 100644 index 000000000000..998d68f5c20c --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts @@ -0,0 +1,13 @@ +export type SecuritySchemeKey = string; +/** + * A collection of security schemes, where the key is the name of the security scheme and the value is the list of scopes required for that scheme. + * All schemes in the collection must be satisfied for authentication to be successful. + */ +export type SecuritySchemeCollection = Record; +export type AuthScope = string; +export type EndpointMetadata = { + /** + * An array of security scheme collections. Each collection represents an alternative way to authenticate. + */ + security?: SecuritySchemeCollection[]; +}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts b/seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts new file mode 100644 index 000000000000..aad81f0d9040 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts @@ -0,0 +1,14 @@ +import type { EndpointMetadata } from "./EndpointMetadata.js"; +import type { Supplier } from "./Supplier.js"; + +type EndpointSupplierFn = (arg: { endpointMetadata?: EndpointMetadata }) => T | Promise; +export type EndpointSupplier = Supplier | EndpointSupplierFn; +export const EndpointSupplier = { + get: async (supplier: EndpointSupplier, arg: { endpointMetadata?: EndpointMetadata }): Promise => { + if (typeof supplier === "function") { + return (supplier as EndpointSupplierFn)(arg); + } else { + return supplier; + } + }, +}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts new file mode 100644 index 000000000000..928dfeaabae6 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts @@ -0,0 +1,404 @@ +import { toJson } from "../json.js"; +import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; +import type { APIResponse } from "./APIResponse.js"; +import { createRequestUrl } from "./createRequestUrl.js"; +import type { EndpointMetadata } from "./EndpointMetadata.js"; +import { EndpointSupplier } from "./EndpointSupplier.js"; +import { getErrorResponseBody } from "./getErrorResponseBody.js"; +import { getFetchFn } from "./getFetchFn.js"; +import { getRequestBody } from "./getRequestBody.js"; +import { getResponseBody } from "./getResponseBody.js"; +import { Headers } from "./Headers.js"; +import { makeRequest } from "./makeRequest.js"; +import { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; +import { requestWithRetries } from "./requestWithRetries.js"; + +export type FetchFunction = (args: Fetcher.Args) => Promise>; + +export declare namespace Fetcher { + export interface Args { + url: string; + method: string; + contentType?: string; + headers?: Record; + queryParameters?: Record; + body?: unknown; + timeoutMs?: number; + maxRetries?: number; + withCredentials?: boolean; + abortSignal?: AbortSignal; + requestType?: "json" | "file" | "bytes" | "form" | "other"; + responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer" | "binary-response"; + duplex?: "half"; + endpointMetadata?: EndpointMetadata; + fetchFn?: typeof fetch; + logging?: LogConfig | Logger; + } + + export type Error = FailedStatusCodeError | NonJsonError | BodyIsNullError | TimeoutError | UnknownError; + + export interface FailedStatusCodeError { + reason: "status-code"; + statusCode: number; + body: unknown; + } + + export interface NonJsonError { + reason: "non-json"; + statusCode: number; + rawBody: string; + } + + export interface BodyIsNullError { + reason: "body-is-null"; + statusCode: number; + } + + export interface TimeoutError { + reason: "timeout"; + cause?: unknown; + } + + export interface UnknownError { + reason: "unknown"; + errorMessage: string; + cause?: unknown; + } +} + +const SENSITIVE_HEADERS = new Set([ + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "cookie", + "set-cookie", + "proxy-authorization", + "proxy-authenticate", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token", +]); + +function redactHeaders(headers: Headers | Record): Record { + const filtered: Record = {}; + for (const [key, value] of headers instanceof Headers ? headers.entries() : Object.entries(headers)) { + if (SENSITIVE_HEADERS.has(key.toLowerCase())) { + filtered[key] = "[REDACTED]"; + } else { + filtered[key] = value; + } + } + return filtered; +} + +const SENSITIVE_QUERY_PARAMS = new Set([ + "api_key", + "api-key", + "apikey", + "token", + "access_token", + "access-token", + "auth_token", + "auth-token", + "password", + "passwd", + "secret", + "api_secret", + "api-secret", + "apisecret", + "key", + "session", + "session_id", + "session-id", +]); + +function redactQueryParameters(queryParameters?: Record): Record | undefined { + if (queryParameters == null) { + return queryParameters; + } + const redacted: Record = {}; + for (const [key, value] of Object.entries(queryParameters)) { + if (SENSITIVE_QUERY_PARAMS.has(key.toLowerCase())) { + redacted[key] = "[REDACTED]"; + } else { + redacted[key] = value; + } + } + return redacted; +} + +function redactUrl(url: string): string { + const protocolIndex = url.indexOf("://"); + if (protocolIndex === -1) return url; + + const afterProtocol = protocolIndex + 3; + + // Find the first delimiter that marks the end of the authority section + const pathStart = url.indexOf("/", afterProtocol); + let queryStart = url.indexOf("?", afterProtocol); + let fragmentStart = url.indexOf("#", afterProtocol); + + const firstDelimiter = Math.min( + pathStart === -1 ? url.length : pathStart, + queryStart === -1 ? url.length : queryStart, + fragmentStart === -1 ? url.length : fragmentStart, + ); + + // Find the LAST @ before the delimiter (handles multiple @ in credentials) + let atIndex = -1; + for (let i = afterProtocol; i < firstDelimiter; i++) { + if (url[i] === "@") { + atIndex = i; + } + } + + if (atIndex !== -1) { + url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`; + } + + // Recalculate queryStart since url might have changed + queryStart = url.indexOf("?"); + if (queryStart === -1) return url; + + fragmentStart = url.indexOf("#", queryStart); + const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length; + const queryString = url.slice(queryStart + 1, queryEnd); + + if (queryString.length === 0) return url; + + // FAST PATH: Quick check if any sensitive keywords present + // Using indexOf is faster than regex for simple substring matching + const lower = queryString.toLowerCase(); + const hasSensitive = + lower.includes("token") || + lower.includes("key") || + lower.includes("password") || + lower.includes("passwd") || + lower.includes("secret") || + lower.includes("session") || + lower.includes("auth"); + + if (!hasSensitive) { + return url; + } + + // SLOW PATH: Parse and redact + const redactedParams: string[] = []; + const params = queryString.split("&"); + + for (const param of params) { + const equalIndex = param.indexOf("="); + if (equalIndex === -1) { + redactedParams.push(param); + continue; + } + + const key = param.slice(0, equalIndex); + let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()); + + if (!shouldRedact && key.includes("%")) { + try { + const decodedKey = decodeURIComponent(key); + shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase()); + } catch {} + } + + redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param); + } + + return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd); +} + +async function getHeaders(args: Fetcher.Args): Promise { + const newHeaders: Headers = new Headers(); + + newHeaders.set( + "Accept", + args.responseType === "json" + ? "application/json" + : args.responseType === "text" + ? "text/plain" + : args.responseType === "sse" + ? "text/event-stream" + : "*/*", + ); + if (args.body !== undefined && args.contentType != null) { + newHeaders.set("Content-Type", args.contentType); + } + + if (args.headers == null) { + return newHeaders; + } + + for (const [key, value] of Object.entries(args.headers)) { + const result = await EndpointSupplier.get(value, { endpointMetadata: args.endpointMetadata ?? {} }); + if (typeof result === "string") { + newHeaders.set(key, result); + continue; + } + if (result == null) { + continue; + } + newHeaders.set(key, `${result}`); + } + return newHeaders; +} + +export async function fetcherImpl(args: Fetcher.Args): Promise> { + const url = createRequestUrl(args.url, args.queryParameters); + const requestBody: BodyInit | undefined = await getRequestBody({ + body: args.body, + type: args.requestType ?? "other", + }); + const fetchFn = args.fetchFn ?? (await getFetchFn()); + const headers = await getHeaders(args); + const logger = createLogger(args.logging); + + if (logger.isDebug()) { + const metadata = { + method: args.method, + url: redactUrl(url), + headers: redactHeaders(headers), + queryParameters: redactQueryParameters(args.queryParameters), + hasBody: requestBody != null, + }; + logger.debug("Making HTTP request", metadata); + } + + try { + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + url, + args.method, + headers, + requestBody, + args.timeoutMs, + args.abortSignal, + args.withCredentials, + args.duplex, + args.responseType === "streaming" || args.responseType === "sse", + ), + args.maxRetries, + ); + + if (response.status >= 200 && response.status < 400) { + if (logger.isDebug()) { + const metadata = { + method: args.method, + url: redactUrl(url), + statusCode: response.status, + responseHeaders: redactHeaders(response.headers), + }; + logger.debug("HTTP request succeeded", metadata); + } + const body = await getResponseBody(response, args.responseType); + return { + ok: true, + body: body as R, + headers: response.headers, + rawResponse: toRawResponse(response), + }; + } else { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + statusCode: response.status, + responseHeaders: redactHeaders(Object.fromEntries(response.headers.entries())), + }; + logger.error("HTTP request failed with error status", metadata); + } + return { + ok: false, + error: { + reason: "status-code", + statusCode: response.status, + body: await getErrorResponseBody(response), + }, + rawResponse: toRawResponse(response), + }; + } + } catch (error) { + if (args.abortSignal?.aborted) { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + }; + logger.error("HTTP request was aborted", metadata); + } + return { + ok: false, + error: { + reason: "unknown", + errorMessage: "The user aborted a request", + cause: error, + }, + rawResponse: abortRawResponse, + }; + } else if (error instanceof Error && error.name === "AbortError") { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + timeoutMs: args.timeoutMs, + }; + logger.error("HTTP request timed out", metadata); + } + return { + ok: false, + error: { + reason: "timeout", + cause: error, + }, + rawResponse: abortRawResponse, + }; + } else if (error instanceof Error) { + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + errorMessage: error.message, + }; + logger.error("HTTP request failed with error", metadata); + } + return { + ok: false, + error: { + reason: "unknown", + errorMessage: error.message, + cause: error, + }, + rawResponse: unknownRawResponse, + }; + } + + if (logger.isError()) { + const metadata = { + method: args.method, + url: redactUrl(url), + error: toJson(error), + }; + logger.error("HTTP request failed with unknown error", metadata); + } + return { + ok: false, + error: { + reason: "unknown", + errorMessage: toJson(error), + cause: error, + }, + rawResponse: unknownRawResponse, + }; + } +} + +export const fetcher: FetchFunction = fetcherImpl; diff --git a/seed/ts-sdk/allof/src/core/fetcher/Headers.ts b/seed/ts-sdk/allof/src/core/fetcher/Headers.ts new file mode 100644 index 000000000000..af841aa24f55 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/Headers.ts @@ -0,0 +1,93 @@ +let Headers: typeof globalThis.Headers; + +if (typeof globalThis.Headers !== "undefined") { + Headers = globalThis.Headers; +} else { + Headers = class Headers implements Headers { + private headers: Map; + + constructor(init?: HeadersInit) { + this.headers = new Map(); + + if (init) { + if (init instanceof Headers) { + init.forEach((value, key) => this.append(key, value)); + } else if (Array.isArray(init)) { + for (const [key, value] of init) { + if (typeof key === "string" && typeof value === "string") { + this.append(key, value); + } else { + throw new TypeError("Each header entry must be a [string, string] tuple"); + } + } + } else { + for (const [key, value] of Object.entries(init)) { + if (typeof value === "string") { + this.append(key, value); + } else { + throw new TypeError("Header values must be strings"); + } + } + } + } + } + + append(name: string, value: string): void { + const key = name.toLowerCase(); + const existing = this.headers.get(key) || []; + this.headers.set(key, [...existing, value]); + } + + delete(name: string): void { + const key = name.toLowerCase(); + this.headers.delete(key); + } + + get(name: string): string | null { + const key = name.toLowerCase(); + const values = this.headers.get(key); + return values ? values.join(", ") : null; + } + + has(name: string): boolean { + const key = name.toLowerCase(); + return this.headers.has(key); + } + + set(name: string, value: string): void { + const key = name.toLowerCase(); + this.headers.set(key, [value]); + } + + forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: unknown): void { + const boundCallback = thisArg ? callbackfn.bind(thisArg) : callbackfn; + this.headers.forEach((values, key) => boundCallback(values.join(", "), key, this)); + } + + getSetCookie(): string[] { + return this.headers.get("set-cookie") || []; + } + + *entries(): HeadersIterator<[string, string]> { + for (const [key, values] of this.headers.entries()) { + yield [key, values.join(", ")]; + } + } + + *keys(): HeadersIterator { + yield* this.headers.keys(); + } + + *values(): HeadersIterator { + for (const values of this.headers.values()) { + yield values.join(", "); + } + } + + [Symbol.iterator](): HeadersIterator<[string, string]> { + return this.entries(); + } + }; +} + +export { Headers }; diff --git a/seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts b/seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts new file mode 100644 index 000000000000..692ca7d795f0 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts @@ -0,0 +1,116 @@ +import type { WithRawResponse } from "./RawResponse.js"; + +/** + * A promise that returns the parsed response and lets you retrieve the raw response too. + */ +export class HttpResponsePromise extends Promise { + private innerPromise: Promise>; + private unwrappedPromise: Promise | undefined; + + private constructor(promise: Promise>) { + // Initialize with a no-op to avoid premature parsing + super((resolve) => { + resolve(undefined as unknown as T); + }); + this.innerPromise = promise; + } + + /** + * Creates an `HttpResponsePromise` from a function that returns a promise. + * + * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. + * @param args - Arguments to pass to the function. + * @returns An `HttpResponsePromise` instance. + */ + public static fromFunction Promise>, T>( + fn: F, + ...args: Parameters + ): HttpResponsePromise { + return new HttpResponsePromise(fn(...args)); + } + + /** + * Creates a function that returns an `HttpResponsePromise` from a function that returns a promise. + * + * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. + * @returns A function that returns an `HttpResponsePromise` instance. + */ + public static interceptFunction< + F extends (...args: never[]) => Promise>, + T = Awaited>["data"], + >(fn: F): (...args: Parameters) => HttpResponsePromise { + return (...args: Parameters): HttpResponsePromise => { + return HttpResponsePromise.fromPromise(fn(...args)); + }; + } + + /** + * Creates an `HttpResponsePromise` from an existing promise. + * + * @param promise - A promise resolving to a `WithRawResponse` object. + * @returns An `HttpResponsePromise` instance. + */ + public static fromPromise(promise: Promise>): HttpResponsePromise { + return new HttpResponsePromise(promise); + } + + /** + * Creates an `HttpResponsePromise` from an executor function. + * + * @param executor - A function that takes resolve and reject callbacks to create a promise. + * @returns An `HttpResponsePromise` instance. + */ + public static fromExecutor( + executor: (resolve: (value: WithRawResponse) => void, reject: (reason?: unknown) => void) => void, + ): HttpResponsePromise { + const promise = new Promise>(executor); + return new HttpResponsePromise(promise); + } + + /** + * Creates an `HttpResponsePromise` from a resolved result. + * + * @param result - A `WithRawResponse` object to resolve immediately. + * @returns An `HttpResponsePromise` instance. + */ + public static fromResult(result: WithRawResponse): HttpResponsePromise { + const promise = Promise.resolve(result); + return new HttpResponsePromise(promise); + } + + private unwrap(): Promise { + if (!this.unwrappedPromise) { + this.unwrappedPromise = this.innerPromise.then(({ data }) => data); + } + return this.unwrappedPromise; + } + + /** @inheritdoc */ + public override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, + ): Promise { + return this.unwrap().then(onfulfilled, onrejected); + } + + /** @inheritdoc */ + public override catch( + onrejected?: ((reason: unknown) => TResult | PromiseLike) | null, + ): Promise { + return this.unwrap().catch(onrejected); + } + + /** @inheritdoc */ + public override finally(onfinally?: (() => void) | null): Promise { + return this.unwrap().finally(onfinally); + } + + /** + * Retrieves the data and raw response. + * + * @returns A promise resolving to a `WithRawResponse` object. + */ + public async withRawResponse(): Promise> { + return await this.innerPromise; + } +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts b/seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts new file mode 100644 index 000000000000..37fb44e2aa99 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts @@ -0,0 +1,61 @@ +import { Headers } from "./Headers.js"; + +/** + * The raw response from the fetch call excluding the body. + */ +export type RawResponse = Omit< + { + [K in keyof Response as Response[K] extends Function ? never : K]: Response[K]; // strips out functions + }, + "ok" | "body" | "bodyUsed" +>; // strips out body and bodyUsed + +/** + * A raw response indicating that the request was aborted. + */ +export const abortRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 499, + statusText: "Client Closed Request", + type: "error", + url: "", +} as const; + +/** + * A raw response indicating an unknown error. + */ +export const unknownRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 0, + statusText: "Unknown Error", + type: "error", + url: "", +} as const; + +/** + * Converts a `RawResponse` object into a `RawResponse` by extracting its properties, + * excluding the `body` and `bodyUsed` fields. + * + * @param response - The `RawResponse` object to convert. + * @returns A `RawResponse` object containing the extracted properties of the input response. + */ +export function toRawResponse(response: Response): RawResponse { + return { + headers: response.headers, + redirected: response.redirected, + status: response.status, + statusText: response.statusText, + type: response.type, + url: response.url, + }; +} + +/** + * Creates a `RawResponse` from a standard `Response` object. + */ +export interface WithRawResponse { + readonly data: T; + readonly rawResponse: RawResponse; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/Supplier.ts b/seed/ts-sdk/allof/src/core/fetcher/Supplier.ts new file mode 100644 index 000000000000..867c931c02f4 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/Supplier.ts @@ -0,0 +1,11 @@ +export type Supplier = T | Promise | (() => T | Promise); + +export const Supplier = { + get: async (supplier: Supplier): Promise => { + if (typeof supplier === "function") { + return (supplier as () => T)(); + } else { + return supplier; + } + }, +}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts b/seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts new file mode 100644 index 000000000000..88e13265e112 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts @@ -0,0 +1,6 @@ +import { toQueryString } from "../url/qs.js"; + +export function createRequestUrl(baseUrl: string, queryParameters?: Record): string { + const queryString = toQueryString(queryParameters, { arrayFormat: "repeat" }); + return queryString ? `${baseUrl}?${queryString}` : baseUrl; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts b/seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts new file mode 100644 index 000000000000..7cf4e623c2f5 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts @@ -0,0 +1,33 @@ +import { fromJson } from "../json.js"; +import { getResponseBody } from "./getResponseBody.js"; + +export async function getErrorResponseBody(response: Response): Promise { + let contentType = response.headers.get("Content-Type")?.toLowerCase(); + if (contentType == null || contentType.length === 0) { + return getResponseBody(response); + } + + if (contentType.indexOf(";") !== -1) { + contentType = contentType.split(";")[0]?.trim() ?? ""; + } + switch (contentType) { + case "application/hal+json": + case "application/json": + case "application/ld+json": + case "application/problem+json": + case "application/vnd.api+json": + case "text/json": { + const text = await response.text(); + return text.length > 0 ? fromJson(text) : undefined; + } + default: + if (contentType.startsWith("application/vnd.") && contentType.endsWith("+json")) { + const text = await response.text(); + return text.length > 0 ? fromJson(text) : undefined; + } + + // Fallback to plain text if content type is not recognized + // Even if no body is present, the response will be an empty string + return await response.text(); + } +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts new file mode 100644 index 000000000000..9f845b956392 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts @@ -0,0 +1,3 @@ +export async function getFetchFn(): Promise { + return fetch; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getHeader.ts b/seed/ts-sdk/allof/src/core/fetcher/getHeader.ts new file mode 100644 index 000000000000..50f922b0e87f --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/getHeader.ts @@ -0,0 +1,8 @@ +export function getHeader(headers: Record, header: string): string | undefined { + for (const [headerKey, headerValue] of Object.entries(headers)) { + if (headerKey.toLowerCase() === header.toLowerCase()) { + return headerValue; + } + } + return undefined; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts b/seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts new file mode 100644 index 000000000000..91d9d81f50e5 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts @@ -0,0 +1,20 @@ +import { toJson } from "../json.js"; +import { toQueryString } from "../url/qs.js"; + +export declare namespace GetRequestBody { + interface Args { + body: unknown; + type: "json" | "file" | "bytes" | "form" | "other"; + } +} + +export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { + if (type === "form") { + return toQueryString(body, { arrayFormat: "repeat", encode: true }); + } + if (type.includes("json")) { + return toJson(body); + } else { + return body as BodyInit; + } +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts new file mode 100644 index 000000000000..708d55728f2b --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts @@ -0,0 +1,58 @@ +import { fromJson } from "../json.js"; +import { getBinaryResponse } from "./BinaryResponse.js"; + +export async function getResponseBody(response: Response, responseType?: string): Promise { + switch (responseType) { + case "binary-response": + return getBinaryResponse(response); + case "blob": + return await response.blob(); + case "arrayBuffer": + return await response.arrayBuffer(); + case "sse": + if (response.body == null) { + return { + ok: false, + error: { + reason: "body-is-null", + statusCode: response.status, + }, + }; + } + return response.body; + case "streaming": + if (response.body == null) { + return { + ok: false, + error: { + reason: "body-is-null", + statusCode: response.status, + }, + }; + } + + return response.body; + + case "text": + return await response.text(); + } + + // if responseType is "json" or not specified, try to parse as JSON + const text = await response.text(); + if (text.length > 0) { + try { + const responseBody = fromJson(text); + return responseBody; + } catch (_err) { + return { + ok: false, + error: { + reason: "non-json", + statusCode: response.status, + rawBody: text, + }, + }; + } + } + return undefined; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/index.ts b/seed/ts-sdk/allof/src/core/fetcher/index.ts new file mode 100644 index 000000000000..bd5db362c778 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/index.ts @@ -0,0 +1,13 @@ +export type { APIResponse } from "./APIResponse.js"; +export type { BinaryResponse } from "./BinaryResponse.js"; +export type { EndpointMetadata } from "./EndpointMetadata.js"; +export { EndpointSupplier } from "./EndpointSupplier.js"; +export type { Fetcher, FetchFunction } from "./Fetcher.js"; +export { fetcher } from "./Fetcher.js"; +export { getHeader } from "./getHeader.js"; +export { HttpResponsePromise } from "./HttpResponsePromise.js"; +export type { PassthroughRequest } from "./makePassthroughRequest.js"; +export { makePassthroughRequest } from "./makePassthroughRequest.js"; +export type { RawResponse, WithRawResponse } from "./RawResponse.js"; +export { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; +export { Supplier } from "./Supplier.js"; diff --git a/seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts b/seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts new file mode 100644 index 000000000000..f5ba761400f8 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts @@ -0,0 +1,189 @@ +import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; +import { join } from "../url/join.js"; +import { EndpointSupplier } from "./EndpointSupplier.js"; +import { getFetchFn } from "./getFetchFn.js"; +import { makeRequest } from "./makeRequest.js"; +import { requestWithRetries } from "./requestWithRetries.js"; +import { Supplier } from "./Supplier.js"; + +export declare namespace PassthroughRequest { + /** + * Per-request options that can override the SDK client defaults. + */ + export interface RequestOptions { + /** Override the default timeout for this request (in seconds). */ + timeoutInSeconds?: number; + /** Override the default number of retries for this request. */ + maxRetries?: number; + /** Additional headers to include in this request. */ + headers?: Record; + /** Abort signal for this request. */ + abortSignal?: AbortSignal; + } + + /** + * SDK client configuration used by the passthrough fetch method. + */ + export interface ClientOptions { + /** The base URL or environment for the client. */ + environment?: Supplier; + /** Override the base URL. */ + baseUrl?: Supplier; + /** Default headers to include in requests. */ + headers?: Record; + /** Default maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** Default number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A custom fetch function. */ + fetch?: typeof fetch; + /** Logging configuration. */ + logging?: LogConfig | Logger; + /** A function that returns auth headers. */ + getAuthHeaders?: () => Promise>; + } +} + +/** + * Makes a passthrough HTTP request using the SDK's configuration (auth, retry, logging, etc.) + * while mimicking the standard `fetch` API. + * + * @param input - The URL, path, or Request object. If a relative path, it will be resolved against the configured base URL. + * @param init - Standard RequestInit options (method, headers, body, signal, etc.) + * @param clientOptions - SDK client options (auth, default headers, logging, etc.) + * @param requestOptions - Per-request overrides (timeout, retries, extra headers, abort signal). + * @returns A standard Response object. + */ +export async function makePassthroughRequest( + input: Request | string | URL, + init: RequestInit | undefined, + clientOptions: PassthroughRequest.ClientOptions, + requestOptions?: PassthroughRequest.RequestOptions, +): Promise { + const logger = createLogger(clientOptions.logging); + + // Extract URL and default init properties from Request object if provided + let url: string; + let effectiveInit: RequestInit | undefined = init; + if (input instanceof Request) { + url = input.url; + // If no explicit init provided, extract properties from the Request object + if (init == null) { + effectiveInit = { + method: input.method, + headers: Object.fromEntries(input.headers.entries()), + body: input.body, + signal: input.signal, + credentials: input.credentials, + cache: input.cache as RequestCache, + redirect: input.redirect, + referrer: input.referrer, + integrity: input.integrity, + mode: input.mode, + }; + } + } else { + url = input instanceof URL ? input.toString() : input; + } + + // Resolve the base URL + const baseUrl = + (clientOptions.baseUrl != null ? await Supplier.get(clientOptions.baseUrl) : undefined) ?? + (clientOptions.environment != null ? await Supplier.get(clientOptions.environment) : undefined); + + // Determine the full URL + let fullUrl: string; + if (url.startsWith("http://") || url.startsWith("https://")) { + fullUrl = url; + } else if (baseUrl != null) { + fullUrl = join(baseUrl, url); + } else { + fullUrl = url; + } + + // Merge headers: SDK default headers -> auth headers -> user-provided headers + const mergedHeaders: Record = {}; + + // Apply SDK default headers (resolve suppliers) + if (clientOptions.headers != null) { + for (const [key, value] of Object.entries(clientOptions.headers)) { + const resolved = await EndpointSupplier.get(value, { endpointMetadata: {} }); + if (resolved != null) { + mergedHeaders[key.toLowerCase()] = `${resolved}`; + } + } + } + + // Apply auth headers + if (clientOptions.getAuthHeaders != null) { + const authHeaders = await clientOptions.getAuthHeaders(); + for (const [key, value] of Object.entries(authHeaders)) { + mergedHeaders[key.toLowerCase()] = value; + } + } + + // Apply user-provided headers from init + if (effectiveInit?.headers != null) { + const initHeaders = + effectiveInit.headers instanceof Headers + ? Object.fromEntries(effectiveInit.headers.entries()) + : Array.isArray(effectiveInit.headers) + ? Object.fromEntries(effectiveInit.headers) + : effectiveInit.headers; + for (const [key, value] of Object.entries(initHeaders)) { + if (value != null) { + mergedHeaders[key.toLowerCase()] = value; + } + } + } + + // Apply per-request option headers (highest priority) + if (requestOptions?.headers != null) { + for (const [key, value] of Object.entries(requestOptions.headers)) { + mergedHeaders[key.toLowerCase()] = value; + } + } + + const method = effectiveInit?.method ?? "GET"; + const body = effectiveInit?.body; + const timeoutInSeconds = requestOptions?.timeoutInSeconds ?? clientOptions.timeoutInSeconds; + const timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : undefined; + const maxRetries = requestOptions?.maxRetries ?? clientOptions.maxRetries; + const abortSignal = requestOptions?.abortSignal ?? effectiveInit?.signal ?? undefined; + const fetchFn = clientOptions.fetch ?? (await getFetchFn()); + + if (logger.isDebug()) { + logger.debug("Making passthrough HTTP request", { + method, + url: fullUrl, + hasBody: body != null, + }); + } + + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + fullUrl, + method, + mergedHeaders, + body ?? undefined, + timeoutMs, + abortSignal, + effectiveInit?.credentials === "include", + undefined, // duplex + false, // disableCache + ), + maxRetries, + ); + + if (logger.isDebug()) { + logger.debug("Passthrough HTTP request completed", { + method, + url: fullUrl, + statusCode: response.status, + }); + } + + return response; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts b/seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts new file mode 100644 index 000000000000..360a86df40ad --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts @@ -0,0 +1,70 @@ +import { anySignal, getTimeoutSignal } from "./signals.js"; + +/** + * Cached result of checking whether the current runtime supports + * the `cache` option in `Request`. Some runtimes (e.g. Cloudflare Workers) + * throw a TypeError when this option is used. + */ +let _cacheNoStoreSupported: boolean | undefined; +export function isCacheNoStoreSupported(): boolean { + if (_cacheNoStoreSupported != null) { + return _cacheNoStoreSupported; + } + try { + new Request("http://localhost", { cache: "no-store" }); + _cacheNoStoreSupported = true; + } catch { + _cacheNoStoreSupported = false; + } + return _cacheNoStoreSupported; +} + +/** + * Reset the cached result of `isCacheNoStoreSupported`. Exposed for testing only. + */ +export function resetCacheNoStoreSupported(): void { + _cacheNoStoreSupported = undefined; +} + +export const makeRequest = async ( + fetchFn: (url: string, init: RequestInit) => Promise, + url: string, + method: string, + headers: Headers | Record, + requestBody: BodyInit | undefined, + timeoutMs?: number, + abortSignal?: AbortSignal, + withCredentials?: boolean, + duplex?: "half", + disableCache?: boolean, +): Promise => { + const signals: AbortSignal[] = []; + + let timeoutAbortId: ReturnType | undefined; + if (timeoutMs != null) { + const { signal, abortId } = getTimeoutSignal(timeoutMs); + timeoutAbortId = abortId; + signals.push(signal); + } + + if (abortSignal != null) { + signals.push(abortSignal); + } + const newSignals = anySignal(signals); + const response = await fetchFn(url, { + method: method, + headers, + body: requestBody, + signal: newSignals, + credentials: withCredentials ? "include" : undefined, + // @ts-ignore + duplex, + ...(disableCache && isCacheNoStoreSupported() ? { cache: "no-store" as RequestCache } : {}), + }); + + if (timeoutAbortId != null) { + clearTimeout(timeoutAbortId); + } + + return response; +}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts new file mode 100644 index 000000000000..1f689688c4b2 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts @@ -0,0 +1,64 @@ +const INITIAL_RETRY_DELAY = 1000; // in milliseconds +const MAX_RETRY_DELAY = 60000; // in milliseconds +const DEFAULT_MAX_RETRIES = 2; +const JITTER_FACTOR = 0.2; // 20% random jitter + +function addPositiveJitter(delay: number): number { + const jitterMultiplier = 1 + Math.random() * JITTER_FACTOR; + return delay * jitterMultiplier; +} + +function addSymmetricJitter(delay: number): number { + const jitterMultiplier = 1 + (Math.random() - 0.5) * JITTER_FACTOR; + return delay * jitterMultiplier; +} + +function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { + const retryAfter = response.headers.get("Retry-After"); + if (retryAfter) { + const retryAfterSeconds = parseInt(retryAfter, 10); + if (!Number.isNaN(retryAfterSeconds) && retryAfterSeconds > 0) { + return Math.min(retryAfterSeconds * 1000, MAX_RETRY_DELAY); + } + + const retryAfterDate = new Date(retryAfter); + if (!Number.isNaN(retryAfterDate.getTime())) { + const delay = retryAfterDate.getTime() - Date.now(); + if (delay > 0) { + return Math.min(Math.max(delay, 0), MAX_RETRY_DELAY); + } + } + } + + const rateLimitReset = response.headers.get("X-RateLimit-Reset"); + if (rateLimitReset) { + const resetTime = parseInt(rateLimitReset, 10); + if (!Number.isNaN(resetTime)) { + const delay = resetTime * 1000 - Date.now(); + if (delay > 0) { + return addPositiveJitter(Math.min(delay, MAX_RETRY_DELAY)); + } + } + } + + return addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** retryAttempt, MAX_RETRY_DELAY)); +} + +export async function requestWithRetries( + requestFn: () => Promise, + maxRetries: number = DEFAULT_MAX_RETRIES, +): Promise { + let response: Response = await requestFn(); + + for (let i = 0; i < maxRetries; ++i) { + if ([408, 429].includes(response.status) || response.status >= 500) { + const delay = getRetryDelayFromHeaders(response, i); + + await new Promise((resolve) => setTimeout(resolve, delay)); + response = await requestFn(); + } else { + break; + } + } + return response!; +} diff --git a/seed/ts-sdk/allof/src/core/fetcher/signals.ts b/seed/ts-sdk/allof/src/core/fetcher/signals.ts new file mode 100644 index 000000000000..7bd3757ec3a7 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/fetcher/signals.ts @@ -0,0 +1,26 @@ +const TIMEOUT = "timeout"; + +export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: ReturnType } { + const controller = new AbortController(); + const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); + return { signal: controller.signal, abortId }; +} + +export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { + const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args) as AbortSignal[]; + + const controller = new AbortController(); + + for (const signal of signals) { + if (signal.aborted) { + controller.abort((signal as any)?.reason); + break; + } + + signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { + signal: controller.signal, + }); + } + + return controller.signal; +} diff --git a/seed/ts-sdk/allof/src/core/headers.ts b/seed/ts-sdk/allof/src/core/headers.ts new file mode 100644 index 000000000000..be45c4552a35 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/headers.ts @@ -0,0 +1,33 @@ +export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { + const result: Record = {}; + + for (const [key, value] of headersArray + .filter((headers) => headers != null) + .flatMap((headers) => Object.entries(headers))) { + const insensitiveKey = key.toLowerCase(); + if (value != null) { + result[insensitiveKey] = value; + } else if (insensitiveKey in result) { + delete result[insensitiveKey]; + } + } + + return result; +} + +export function mergeOnlyDefinedHeaders( + ...headersArray: (Record | null | undefined)[] +): Record { + const result: Record = {}; + + for (const [key, value] of headersArray + .filter((headers) => headers != null) + .flatMap((headers) => Object.entries(headers))) { + const insensitiveKey = key.toLowerCase(); + if (value != null) { + result[insensitiveKey] = value; + } + } + + return result; +} diff --git a/seed/ts-sdk/allof/src/core/index.ts b/seed/ts-sdk/allof/src/core/index.ts new file mode 100644 index 000000000000..afa8351fcf85 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/index.ts @@ -0,0 +1,4 @@ +export * from "./fetcher/index.js"; +export * as logging from "./logging/index.js"; +export * from "./runtime/index.js"; +export * as url from "./url/index.js"; diff --git a/seed/ts-sdk/allof/src/core/json.ts b/seed/ts-sdk/allof/src/core/json.ts new file mode 100644 index 000000000000..c052f3249f4f --- /dev/null +++ b/seed/ts-sdk/allof/src/core/json.ts @@ -0,0 +1,27 @@ +/** + * Serialize a value to JSON + * @param value A JavaScript value, usually an object or array, to be converted. + * @param replacer A function that transforms the results. + * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. + * @returns JSON string + */ +export const toJson = ( + value: unknown, + replacer?: (this: unknown, key: string, value: unknown) => unknown, + space?: string | number, +): string => { + return JSON.stringify(value, replacer, space); +}; + +/** + * Parse JSON string to object, array, or other type + * @param text A valid JSON string. + * @param reviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is. + * @returns Parsed object, array, or other type + */ +export function fromJson( + text: string, + reviver?: (this: unknown, key: string, value: unknown) => unknown, +): T { + return JSON.parse(text, reviver); +} diff --git a/seed/ts-sdk/allof/src/core/logging/exports.ts b/seed/ts-sdk/allof/src/core/logging/exports.ts new file mode 100644 index 000000000000..88f6c00db0cf --- /dev/null +++ b/seed/ts-sdk/allof/src/core/logging/exports.ts @@ -0,0 +1,19 @@ +import * as logger from "./logger.js"; + +export namespace logging { + /** + * Configuration for logger instances. + */ + export type LogConfig = logger.LogConfig; + export type LogLevel = logger.LogLevel; + export const LogLevel: typeof logger.LogLevel = logger.LogLevel; + export type ILogger = logger.ILogger; + /** + * Console logger implementation that outputs to the console. + */ + export type ConsoleLogger = logger.ConsoleLogger; + /** + * Console logger implementation that outputs to the console. + */ + export const ConsoleLogger: typeof logger.ConsoleLogger = logger.ConsoleLogger; +} diff --git a/seed/ts-sdk/allof/src/core/logging/index.ts b/seed/ts-sdk/allof/src/core/logging/index.ts new file mode 100644 index 000000000000..d81cc32c40f9 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/logging/index.ts @@ -0,0 +1 @@ +export * from "./logger.js"; diff --git a/seed/ts-sdk/allof/src/core/logging/logger.ts b/seed/ts-sdk/allof/src/core/logging/logger.ts new file mode 100644 index 000000000000..a3f3673cda93 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/logging/logger.ts @@ -0,0 +1,203 @@ +export const LogLevel = { + Debug: "debug", + Info: "info", + Warn: "warn", + Error: "error", +} as const; +export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; +const logLevelMap: Record = { + [LogLevel.Debug]: 1, + [LogLevel.Info]: 2, + [LogLevel.Warn]: 3, + [LogLevel.Error]: 4, +}; + +export interface ILogger { + /** + * Logs a debug message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + debug(message: string, ...args: unknown[]): void; + /** + * Logs an info message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + info(message: string, ...args: unknown[]): void; + /** + * Logs a warning message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + warn(message: string, ...args: unknown[]): void; + /** + * Logs an error message. + * @param message - The message to log + * @param args - Additional arguments to log + */ + error(message: string, ...args: unknown[]): void; +} + +/** + * Configuration for logger initialization. + */ +export interface LogConfig { + /** + * Minimum log level to output. + * @default LogLevel.Info + */ + level?: LogLevel; + /** + * Logger implementation to use. + * @default new ConsoleLogger() + */ + logger?: ILogger; + /** + * Whether logging should be silenced. + * @default true + */ + silent?: boolean; +} + +/** + * Default console-based logger implementation. + */ +export class ConsoleLogger implements ILogger { + debug(message: string, ...args: unknown[]): void { + console.debug(message, ...args); + } + info(message: string, ...args: unknown[]): void { + console.info(message, ...args); + } + warn(message: string, ...args: unknown[]): void { + console.warn(message, ...args); + } + error(message: string, ...args: unknown[]): void { + console.error(message, ...args); + } +} + +/** + * Logger class that provides level-based logging functionality. + */ +export class Logger { + private readonly level: number; + private readonly logger: ILogger; + private readonly silent: boolean; + + /** + * Creates a new logger instance. + * @param config - Logger configuration + */ + constructor(config: Required) { + this.level = logLevelMap[config.level]; + this.logger = config.logger; + this.silent = config.silent; + } + + /** + * Checks if a log level should be output based on configuration. + * @param level - The log level to check + * @returns True if the level should be logged + */ + public shouldLog(level: LogLevel): boolean { + return !this.silent && this.level <= logLevelMap[level]; + } + + /** + * Checks if debug logging is enabled. + * @returns True if debug logs should be output + */ + public isDebug(): boolean { + return this.shouldLog(LogLevel.Debug); + } + + /** + * Logs a debug message if debug logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public debug(message: string, ...args: unknown[]): void { + if (this.isDebug()) { + this.logger.debug(message, ...args); + } + } + + /** + * Checks if info logging is enabled. + * @returns True if info logs should be output + */ + public isInfo(): boolean { + return this.shouldLog(LogLevel.Info); + } + + /** + * Logs an info message if info logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public info(message: string, ...args: unknown[]): void { + if (this.isInfo()) { + this.logger.info(message, ...args); + } + } + + /** + * Checks if warning logging is enabled. + * @returns True if warning logs should be output + */ + public isWarn(): boolean { + return this.shouldLog(LogLevel.Warn); + } + + /** + * Logs a warning message if warning logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public warn(message: string, ...args: unknown[]): void { + if (this.isWarn()) { + this.logger.warn(message, ...args); + } + } + + /** + * Checks if error logging is enabled. + * @returns True if error logs should be output + */ + public isError(): boolean { + return this.shouldLog(LogLevel.Error); + } + + /** + * Logs an error message if error logging is enabled. + * @param message - The message to log + * @param args - Additional arguments to log + */ + public error(message: string, ...args: unknown[]): void { + if (this.isError()) { + this.logger.error(message, ...args); + } + } +} + +export function createLogger(config?: LogConfig | Logger): Logger { + if (config == null) { + return defaultLogger; + } + if (config instanceof Logger) { + return config; + } + config = config ?? {}; + config.level ??= LogLevel.Info; + config.logger ??= new ConsoleLogger(); + config.silent ??= true; + return new Logger(config as Required); +} + +const defaultLogger: Logger = new Logger({ + level: LogLevel.Info, + logger: new ConsoleLogger(), + silent: true, +}); diff --git a/seed/ts-sdk/allof/src/core/runtime/index.ts b/seed/ts-sdk/allof/src/core/runtime/index.ts new file mode 100644 index 000000000000..cfab23f9a834 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/runtime/index.ts @@ -0,0 +1 @@ +export { RUNTIME } from "./runtime.js"; diff --git a/seed/ts-sdk/allof/src/core/runtime/runtime.ts b/seed/ts-sdk/allof/src/core/runtime/runtime.ts new file mode 100644 index 000000000000..e6e66b2a7bce --- /dev/null +++ b/seed/ts-sdk/allof/src/core/runtime/runtime.ts @@ -0,0 +1,134 @@ +interface DenoGlobal { + version: { + deno: string; + }; +} + +interface BunGlobal { + version: string; +} + +declare const Deno: DenoGlobal | undefined; +declare const Bun: BunGlobal | undefined; +declare const EdgeRuntime: string | undefined; +declare const self: typeof globalThis.self & { + importScripts?: unknown; +}; + +/** + * A constant that indicates which environment and version the SDK is running in. + */ +export const RUNTIME: Runtime = evaluateRuntime(); + +export interface Runtime { + type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd" | "edge-runtime"; + version?: string; + parsedVersion?: number; +} + +function evaluateRuntime(): Runtime { + /** + * A constant that indicates whether the environment the code is running is a Web Browser. + */ + const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; + if (isBrowser) { + return { + type: "browser", + version: window.navigator.userAgent, + }; + } + + /** + * A constant that indicates whether the environment the code is running is Cloudflare. + * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent + */ + const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; + if (isCloudflare) { + return { + type: "workerd", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Edge Runtime. + * https://vercel.com/docs/functions/runtimes/edge-runtime#check-if-you're-running-on-the-edge-runtime + */ + const isEdgeRuntime = typeof EdgeRuntime === "string"; + if (isEdgeRuntime) { + return { + type: "edge-runtime", + }; + } + + /** + * A constant that indicates whether the environment the code is running is a Web Worker. + */ + const isWebWorker = + typeof self === "object" && + typeof self?.importScripts === "function" && + (self.constructor?.name === "DedicatedWorkerGlobalScope" || + self.constructor?.name === "ServiceWorkerGlobalScope" || + self.constructor?.name === "SharedWorkerGlobalScope"); + if (isWebWorker) { + return { + type: "web-worker", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Deno. + * FYI Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions + */ + const isDeno = + typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; + if (isDeno) { + return { + type: "deno", + version: Deno.version.deno, + }; + } + + /** + * A constant that indicates whether the environment the code is running is Bun.sh. + */ + const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; + if (isBun) { + return { + type: "bun", + version: Bun.version, + }; + } + + /** + * A constant that indicates whether the environment the code is running is in React-Native. + * This check should come before Node.js detection since React Native may have a process polyfill. + * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js + */ + const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; + if (isReactNative) { + return { + type: "react-native", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Node.JS. + * + * We assign `process` to a local variable first to avoid being flagged by + * bundlers that perform static analysis on `process.versions` (e.g. Next.js + * Edge Runtime warns about Node.js APIs even when they are guarded). + */ + const _process = typeof process !== "undefined" ? process : undefined; + const isNode = typeof _process !== "undefined" && typeof _process.versions?.node === "string"; + if (isNode) { + return { + type: "node", + version: _process.versions.node, + parsedVersion: Number(_process.versions.node.split(".")[0]), + }; + } + + return { + type: "unknown", + }; +} diff --git a/seed/ts-sdk/allof/src/core/url/encodePathParam.ts b/seed/ts-sdk/allof/src/core/url/encodePathParam.ts new file mode 100644 index 000000000000..19b901244218 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/url/encodePathParam.ts @@ -0,0 +1,18 @@ +export function encodePathParam(param: unknown): string { + if (param === null) { + return "null"; + } + const typeofParam = typeof param; + switch (typeofParam) { + case "undefined": + return "undefined"; + case "string": + case "number": + case "boolean": + break; + default: + param = String(param); + break; + } + return encodeURIComponent(param as string | number | boolean); +} diff --git a/seed/ts-sdk/allof/src/core/url/index.ts b/seed/ts-sdk/allof/src/core/url/index.ts new file mode 100644 index 000000000000..f2e0fa2d2221 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/url/index.ts @@ -0,0 +1,3 @@ +export { encodePathParam } from "./encodePathParam.js"; +export { join } from "./join.js"; +export { toQueryString } from "./qs.js"; diff --git a/seed/ts-sdk/allof/src/core/url/join.ts b/seed/ts-sdk/allof/src/core/url/join.ts new file mode 100644 index 000000000000..7ca7daef094d --- /dev/null +++ b/seed/ts-sdk/allof/src/core/url/join.ts @@ -0,0 +1,79 @@ +export function join(base: string, ...segments: string[]): string { + if (!base) { + return ""; + } + + if (segments.length === 0) { + return base; + } + + if (base.includes("://")) { + let url: URL; + try { + url = new URL(base); + } catch { + return joinPath(base, ...segments); + } + + const lastSegment = segments[segments.length - 1]; + const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); + + for (const segment of segments) { + const cleanSegment = trimSlashes(segment); + if (cleanSegment) { + url.pathname = joinPathSegments(url.pathname, cleanSegment); + } + } + + if (shouldPreserveTrailingSlash && !url.pathname.endsWith("/")) { + url.pathname += "/"; + } + + return url.toString(); + } + + return joinPath(base, ...segments); +} + +function joinPath(base: string, ...segments: string[]): string { + if (segments.length === 0) { + return base; + } + + let result = base; + + const lastSegment = segments[segments.length - 1]; + const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); + + for (const segment of segments) { + const cleanSegment = trimSlashes(segment); + if (cleanSegment) { + result = joinPathSegments(result, cleanSegment); + } + } + + if (shouldPreserveTrailingSlash && !result.endsWith("/")) { + result += "/"; + } + + return result; +} + +function joinPathSegments(left: string, right: string): string { + if (left.endsWith("/")) { + return left + right; + } + return `${left}/${right}`; +} + +function trimSlashes(str: string): string { + if (!str) return str; + + let start = 0; + let end = str.length; + + if (str.startsWith("/")) start = 1; + if (str.endsWith("/")) end = str.length - 1; + + return start === 0 && end === str.length ? str : str.slice(start, end); +} diff --git a/seed/ts-sdk/allof/src/core/url/qs.ts b/seed/ts-sdk/allof/src/core/url/qs.ts new file mode 100644 index 000000000000..13e89be9d9a6 --- /dev/null +++ b/seed/ts-sdk/allof/src/core/url/qs.ts @@ -0,0 +1,74 @@ +interface QueryStringOptions { + arrayFormat?: "indices" | "repeat"; + encode?: boolean; +} + +const defaultQsOptions: Required = { + arrayFormat: "indices", + encode: true, +} as const; + +function encodeValue(value: unknown, shouldEncode: boolean): string { + if (value === undefined) { + return ""; + } + if (value === null) { + return ""; + } + const stringValue = String(value); + return shouldEncode ? encodeURIComponent(stringValue) : stringValue; +} + +function stringifyObject(obj: Record, prefix = "", options: Required): string[] { + const parts: string[] = []; + + for (const [key, value] of Object.entries(obj)) { + const fullKey = prefix ? `${prefix}[${key}]` : key; + + if (value === undefined) { + continue; + } + + if (Array.isArray(value)) { + if (value.length === 0) { + continue; + } + for (let i = 0; i < value.length; i++) { + const item = value[i]; + if (item === undefined) { + continue; + } + if (typeof item === "object" && !Array.isArray(item) && item !== null) { + const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; + parts.push(...stringifyObject(item as Record, arrayKey, options)); + } else { + const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; + const encodedKey = options.encode ? encodeURIComponent(arrayKey) : arrayKey; + parts.push(`${encodedKey}=${encodeValue(item, options.encode)}`); + } + } + } else if (typeof value === "object" && value !== null) { + if (Object.keys(value as Record).length === 0) { + continue; + } + parts.push(...stringifyObject(value as Record, fullKey, options)); + } else { + const encodedKey = options.encode ? encodeURIComponent(fullKey) : fullKey; + parts.push(`${encodedKey}=${encodeValue(value, options.encode)}`); + } + } + + return parts; +} + +export function toQueryString(obj: unknown, options?: QueryStringOptions): string { + if (obj == null || typeof obj !== "object") { + return ""; + } + + const parts = stringifyObject(obj as Record, "", { + ...defaultQsOptions, + ...options, + }); + return parts.join("&"); +} diff --git a/seed/ts-sdk/allof/src/environments.ts b/seed/ts-sdk/allof/src/environments.ts new file mode 100644 index 000000000000..92d0fc94dd2c --- /dev/null +++ b/seed/ts-sdk/allof/src/environments.ts @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +export const SeedApiEnvironment = { + Default: "https://api.example.com", +} as const; + +export type SeedApiEnvironment = typeof SeedApiEnvironment.Default; diff --git a/seed/ts-sdk/allof/src/errors/SeedApiError.ts b/seed/ts-sdk/allof/src/errors/SeedApiError.ts new file mode 100644 index 000000000000..ec2bc570e2a7 --- /dev/null +++ b/seed/ts-sdk/allof/src/errors/SeedApiError.ts @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as core from "../core/index.js"; +import { toJson } from "../core/json.js"; + +export class SeedApiError extends Error { + public readonly statusCode?: number; + public readonly body?: unknown; + public readonly rawResponse?: core.RawResponse; + public readonly cause?: unknown; + + constructor({ + message, + statusCode, + body, + rawResponse, + cause, + }: { + message?: string; + statusCode?: number; + body?: unknown; + rawResponse?: core.RawResponse; + cause?: unknown; + }) { + super(buildMessage({ message, statusCode, body })); + Object.setPrototypeOf(this, new.target.prototype); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + + this.name = this.constructor.name; + this.statusCode = statusCode; + this.body = body; + this.rawResponse = rawResponse; + if (cause != null) { + this.cause = cause; + } + } +} + +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { + const lines: string[] = []; + if (message != null) { + lines.push(message); + } + + if (statusCode != null) { + lines.push(`Status code: ${statusCode.toString()}`); + } + + if (body != null) { + lines.push(`Body: ${toJson(body, undefined, 2)}`); + } + + return lines.join("\n"); +} diff --git a/seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts b/seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts new file mode 100644 index 000000000000..f8f6a5f95430 --- /dev/null +++ b/seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts @@ -0,0 +1,18 @@ +// This file was auto-generated by Fern from our API Definition. + +export class SeedApiTimeoutError extends Error { + public readonly cause?: unknown; + + constructor(message: string, opts?: { cause?: unknown }) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + + this.name = this.constructor.name; + if (opts?.cause != null) { + this.cause = opts.cause; + } + } +} diff --git a/seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts new file mode 100644 index 000000000000..27d1ebec132d --- /dev/null +++ b/seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts @@ -0,0 +1,40 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as core from "../core/index.js"; +import * as errors from "./index.js"; + +export function handleNonStatusCodeError( + error: core.Fetcher.Error, + rawResponse: core.RawResponse, + method: string, + path: string, +): never { + switch (error.reason) { + case "non-json": + throw new errors.SeedApiError({ + statusCode: error.statusCode, + body: error.rawBody, + rawResponse: rawResponse, + }); + case "body-is-null": + throw new errors.SeedApiError({ + statusCode: error.statusCode, + rawResponse: rawResponse, + }); + case "timeout": + throw new errors.SeedApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`, { + cause: error.cause, + }); + case "unknown": + throw new errors.SeedApiError({ + message: error.errorMessage, + rawResponse: rawResponse, + cause: error.cause, + }); + default: + throw new errors.SeedApiError({ + message: "Unknown error", + rawResponse: rawResponse, + }); + } +} diff --git a/seed/ts-sdk/allof/src/errors/index.ts b/seed/ts-sdk/allof/src/errors/index.ts new file mode 100644 index 000000000000..09e82b954c26 --- /dev/null +++ b/seed/ts-sdk/allof/src/errors/index.ts @@ -0,0 +1,2 @@ +export { SeedApiError } from "./SeedApiError.js"; +export { SeedApiTimeoutError } from "./SeedApiTimeoutError.js"; diff --git a/seed/ts-sdk/allof/src/exports.ts b/seed/ts-sdk/allof/src/exports.ts new file mode 100644 index 000000000000..7b70ee14fc02 --- /dev/null +++ b/seed/ts-sdk/allof/src/exports.ts @@ -0,0 +1 @@ +export * from "./core/exports.js"; diff --git a/seed/ts-sdk/allof/src/index.ts b/seed/ts-sdk/allof/src/index.ts new file mode 100644 index 000000000000..a11386c163bd --- /dev/null +++ b/seed/ts-sdk/allof/src/index.ts @@ -0,0 +1,6 @@ +export * as SeedApi from "./api/index.js"; +export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; +export { SeedApiClient } from "./Client.js"; +export { SeedApiEnvironment } from "./environments.js"; +export { SeedApiError, SeedApiTimeoutError } from "./errors/index.js"; +export * from "./exports.js"; diff --git a/seed/ts-sdk/allof/src/version.ts b/seed/ts-sdk/allof/src/version.ts new file mode 100644 index 000000000000..b643a3e3ea27 --- /dev/null +++ b/seed/ts-sdk/allof/src/version.ts @@ -0,0 +1 @@ +export const SDK_VERSION = "0.0.1"; diff --git a/seed/ts-sdk/allof/tests/custom.test.ts b/seed/ts-sdk/allof/tests/custom.test.ts new file mode 100644 index 000000000000..7f5e031c8396 --- /dev/null +++ b/seed/ts-sdk/allof/tests/custom.test.ts @@ -0,0 +1,13 @@ +/** + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ +describe("test", () => { + it("default", () => { + expect(true).toBe(true); + }); +}); diff --git a/seed/ts-sdk/allof/tests/mock-server/MockServer.ts b/seed/ts-sdk/allof/tests/mock-server/MockServer.ts new file mode 100644 index 000000000000..954872157d52 --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/MockServer.ts @@ -0,0 +1,29 @@ +import type { RequestHandlerOptions } from "msw"; +import type { SetupServer } from "msw/node"; + +import { mockEndpointBuilder } from "./mockEndpointBuilder"; + +export interface MockServerOptions { + baseUrl: string; + server: SetupServer; +} + +export class MockServer { + private readonly server: SetupServer; + public readonly baseUrl: string; + + constructor({ baseUrl, server }: MockServerOptions) { + this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl; + this.server = server; + } + + public mockEndpoint(options?: RequestHandlerOptions): ReturnType { + const builder = mockEndpointBuilder({ + once: options?.once ?? true, + onBuild: (handler) => { + this.server.use(handler); + }, + }).baseUrl(this.baseUrl); + return builder; + } +} diff --git a/seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts b/seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts new file mode 100644 index 000000000000..d7d891a2d80b --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts @@ -0,0 +1,106 @@ +import { setupServer } from "msw/node"; + +import { fromJson, toJson } from "../../src/core/json"; +import { MockServer } from "./MockServer"; +import { randomBaseUrl } from "./randomBaseUrl"; + +const mswServer = setupServer(); +interface MockServerOptions { + baseUrl?: string; +} + +async function formatHttpRequest(request: Request, id?: string): Promise { + try { + const clone = request.clone(); + const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); + + let body = ""; + try { + const contentType = clone.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = toJson(fromJson(await clone.text()), undefined, 2); + } else if (clone.body) { + body = await clone.text(); + } + } catch (_e) { + body = "(unable to parse body)"; + } + + const title = id ? `### Request ${id} ###\n` : ""; + const firstLine = `${title}${request.method} ${request.url.toString()} HTTP/1.1`; + + return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; + } catch (e) { + return `Error formatting request: ${e}`; + } +} + +async function formatHttpResponse(response: Response, id?: string): Promise { + try { + const clone = response.clone(); + const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); + + let body = ""; + try { + const contentType = clone.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = toJson(fromJson(await clone.text()), undefined, 2); + } else if (clone.body) { + body = await clone.text(); + } + } catch (_e) { + body = "(unable to parse body)"; + } + + const title = id ? `### Response for ${id} ###\n` : ""; + const firstLine = `${title}HTTP/1.1 ${response.status} ${response.statusText}`; + + return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; + } catch (e) { + return `Error formatting response: ${e}`; + } +} + +class MockServerPool { + private servers: MockServer[] = []; + + public createServer(options?: Partial): MockServer { + const baseUrl = options?.baseUrl || randomBaseUrl(); + const server = new MockServer({ baseUrl, server: mswServer }); + this.servers.push(server); + return server; + } + + public getServers(): MockServer[] { + return [...this.servers]; + } + + public listen(): void { + const onUnhandledRequest = process.env.LOG_LEVEL === "debug" ? "warn" : "bypass"; + mswServer.listen({ onUnhandledRequest }); + + if (process.env.LOG_LEVEL === "debug") { + mswServer.events.on("request:start", async ({ request, requestId }) => { + const formattedRequest = await formatHttpRequest(request, requestId); + console.debug(`request:start\n${formattedRequest}`); + }); + + mswServer.events.on("request:unhandled", async ({ request, requestId }) => { + const formattedRequest = await formatHttpRequest(request, requestId); + console.debug(`request:unhandled\n${formattedRequest}`); + }); + + mswServer.events.on("response:mocked", async ({ request, response, requestId }) => { + const formattedResponse = await formatHttpResponse(response, requestId); + console.debug(`response:mocked\n${formattedResponse}`); + }); + } + } + + public close(): void { + this.servers = []; + mswServer.close(); + } +} + +export const mockServerPool: MockServerPool = new MockServerPool(); diff --git a/seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts b/seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts new file mode 100644 index 000000000000..3e8540a3ba5a --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts @@ -0,0 +1,234 @@ +import { type DefaultBodyType, type HttpHandler, HttpResponse, type HttpResponseResolver, http } from "msw"; + +import { url } from "../../src/core"; +import { toJson } from "../../src/core/json"; +import { type WithFormUrlEncodedOptions, withFormUrlEncoded } from "./withFormUrlEncoded"; +import { withHeaders } from "./withHeaders"; +import { type WithJsonOptions, withJson } from "./withJson"; + +type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; + +interface MethodStage { + baseUrl(baseUrl: string): MethodStage; + all(path: string): RequestHeadersStage; + get(path: string): RequestHeadersStage; + post(path: string): RequestHeadersStage; + put(path: string): RequestHeadersStage; + delete(path: string): RequestHeadersStage; + patch(path: string): RequestHeadersStage; + options(path: string): RequestHeadersStage; + head(path: string): RequestHeadersStage; +} + +interface RequestHeadersStage extends RequestBodyStage, ResponseStage { + header(name: string, value: string): RequestHeadersStage; + headers(headers: Record): RequestBodyStage; +} + +interface RequestBodyStage extends ResponseStage { + jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage; + formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage; +} + +interface ResponseStage { + respondWith(): ResponseStatusStage; +} +interface ResponseStatusStage { + statusCode(statusCode: number): ResponseHeaderStage; +} + +interface ResponseHeaderStage extends ResponseBodyStage, BuildStage { + header(name: string, value: string): ResponseHeaderStage; + headers(headers: Record): ResponseHeaderStage; +} + +interface ResponseBodyStage { + jsonBody(body: unknown): BuildStage; + sseBody(body: string): BuildStage; +} + +interface BuildStage { + build(): HttpHandler; +} + +export interface HttpHandlerBuilderOptions { + onBuild?: (handler: HttpHandler) => void; + once?: boolean; +} + +class RequestBuilder implements MethodStage, RequestHeadersStage, RequestBodyStage, ResponseStage { + private method: HttpMethod = "get"; + private _baseUrl: string = ""; + private path: string = "/"; + private readonly predicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[] = []; + private readonly handlerOptions?: HttpHandlerBuilderOptions; + + constructor(options?: HttpHandlerBuilderOptions) { + this.handlerOptions = options; + } + + baseUrl(baseUrl: string): MethodStage { + this._baseUrl = baseUrl; + return this; + } + + all(path: string): RequestHeadersStage { + this.method = "all"; + this.path = path; + return this; + } + + get(path: string): RequestHeadersStage { + this.method = "get"; + this.path = path; + return this; + } + + post(path: string): RequestHeadersStage { + this.method = "post"; + this.path = path; + return this; + } + + put(path: string): RequestHeadersStage { + this.method = "put"; + this.path = path; + return this; + } + + delete(path: string): RequestHeadersStage { + this.method = "delete"; + this.path = path; + return this; + } + + patch(path: string): RequestHeadersStage { + this.method = "patch"; + this.path = path; + return this; + } + + options(path: string): RequestHeadersStage { + this.method = "options"; + this.path = path; + return this; + } + + head(path: string): RequestHeadersStage { + this.method = "head"; + this.path = path; + return this; + } + + header(name: string, value: string): RequestHeadersStage { + this.predicates.push((resolver) => withHeaders({ [name]: value }, resolver)); + return this; + } + + headers(headers: Record): RequestBodyStage { + this.predicates.push((resolver) => withHeaders(headers, resolver)); + return this; + } + + jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage { + if (body === undefined) { + throw new Error("Undefined is not valid JSON. Do not call jsonBody if you want an empty body."); + } + this.predicates.push((resolver) => withJson(body, resolver, options)); + return this; + } + + formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage { + if (body === undefined) { + throw new Error( + "Undefined is not valid for form-urlencoded. Do not call formUrlEncodedBody if you want an empty body.", + ); + } + this.predicates.push((resolver) => withFormUrlEncoded(body, resolver, options)); + return this; + } + + respondWith(): ResponseStatusStage { + return new ResponseBuilder(this.method, this.buildUrl(), this.predicates, this.handlerOptions); + } + + private buildUrl(): string { + return url.join(this._baseUrl, this.path); + } +} + +class ResponseBuilder implements ResponseStatusStage, ResponseHeaderStage, ResponseBodyStage, BuildStage { + private readonly method: HttpMethod; + private readonly url: string; + private readonly requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[]; + private readonly handlerOptions?: HttpHandlerBuilderOptions; + + private responseStatusCode: number = 200; + private responseHeaders: Record = {}; + private responseBody: DefaultBodyType = undefined; + + constructor( + method: HttpMethod, + url: string, + requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[], + options?: HttpHandlerBuilderOptions, + ) { + this.method = method; + this.url = url; + this.requestPredicates = requestPredicates; + this.handlerOptions = options; + } + + public statusCode(code: number): ResponseHeaderStage { + this.responseStatusCode = code; + return this; + } + + public header(name: string, value: string): ResponseHeaderStage { + this.responseHeaders[name] = value; + return this; + } + + public headers(headers: Record): ResponseHeaderStage { + this.responseHeaders = { ...this.responseHeaders, ...headers }; + return this; + } + + public jsonBody(body: unknown): BuildStage { + if (body === undefined) { + throw new Error("Undefined is not valid JSON. Do not call jsonBody if you expect an empty body."); + } + this.responseBody = toJson(body); + return this; + } + + public sseBody(body: string): BuildStage { + this.responseHeaders["Content-Type"] = "text/event-stream"; + this.responseBody = body; + return this; + } + + public build(): HttpHandler { + const responseResolver: HttpResponseResolver = () => { + const response = new HttpResponse(this.responseBody, { + status: this.responseStatusCode, + headers: this.responseHeaders, + }); + // if no Content-Type header is set, delete the default text content type that is set + if (Object.keys(this.responseHeaders).some((key) => key.toLowerCase() === "content-type") === false) { + response.headers.delete("Content-Type"); + } + return response; + }; + + const finalResolver = this.requestPredicates.reduceRight((acc, predicate) => predicate(acc), responseResolver); + + const handler = http[this.method](this.url, finalResolver, this.handlerOptions); + this.handlerOptions?.onBuild?.(handler); + return handler; + } +} + +export function mockEndpointBuilder(options?: HttpHandlerBuilderOptions): MethodStage { + return new RequestBuilder(options); +} diff --git a/seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts b/seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts new file mode 100644 index 000000000000..031aa6408aca --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts @@ -0,0 +1,4 @@ +export function randomBaseUrl(): string { + const randomString = Math.random().toString(36).substring(2, 15); + return `http://${randomString}.localhost`; +} diff --git a/seed/ts-sdk/allof/tests/mock-server/setup.ts b/seed/ts-sdk/allof/tests/mock-server/setup.ts new file mode 100644 index 000000000000..aeb3a95af7dc --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/setup.ts @@ -0,0 +1,10 @@ +import { afterAll, beforeAll } from "vitest"; + +import { mockServerPool } from "./MockServerPool"; + +beforeAll(() => { + mockServerPool.listen(); +}); +afterAll(() => { + mockServerPool.close(); +}); diff --git a/seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts b/seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts new file mode 100644 index 000000000000..2b23448e3102 --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts @@ -0,0 +1,104 @@ +import { type HttpResponseResolver, passthrough } from "msw"; + +import { toJson } from "../../src/core/json"; + +export interface WithFormUrlEncodedOptions { + /** + * List of field names to ignore when comparing request bodies. + * This is useful for pagination cursor fields that change between requests. + */ + ignoredFields?: string[]; +} + +/** + * Creates a request matcher that validates if the request form-urlencoded body exactly matches the expected object + * @param expectedBody - The exact body object to match against + * @param resolver - Response resolver to execute if body matches + * @param options - Optional configuration including fields to ignore + */ +export function withFormUrlEncoded( + expectedBody: unknown, + resolver: HttpResponseResolver, + options?: WithFormUrlEncodedOptions, +): HttpResponseResolver { + const ignoredFields = options?.ignoredFields ?? []; + return async (args) => { + const { request } = args; + + let clonedRequest: Request; + let bodyText: string | undefined; + let actualBody: Record; + try { + clonedRequest = request.clone(); + bodyText = await clonedRequest.text(); + if (bodyText === "") { + // Empty body is valid if expected body is also empty + const isExpectedEmpty = + expectedBody != null && + typeof expectedBody === "object" && + Object.keys(expectedBody as Record).length === 0; + if (!isExpectedEmpty) { + console.error("Request body is empty, expected a form-urlencoded body."); + return passthrough(); + } + actualBody = {}; + } else { + const params = new URLSearchParams(bodyText); + actualBody = {}; + for (const [key, value] of params.entries()) { + actualBody[key] = value; + } + } + } catch (error) { + console.error(`Error processing form-urlencoded request body:\n\tError: ${error}\n\tBody: ${bodyText}`); + return passthrough(); + } + + const mismatches = findMismatches(actualBody, expectedBody); + const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); + if (filteredMismatches.length > 0) { + console.error("Form-urlencoded body mismatch:", toJson(mismatches, undefined, 2)); + return passthrough(); + } + + return resolver(args); + }; +} + +function findMismatches(actual: any, expected: any): Record { + const mismatches: Record = {}; + + if (typeof actual !== typeof expected) { + return { value: { actual, expected } }; + } + + if (typeof actual !== "object" || actual === null || expected === null) { + if (actual !== expected) { + return { value: { actual, expected } }; + } + return {}; + } + + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + + const allKeys = new Set([...actualKeys, ...expectedKeys]); + + for (const key of allKeys) { + if (!expectedKeys.includes(key)) { + if (actual[key] === undefined) { + continue; + } + mismatches[key] = { actual: actual[key], expected: undefined }; + } else if (!actualKeys.includes(key)) { + if (expected[key] === undefined) { + continue; + } + mismatches[key] = { actual: undefined, expected: expected[key] }; + } else if (actual[key] !== expected[key]) { + mismatches[key] = { actual: actual[key], expected: expected[key] }; + } + } + + return mismatches; +} diff --git a/seed/ts-sdk/allof/tests/mock-server/withHeaders.ts b/seed/ts-sdk/allof/tests/mock-server/withHeaders.ts new file mode 100644 index 000000000000..6599d2b4a92d --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/withHeaders.ts @@ -0,0 +1,70 @@ +import { type HttpResponseResolver, passthrough } from "msw"; + +/** + * Creates a request matcher that validates if request headers match specified criteria + * @param expectedHeaders - Headers to match against + * @param resolver - Response resolver to execute if headers match + */ +export function withHeaders( + expectedHeaders: Record boolean)>, + resolver: HttpResponseResolver, +): HttpResponseResolver { + return (args) => { + const { request } = args; + const { headers } = request; + + const mismatches: Record< + string, + { actual: string | null; expected: string | RegExp | ((value: string) => boolean) } + > = {}; + + for (const [key, expectedValue] of Object.entries(expectedHeaders)) { + const actualValue = headers.get(key); + + if (actualValue === null) { + mismatches[key] = { actual: null, expected: expectedValue }; + continue; + } + + if (typeof expectedValue === "function") { + if (!expectedValue(actualValue)) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } else if (expectedValue instanceof RegExp) { + if (!expectedValue.test(actualValue)) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } else if (expectedValue !== actualValue) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } + + if (Object.keys(mismatches).length > 0) { + const formattedMismatches = formatHeaderMismatches(mismatches); + console.error("Header mismatch:", formattedMismatches); + return passthrough(); + } + + return resolver(args); + }; +} + +function formatHeaderMismatches( + mismatches: Record boolean) }>, +): Record { + const formatted: Record = {}; + + for (const [key, { actual, expected }] of Object.entries(mismatches)) { + formatted[key] = { + actual, + expected: + expected instanceof RegExp + ? expected.toString() + : typeof expected === "function" + ? "[Function]" + : expected, + }; + } + + return formatted; +} diff --git a/seed/ts-sdk/allof/tests/mock-server/withJson.ts b/seed/ts-sdk/allof/tests/mock-server/withJson.ts new file mode 100644 index 000000000000..3e8800a0c374 --- /dev/null +++ b/seed/ts-sdk/allof/tests/mock-server/withJson.ts @@ -0,0 +1,173 @@ +import { type HttpResponseResolver, passthrough } from "msw"; + +import { fromJson, toJson } from "../../src/core/json"; + +export interface WithJsonOptions { + /** + * List of field names to ignore when comparing request bodies. + * This is useful for pagination cursor fields that change between requests. + */ + ignoredFields?: string[]; +} + +/** + * Creates a request matcher that validates if the request JSON body exactly matches the expected object + * @param expectedBody - The exact body object to match against + * @param resolver - Response resolver to execute if body matches + * @param options - Optional configuration including fields to ignore + */ +export function withJson( + expectedBody: unknown, + resolver: HttpResponseResolver, + options?: WithJsonOptions, +): HttpResponseResolver { + const ignoredFields = options?.ignoredFields ?? []; + return async (args) => { + const { request } = args; + + let clonedRequest: Request; + let bodyText: string | undefined; + let actualBody: unknown; + try { + clonedRequest = request.clone(); + bodyText = await clonedRequest.text(); + if (bodyText === "") { + console.error("Request body is empty, expected a JSON object."); + return passthrough(); + } + actualBody = fromJson(bodyText); + } catch (error) { + console.error(`Error processing request body:\n\tError: ${error}\n\tBody: ${bodyText}`); + return passthrough(); + } + + const mismatches = findMismatches(actualBody, expectedBody); + const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); + if (filteredMismatches.length > 0) { + console.error("JSON body mismatch:", toJson(mismatches, undefined, 2)); + return passthrough(); + } + + return resolver(args); + }; +} + +function findMismatches(actual: any, expected: any): Record { + const mismatches: Record = {}; + + if (typeof actual !== typeof expected) { + if (areEquivalent(actual, expected)) { + return {}; + } + return { value: { actual, expected } }; + } + + if (typeof actual !== "object" || actual === null || expected === null) { + if (actual !== expected) { + if (areEquivalent(actual, expected)) { + return {}; + } + return { value: { actual, expected } }; + } + return {}; + } + + if (Array.isArray(actual) && Array.isArray(expected)) { + if (actual.length !== expected.length) { + return { length: { actual: actual.length, expected: expected.length } }; + } + + const arrayMismatches: Record = {}; + for (let i = 0; i < actual.length; i++) { + const itemMismatches = findMismatches(actual[i], expected[i]); + if (Object.keys(itemMismatches).length > 0) { + for (const [mismatchKey, mismatchValue] of Object.entries(itemMismatches)) { + arrayMismatches[`[${i}]${mismatchKey === "value" ? "" : `.${mismatchKey}`}`] = mismatchValue; + } + } + } + return arrayMismatches; + } + + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + + const allKeys = new Set([...actualKeys, ...expectedKeys]); + + for (const key of allKeys) { + if (!expectedKeys.includes(key)) { + if (actual[key] === undefined) { + continue; // Skip undefined values in actual + } + mismatches[key] = { actual: actual[key], expected: undefined }; + } else if (!actualKeys.includes(key)) { + if (expected[key] === undefined) { + continue; // Skip undefined values in expected + } + mismatches[key] = { actual: undefined, expected: expected[key] }; + } else if ( + typeof actual[key] === "object" && + actual[key] !== null && + typeof expected[key] === "object" && + expected[key] !== null + ) { + const nestedMismatches = findMismatches(actual[key], expected[key]); + if (Object.keys(nestedMismatches).length > 0) { + for (const [nestedKey, nestedValue] of Object.entries(nestedMismatches)) { + mismatches[`${key}${nestedKey === "value" ? "" : `.${nestedKey}`}`] = nestedValue; + } + } + } else if (actual[key] !== expected[key]) { + if (areEquivalent(actual[key], expected[key])) { + continue; + } + mismatches[key] = { actual: actual[key], expected: expected[key] }; + } + } + + return mismatches; +} + +function areEquivalent(actual: unknown, expected: unknown): boolean { + if (actual === expected) { + return true; + } + if (isEquivalentBigInt(actual, expected)) { + return true; + } + if (isEquivalentDatetime(actual, expected)) { + return true; + } + return false; +} + +function isEquivalentBigInt(actual: unknown, expected: unknown) { + if (typeof actual === "number") { + actual = BigInt(actual); + } + if (typeof expected === "number") { + expected = BigInt(expected); + } + if (typeof actual === "bigint" && typeof expected === "bigint") { + return actual === expected; + } + return false; +} + +function isEquivalentDatetime(str1: unknown, str2: unknown): boolean { + if (typeof str1 !== "string" || typeof str2 !== "string") { + return false; + } + const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/; + if (!isoDatePattern.test(str1) || !isoDatePattern.test(str2)) { + return false; + } + + try { + const date1 = new Date(str1).getTime(); + const date2 = new Date(str2).getTime(); + return date1 === date2; + } catch { + return false; + } +} diff --git a/seed/ts-sdk/allof/tests/setup.ts b/seed/ts-sdk/allof/tests/setup.ts new file mode 100644 index 000000000000..a5651f81ba10 --- /dev/null +++ b/seed/ts-sdk/allof/tests/setup.ts @@ -0,0 +1,80 @@ +import { expect } from "vitest"; + +interface CustomMatchers { + toContainHeaders(expectedHeaders: Record): R; +} + +declare module "vitest" { + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} +} + +expect.extend({ + toContainHeaders(actual: unknown, expectedHeaders: Record) { + const isHeaders = actual instanceof Headers; + const isPlainObject = typeof actual === "object" && actual !== null && !Array.isArray(actual); + + if (!isHeaders && !isPlainObject) { + throw new TypeError("Received value must be an instance of Headers or a plain object!"); + } + + if (typeof expectedHeaders !== "object" || expectedHeaders === null || Array.isArray(expectedHeaders)) { + throw new TypeError("Expected headers must be a plain object!"); + } + + const missingHeaders: string[] = []; + const mismatchedHeaders: Array<{ key: string; expected: string; actual: string | null }> = []; + + for (const [key, value] of Object.entries(expectedHeaders)) { + let actualValue: string | null = null; + + if (isHeaders) { + // Headers.get() is already case-insensitive + actualValue = (actual as Headers).get(key); + } else { + // For plain objects, do case-insensitive lookup + const actualObj = actual as Record; + const lowerKey = key.toLowerCase(); + const foundKey = Object.keys(actualObj).find((k) => k.toLowerCase() === lowerKey); + actualValue = foundKey ? actualObj[foundKey] : null; + } + + if (actualValue === null || actualValue === undefined) { + missingHeaders.push(key); + } else if (actualValue !== value) { + mismatchedHeaders.push({ key, expected: value, actual: actualValue }); + } + } + + const pass = missingHeaders.length === 0 && mismatchedHeaders.length === 0; + + const actualType = isHeaders ? "Headers" : "object"; + + if (pass) { + return { + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, + pass: true, + }; + } else { + const messages: string[] = []; + + if (missingHeaders.length > 0) { + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); + } + + if (mismatchedHeaders.length > 0) { + const mismatches = mismatchedHeaders.map( + ({ key, expected, actual }) => + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, + ); + messages.push(mismatches.join("\n")); + } + + return { + message: () => + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, + pass: false, + }; + } + }, +}); diff --git a/seed/ts-sdk/allof/tests/tsconfig.json b/seed/ts-sdk/allof/tests/tsconfig.json new file mode 100644 index 000000000000..ac39744de7b2 --- /dev/null +++ b/seed/ts-sdk/allof/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": null, + "rootDir": "..", + "types": ["vitest/globals"] + }, + "include": ["../src", "../tests"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts new file mode 100644 index 000000000000..6c17624228bb --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts @@ -0,0 +1,262 @@ +import fs from "fs"; +import { join } from "path"; +import stream from "stream"; +import type { BinaryResponse } from "../../../src/core"; +import { type Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +describe("Test fetcherImpl", () => { + it("should handle successful request", async () => { + const mockArgs: Fetcher.Args = { + url: "https://httpbin.org/post", + method: "POST", + headers: { "X-Test": "x-test-header" }, + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + maxRetries: 0, + responseType: "json", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + }), + ); + + const result = await fetcherImpl(mockArgs); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + + expect(global.fetch).toHaveBeenCalledWith( + "https://httpbin.org/post", + expect.objectContaining({ + method: "POST", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + body: JSON.stringify({ data: "test" }), + }), + ); + }); + + it("should send octet stream", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "POST", + headers: { "X-Test": "x-test-header" }, + contentType: "application/octet-stream", + requestType: "bytes", + maxRetries: 0, + responseType: "json", + body: fs.createReadStream(join(__dirname, "test-file.txt")), + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + }), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "POST", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + body: expect.any(fs.ReadStream), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + }); + + it("should receive file as stream", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.stream).toBe("function"); + const stream = body.stream(); + expect(stream).toBeInstanceOf(ReadableStream); + const readableStream = stream as ReadableStream; + const reader = readableStream.getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(value); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as blob", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.blob).toBe("function"); + const blob = await body.blob(); + expect(blob).toBeInstanceOf(Blob); + const reader = blob.stream().getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(value); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as arraybuffer", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.arrayBuffer).toBe("function"); + const arrayBuffer = await body.arrayBuffer(); + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(new Uint8Array(arrayBuffer)); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as bytes", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + maxRetries: 0, + responseType: "binary-response", + }; + + global.fetch = vi.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.bytes).toBe("function"); + if (!body.bytes) { + return; + } + const bytes = await body.bytes(); + expect(bytes).toBeInstanceOf(Uint8Array); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(bytes); + expect(streamContent.trim()).toBe("This is a test file!"); + expect(body.bodyUsed).toBe(true); + } + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts new file mode 100644 index 000000000000..2ec008e581d8 --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts @@ -0,0 +1,143 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { HttpResponsePromise } from "../../../src/core/fetcher/HttpResponsePromise"; +import type { RawResponse, WithRawResponse } from "../../../src/core/fetcher/RawResponse"; + +describe("HttpResponsePromise", () => { + const mockRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 200, + statusText: "OK", + type: "basic" as ResponseType, + url: "https://example.com", + }; + const mockData = { id: "123", name: "test" }; + const mockWithRawResponse: WithRawResponse = { + data: mockData, + rawResponse: mockRawResponse, + }; + + describe("fromFunction", () => { + it("should create an HttpResponsePromise from a function", async () => { + const mockFn = vi + .fn<(arg1: string, arg2: string) => Promise>>() + .mockResolvedValue(mockWithRawResponse); + + const responsePromise = HttpResponsePromise.fromFunction(mockFn, "arg1", "arg2"); + + const result = await responsePromise; + expect(result).toEqual(mockData); + expect(mockFn).toHaveBeenCalledWith("arg1", "arg2"); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromPromise", () => { + it("should create an HttpResponsePromise from a promise", async () => { + const promise = Promise.resolve(mockWithRawResponse); + + const responsePromise = HttpResponsePromise.fromPromise(promise); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromExecutor", () => { + it("should create an HttpResponsePromise from an executor function", async () => { + const responsePromise = HttpResponsePromise.fromExecutor((resolve) => { + resolve(mockWithRawResponse); + }); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromResult", () => { + it("should create an HttpResponsePromise from a result", async () => { + const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("Promise methods", () => { + let responsePromise: HttpResponsePromise; + + beforeEach(() => { + responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + }); + + it("should support then() method", async () => { + const result = await responsePromise.then((data) => ({ + ...data, + modified: true, + })); + + expect(result).toEqual({ + ...mockData, + modified: true, + }); + }); + + it("should support catch() method", async () => { + const errorResponsePromise = HttpResponsePromise.fromExecutor((_, reject) => { + reject(new Error("Test error")); + }); + + const catchSpy = vi.fn(); + await errorResponsePromise.catch(catchSpy); + + expect(catchSpy).toHaveBeenCalled(); + const error = catchSpy.mock.calls[0]?.[0]; + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe("Test error"); + }); + + it("should support finally() method", async () => { + const finallySpy = vi.fn(); + await responsePromise.finally(finallySpy); + + expect(finallySpy).toHaveBeenCalled(); + }); + }); + + describe("withRawResponse", () => { + it("should return both data and raw response", async () => { + const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + + const result = await responsePromise.withRawResponse(); + + expect(result).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts new file mode 100644 index 000000000000..375ee3f38064 --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; + +import { toRawResponse } from "../../../src/core/fetcher/RawResponse"; + +describe("RawResponse", () => { + describe("toRawResponse", () => { + it("should convert Response to RawResponse by removing body, bodyUsed, and ok properties", () => { + const mockHeaders = new Headers({ "content-type": "application/json" }); + const mockResponse = { + body: "test body", + bodyUsed: false, + ok: true, + headers: mockHeaders, + redirected: false, + status: 200, + statusText: "OK", + type: "basic" as ResponseType, + url: "https://example.com", + }; + + const result = toRawResponse(mockResponse as unknown as Response); + + expect("body" in result).toBe(false); + expect("bodyUsed" in result).toBe(false); + expect("ok" in result).toBe(false); + expect(result.headers).toBe(mockHeaders); + expect(result.redirected).toBe(false); + expect(result.status).toBe(200); + expect(result.statusText).toBe("OK"); + expect(result.type).toBe("basic"); + expect(result.url).toBe("https://example.com"); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts new file mode 100644 index 000000000000..a92f1b5e81d1 --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts @@ -0,0 +1,163 @@ +import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; + +describe("Test createRequestUrl", () => { + const BASE_URL = "https://api.example.com"; + + interface TestCase { + description: string; + baseUrl: string; + queryParams?: Record; + expected: string; + } + + const testCases: TestCase[] = [ + { + description: "should return the base URL when no query parameters are provided", + baseUrl: BASE_URL, + expected: BASE_URL, + }, + { + description: "should append simple query parameters", + baseUrl: BASE_URL, + queryParams: { key: "value", another: "param" }, + expected: "https://api.example.com?key=value&another=param", + }, + { + description: "should handle array query parameters", + baseUrl: BASE_URL, + queryParams: { items: ["a", "b", "c"] }, + expected: "https://api.example.com?items=a&items=b&items=c", + }, + { + description: "should handle object query parameters", + baseUrl: BASE_URL, + queryParams: { filter: { name: "John", age: 30 } }, + expected: "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30", + }, + { + description: "should handle mixed types of query parameters", + baseUrl: BASE_URL, + queryParams: { + simple: "value", + array: ["x", "y"], + object: { key: "value" }, + }, + expected: "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value", + }, + { + description: "should handle empty query parameters object", + baseUrl: BASE_URL, + queryParams: {}, + expected: BASE_URL, + }, + { + description: "should encode special characters in query parameters", + baseUrl: BASE_URL, + queryParams: { special: "a&b=c d" }, + expected: "https://api.example.com?special=a%26b%3Dc%20d", + }, + { + description: "should handle numeric values", + baseUrl: BASE_URL, + queryParams: { count: 42, price: 19.99, active: 1, inactive: 0 }, + expected: "https://api.example.com?count=42&price=19.99&active=1&inactive=0", + }, + { + description: "should handle boolean values", + baseUrl: BASE_URL, + queryParams: { enabled: true, disabled: false }, + expected: "https://api.example.com?enabled=true&disabled=false", + }, + { + description: "should handle null and undefined values", + baseUrl: BASE_URL, + queryParams: { + valid: "value", + nullValue: null, + undefinedValue: undefined, + emptyString: "", + }, + expected: "https://api.example.com?valid=value&nullValue=&emptyString=", + }, + { + description: "should handle deeply nested objects", + baseUrl: BASE_URL, + queryParams: { + user: { + profile: { + name: "John", + settings: { theme: "dark" }, + }, + }, + }, + expected: + "https://api.example.com?user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", + }, + { + description: "should handle arrays of objects", + baseUrl: BASE_URL, + queryParams: { + users: [ + { name: "John", age: 30 }, + { name: "Jane", age: 25 }, + ], + }, + expected: + "https://api.example.com?users%5Bname%5D=John&users%5Bage%5D=30&users%5Bname%5D=Jane&users%5Bage%5D=25", + }, + { + description: "should handle mixed arrays", + baseUrl: BASE_URL, + queryParams: { + mixed: ["string", 42, true, { key: "value" }], + }, + expected: "https://api.example.com?mixed=string&mixed=42&mixed=true&mixed%5Bkey%5D=value", + }, + { + description: "should handle empty arrays", + baseUrl: BASE_URL, + queryParams: { emptyArray: [] }, + expected: BASE_URL, + }, + { + description: "should handle empty objects", + baseUrl: BASE_URL, + queryParams: { emptyObject: {} }, + expected: BASE_URL, + }, + { + description: "should handle special characters in keys", + baseUrl: BASE_URL, + queryParams: { "key with spaces": "value", "key[with]brackets": "value" }, + expected: "https://api.example.com?key%20with%20spaces=value&key%5Bwith%5Dbrackets=value", + }, + { + description: "should handle URL with existing query parameters", + baseUrl: "https://api.example.com?existing=param", + queryParams: { new: "value" }, + expected: "https://api.example.com?existing=param?new=value", + }, + { + description: "should handle complex nested structures", + baseUrl: BASE_URL, + queryParams: { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }, + expected: + "https://api.example.com?filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + }, + ]; + + testCases.forEach(({ description, baseUrl, queryParams, expected }) => { + it(description, () => { + expect(createRequestUrl(baseUrl, queryParams)).toBe(expected); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts new file mode 100644 index 000000000000..8a6c3a57e211 --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts @@ -0,0 +1,129 @@ +import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; +import { RUNTIME } from "../../../src/core/runtime"; + +describe("Test getRequestBody", () => { + interface TestCase { + description: string; + input: any; + type: "json" | "form" | "file" | "bytes" | "other"; + expected: any; + skipCondition?: () => boolean; + } + + const testCases: TestCase[] = [ + { + description: "should stringify body if not FormData in Node environment", + input: { key: "value" }, + type: "json", + expected: '{"key":"value"}', + skipCondition: () => RUNTIME.type !== "node", + }, + { + description: "should stringify body if not FormData in browser environment", + input: { key: "value" }, + type: "json", + expected: '{"key":"value"}', + skipCondition: () => RUNTIME.type !== "browser", + }, + { + description: "should return the Uint8Array", + input: new Uint8Array([1, 2, 3]), + type: "bytes", + expected: new Uint8Array([1, 2, 3]), + }, + { + description: "should serialize objects for form-urlencoded content type", + input: { username: "johndoe", email: "john@example.com" }, + type: "form", + expected: "username=johndoe&email=john%40example.com", + }, + { + description: "should serialize complex nested objects and arrays for form-urlencoded content type", + input: { + user: { + profile: { + name: "John Doe", + settings: { + theme: "dark", + notifications: true, + }, + }, + tags: ["admin", "user"], + contacts: [ + { type: "email", value: "john@example.com" }, + { type: "phone", value: "+1234567890" }, + ], + }, + filters: { + status: ["active", "pending"], + metadata: { + created: "2024-01-01", + categories: ["electronics", "books"], + }, + }, + preferences: ["notifications", "updates"], + }, + type: "form", + expected: + "user%5Bprofile%5D%5Bname%5D=John%20Doe&" + + "user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark&" + + "user%5Bprofile%5D%5Bsettings%5D%5Bnotifications%5D=true&" + + "user%5Btags%5D=admin&" + + "user%5Btags%5D=user&" + + "user%5Bcontacts%5D%5Btype%5D=email&" + + "user%5Bcontacts%5D%5Bvalue%5D=john%40example.com&" + + "user%5Bcontacts%5D%5Btype%5D=phone&" + + "user%5Bcontacts%5D%5Bvalue%5D=%2B1234567890&" + + "filters%5Bstatus%5D=active&" + + "filters%5Bstatus%5D=pending&" + + "filters%5Bmetadata%5D%5Bcreated%5D=2024-01-01&" + + "filters%5Bmetadata%5D%5Bcategories%5D=electronics&" + + "filters%5Bmetadata%5D%5Bcategories%5D=books&" + + "preferences=notifications&" + + "preferences=updates", + }, + { + description: "should return the input for pre-serialized form-urlencoded strings", + input: "key=value&another=param", + type: "other", + expected: "key=value&another=param", + }, + { + description: "should JSON stringify objects", + input: { key: "value" }, + type: "json", + expected: '{"key":"value"}', + }, + ]; + + testCases.forEach(({ description, input, type, expected, skipCondition }) => { + it(description, async () => { + if (skipCondition?.()) { + return; + } + + const result = await getRequestBody({ + body: input, + type, + }); + + if (input instanceof Uint8Array) { + expect(result).toBe(input); + } else { + expect(result).toBe(expected); + } + }); + }); + + it("should return FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const formData = new FormData(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts new file mode 100644 index 000000000000..ad6be7fc2c9b --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts @@ -0,0 +1,97 @@ +import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; + +import { RUNTIME } from "../../../src/core/runtime"; + +describe("Test getResponseBody", () => { + interface SimpleTestCase { + description: string; + responseData: string | Record; + responseType?: "blob" | "sse" | "streaming" | "text"; + expected: any; + skipCondition?: () => boolean; + } + + const simpleTestCases: SimpleTestCase[] = [ + { + description: "should handle text response type", + responseData: "test text", + responseType: "text", + expected: "test text", + }, + { + description: "should handle JSON response", + responseData: { key: "value" }, + expected: { key: "value" }, + }, + { + description: "should handle empty response", + responseData: "", + expected: undefined, + }, + { + description: "should handle non-JSON response", + responseData: "invalid json", + expected: { + ok: false, + error: { + reason: "non-json", + statusCode: 200, + rawBody: "invalid json", + }, + }, + }, + ]; + + simpleTestCases.forEach(({ description, responseData, responseType, expected, skipCondition }) => { + it(description, async () => { + if (skipCondition?.()) { + return; + } + + const mockResponse = new Response( + typeof responseData === "string" ? responseData : JSON.stringify(responseData), + ); + const result = await getResponseBody(mockResponse, responseType); + expect(result).toEqual(expected); + }); + }); + + it("should handle blob response type", async () => { + const mockBlob = new Blob(["test"], { type: "text/plain" }); + const mockResponse = new Response(mockBlob); + const result = await getResponseBody(mockResponse, "blob"); + // @ts-expect-error + expect(result.constructor.name).toBe("Blob"); + }); + + it("should handle sse response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "sse"); + expect(result).toBe(mockStream); + } + }); + + it("should handle streaming response type", async () => { + const encoder = new TextEncoder(); + const testData = "test stream data"; + const mockStream = new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(testData)); + controller.close(); + }, + }); + + const mockResponse = new Response(mockStream); + const result = (await getResponseBody(mockResponse, "streaming")) as ReadableStream; + + expect(result).toBeInstanceOf(ReadableStream); + + const reader = result.getReader(); + const decoder = new TextDecoder(); + const { value } = await reader.read(); + const streamContent = decoder.decode(value); + expect(streamContent).toBe(testData); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts new file mode 100644 index 000000000000..366c9b6ced61 --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts @@ -0,0 +1,517 @@ +import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +function createMockLogger() { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(data), { + status, + statusText, + }), + ); +} + +function mockErrorResponse(data: unknown = { error: "Error" }, status = 404, statusText = "Not Found") { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(data), { + status, + statusText, + }), + ); +} + +describe("Fetcher Logging Integration", () => { + describe("Request Logging", () => { + it("should log successful request at debug level", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + headers: { "Content-Type": "application/json" }, + body: { test: "data" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "POST", + url: "https://example.com/api", + headers: expect.toContainHeaders({ + "Content-Type": "application/json", + }), + hasBody: true, + }), + ); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + method: "POST", + url: "https://example.com/api", + statusCode: 200, + }), + ); + }); + + it("should not log debug messages at info level for successful requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "info", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + }); + + it("should log request with body flag", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + hasBody: true, + }), + ); + }); + + it("should log request without body flag", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + hasBody: false, + }), + ); + }); + + it("should not log when silent mode is enabled", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: true, + }, + }); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); + + it("should not log when no logging config is provided", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + }); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + }); + }); + + describe("Error Logging", () => { + it("should log 4xx errors at error level", async () => { + const mockLogger = createMockLogger(); + mockErrorResponse({ error: "Not found" }, 404, "Not Found"); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + statusCode: 404, + }), + ); + }); + + it("should log 5xx errors at error level", async () => { + const mockLogger = createMockLogger(); + mockErrorResponse({ error: "Internal error" }, 500, "Internal Server Error"); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + statusCode: 500, + }), + ); + }); + + it("should log aborted request errors", async () => { + const mockLogger = createMockLogger(); + + const abortController = new AbortController(); + abortController.abort(); + + global.fetch = vi.fn().mockRejectedValue(new Error("Aborted")); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + abortSignal: abortController.signal, + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request was aborted", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + }), + ); + }); + + it("should log timeout errors", async () => { + const mockLogger = createMockLogger(); + + const timeoutError = new Error("Request timeout"); + timeoutError.name = "AbortError"; + + global.fetch = vi.fn().mockRejectedValue(timeoutError); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request timed out", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + timeoutMs: undefined, + }), + ); + }); + + it("should log unknown errors", async () => { + const mockLogger = createMockLogger(); + + const unknownError = new Error("Unknown error"); + + global.fetch = vi.fn().mockRejectedValue(unknownError); + + const result = await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(result.ok).toBe(false); + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error", + expect.objectContaining({ + method: "GET", + url: "https://example.com/api", + errorMessage: "Unknown error", + }), + ); + }); + }); + + describe("Logging with Redaction", () => { + it("should redact sensitive data in error logs", async () => { + const mockLogger = createMockLogger(); + mockErrorResponse({ error: "Unauthorized" }, 401, "Unauthorized"); + + await fetcherImpl({ + url: "https://example.com/api?api_key=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + url: "https://example.com/api?api_key=[REDACTED]", + }), + ); + }); + }); + + describe("Different HTTP Methods", () => { + it("should log GET requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "GET", + }), + ); + }); + + it("should log POST requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse({ data: "test" }, 201, "Created"); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "POST", + }), + ); + }); + + it("should log PUT requests", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "PUT", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "PUT", + }), + ); + }); + + it("should log DELETE requests", async () => { + const mockLogger = createMockLogger(); + global.fetch = vi.fn().mockResolvedValue( + new Response(null, { + status: 200, + statusText: "OK", + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "DELETE", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + method: "DELETE", + }), + ); + }); + }); + + describe("Status Code Logging", () => { + it("should log 2xx success status codes", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse({ data: "test" }, 201, "Created"); + + await fetcherImpl({ + url: "https://example.com/api", + method: "POST", + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + statusCode: 201, + }), + ); + }); + + it("should log 3xx redirect status codes as success", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse({ data: "test" }, 301, "Moved Permanently"); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + statusCode: 301, + }), + ); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts new file mode 100644 index 000000000000..1850d1fda959 --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts @@ -0,0 +1,398 @@ +import type { Mock } from "vitest"; +import { makePassthroughRequest } from "../../../src/core/fetcher/makePassthroughRequest"; + +describe("makePassthroughRequest", () => { + let mockFetch: Mock; + + beforeEach(() => { + mockFetch = vi.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 200 })); + }); + + describe("URL resolution", () => { + it("should use absolute URL directly", async () => { + await makePassthroughRequest("https://api.example.com/v1/users", undefined, { + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/users"); + }); + + it("should resolve relative path against baseUrl", async () => { + await makePassthroughRequest("/v1/users", undefined, { + baseUrl: "https://api.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/users"); + }); + + it("should resolve relative path against environment when baseUrl is not set", async () => { + await makePassthroughRequest("/v1/users", undefined, { + environment: "https://env.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://env.example.com/v1/users"); + }); + + it("should prefer baseUrl over environment", async () => { + await makePassthroughRequest("/v1/users", undefined, { + baseUrl: "https://base.example.com", + environment: "https://env.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://base.example.com/v1/users"); + }); + + it("should pass relative URL through as-is when no baseUrl or environment", async () => { + await makePassthroughRequest("/v1/users", undefined, { + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("/v1/users"); + }); + + it("should resolve baseUrl supplier", async () => { + await makePassthroughRequest("/v1/users", undefined, { + baseUrl: () => "https://dynamic.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://dynamic.example.com/v1/users"); + }); + + it("should ignore absolute URL even when baseUrl is set", async () => { + await makePassthroughRequest("https://other.example.com/path", undefined, { + baseUrl: "https://base.example.com", + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://other.example.com/path"); + }); + + it("should accept a URL object", async () => { + await makePassthroughRequest(new URL("https://api.example.com/v1/users"), undefined, { + fetch: mockFetch, + }); + const [calledUrl] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/users"); + }); + }); + + describe("header merge order", () => { + it("should merge headers in correct priority: SDK defaults < auth < init < requestOptions", async () => { + await makePassthroughRequest( + "https://api.example.com", + { + headers: { "X-Custom": "from-init", Authorization: "from-init" }, + }, + { + headers: { + "X-Custom": "from-sdk", + "X-SDK-Only": "sdk-value", + Authorization: "from-sdk", + }, + getAuthHeaders: async () => ({ + Authorization: "Bearer auth-token", + "X-Auth-Only": "auth-value", + }), + fetch: mockFetch, + }, + { + headers: { Authorization: "from-request-options" }, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + const headers = calledOptions.headers; + + // requestOptions.headers wins for Authorization (highest priority) + expect(headers.authorization).toBe("from-request-options"); + // init.headers wins over SDK defaults for X-Custom + expect(headers["x-custom"]).toBe("from-init"); + // SDK-only header is preserved + expect(headers["x-sdk-only"]).toBe("sdk-value"); + // Auth-only header is preserved + expect(headers["x-auth-only"]).toBe("auth-value"); + }); + + it("should lowercase all header keys", async () => { + await makePassthroughRequest( + "https://api.example.com", + { + headers: { "Content-Type": "application/json" }, + }, + { + headers: { "X-Fern-Language": "JavaScript" }, + fetch: mockFetch, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + const headers = calledOptions.headers; + expect(headers["content-type"]).toBe("application/json"); + expect(headers["x-fern-language"]).toBe("JavaScript"); + expect(headers["Content-Type"]).toBeUndefined(); + expect(headers["X-Fern-Language"]).toBeUndefined(); + }); + + it("should handle Headers object in init", async () => { + const initHeaders = new Headers(); + initHeaders.set("X-From-Headers-Object", "value"); + await makePassthroughRequest("https://api.example.com", { headers: initHeaders }, { fetch: mockFetch }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-from-headers-object"]).toBe("value"); + }); + + it("should handle array-style headers in init", async () => { + await makePassthroughRequest( + "https://api.example.com", + { headers: [["X-Array-Header", "array-value"]] }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-array-header"]).toBe("array-value"); + }); + + it("should skip null SDK default header values", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + headers: { "X-Present": "value", "X-Null": null }, + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-present"]).toBe("value"); + expect(calledOptions.headers["x-null"]).toBeUndefined(); + }); + }); + + describe("auth headers", () => { + it("should include auth headers when getAuthHeaders is provided", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBe("Bearer my-token"); + }); + + it("should work without auth headers", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBeUndefined(); + }); + + it("should allow init headers to override auth headers", async () => { + await makePassthroughRequest( + "https://api.example.com", + { headers: { Authorization: "Bearer override" } }, + { + getAuthHeaders: async () => ({ Authorization: "Bearer sdk-auth" }), + fetch: mockFetch, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBe("Bearer override"); + }); + }); + + describe("method and body", () => { + it("should default to GET when no method specified", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.method).toBe("GET"); + }); + + it("should use the method from init", async () => { + await makePassthroughRequest( + "https://api.example.com", + { method: "POST", body: JSON.stringify({ key: "value" }) }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.method).toBe("POST"); + expect(calledOptions.body).toBe(JSON.stringify({ key: "value" })); + }); + + it("should pass body as undefined when not provided", async () => { + await makePassthroughRequest("https://api.example.com", { method: "GET" }, { fetch: mockFetch }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.body).toBeUndefined(); + }); + }); + + describe("timeout and retries", () => { + it("should use requestOptions timeout over client timeout", async () => { + await makePassthroughRequest( + "https://api.example.com", + undefined, + { timeoutInSeconds: 30, fetch: mockFetch }, + { timeoutInSeconds: 10 }, + ); + // The timeout is passed to makeRequest which converts to ms + // We verify via the signal timing behavior (indirectly tested through makeRequest) + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("should use client timeout when requestOptions timeout is not set", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + timeoutInSeconds: 30, + fetch: mockFetch, + }); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("should use requestOptions maxRetries over client maxRetries", async () => { + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + await makePassthroughRequest( + "https://api.example.com", + undefined, + { maxRetries: 5, fetch: mockFetch }, + { maxRetries: 1 }, + ); + // 1 initial + 1 retry = 2 calls + expect(mockFetch).toHaveBeenCalledTimes(2); + + vi.restoreAllMocks(); + }); + }); + + describe("abort signal", () => { + it("should use requestOptions.abortSignal over init.signal", async () => { + const initController = new AbortController(); + const requestController = new AbortController(); + + await makePassthroughRequest( + "https://api.example.com", + { signal: initController.signal }, + { fetch: mockFetch }, + { abortSignal: requestController.signal }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + // The signal passed to makeRequest is combined with timeout signal via anySignal, + // but the requestOptions.abortSignal should be the one that's used (not init.signal) + expect(calledOptions.signal).toBeDefined(); + }); + + it("should use init.signal when requestOptions.abortSignal is not set", async () => { + const initController = new AbortController(); + + await makePassthroughRequest( + "https://api.example.com", + { signal: initController.signal }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.signal).toBeDefined(); + }); + }); + + describe("credentials", () => { + it("should pass credentials include when set", async () => { + await makePassthroughRequest("https://api.example.com", { credentials: "include" }, { fetch: mockFetch }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.credentials).toBe("include"); + }); + + it("should not pass credentials when not set to include", async () => { + await makePassthroughRequest( + "https://api.example.com", + { credentials: "same-origin" }, + { + fetch: mockFetch, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.credentials).toBeUndefined(); + }); + }); + + describe("response", () => { + it("should return the Response object from fetch", async () => { + const mockResponse = new Response(JSON.stringify({ data: "test" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + mockFetch.mockResolvedValue(mockResponse); + + const response = await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + expect(response).toBe(mockResponse); + expect(response.status).toBe(200); + }); + + it("should return error responses without throwing", async () => { + const errorResponse = new Response("Not Found", { status: 404 }); + mockFetch.mockResolvedValue(errorResponse); + + const response = await makePassthroughRequest("https://api.example.com", undefined, { + fetch: mockFetch, + }); + expect(response.status).toBe(404); + }); + }); + + describe("Request object input", () => { + it("should extract URL from Request object", async () => { + const request = new Request("https://api.example.com/v1/resource", { method: "POST" }); + await makePassthroughRequest(request, undefined, { + fetch: mockFetch, + }); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe("https://api.example.com/v1/resource"); + expect(calledOptions.method).toBe("POST"); + }); + + it("should extract headers from Request object when no init provided", async () => { + const request = new Request("https://api.example.com", { + headers: { "X-From-Request": "request-value" }, + }); + await makePassthroughRequest(request, undefined, { + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-from-request"]).toBe("request-value"); + }); + + it("should use explicit init over Request object properties", async () => { + const request = new Request("https://api.example.com", { + method: "POST", + headers: { "X-From-Request": "request-value" }, + }); + await makePassthroughRequest( + request, + { method: "PUT", headers: { "X-From-Init": "init-value" } }, + { fetch: mockFetch }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.method).toBe("PUT"); + expect(calledOptions.headers["x-from-init"]).toBe("init-value"); + // Request headers should NOT be present since explicit init was provided + expect(calledOptions.headers["x-from-request"]).toBeUndefined(); + }); + }); + + describe("SDK default header suppliers", () => { + it("should resolve supplier functions for SDK default headers", async () => { + await makePassthroughRequest("https://api.example.com", undefined, { + headers: { + "X-Static": "static-value", + "X-Dynamic": () => "dynamic-value", + }, + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers["x-static"]).toBe("static-value"); + expect(calledOptions.headers["x-dynamic"]).toBe("dynamic-value"); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts new file mode 100644 index 000000000000..bde194554dd8 --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts @@ -0,0 +1,158 @@ +import type { Mock } from "vitest"; +import { + isCacheNoStoreSupported, + makeRequest, + resetCacheNoStoreSupported, +} from "../../../src/core/fetcher/makeRequest"; + +describe("Test makeRequest", () => { + const mockPostUrl = "https://httpbin.org/post"; + const mockGetUrl = "https://httpbin.org/get"; + const mockHeaders = { "Content-Type": "application/json" }; + const mockBody = JSON.stringify({ key: "value" }); + + let mockFetch: Mock; + + beforeEach(() => { + mockFetch = vi.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); + resetCacheNoStoreSupported(); + }); + + it("should handle POST request correctly", async () => { + const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockPostUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "POST", + headers: mockHeaders, + body: mockBody, + credentials: undefined, + }), + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should handle GET request correctly", async () => { + const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockGetUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "GET", + headers: mockHeaders, + body: undefined, + credentials: undefined, + }), + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should not include cache option when disableCache is not set", async () => { + await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBeUndefined(); + }); + + it("should not include cache option when disableCache is false", async () => { + await makeRequest( + mockFetch, + mockGetUrl, + "GET", + mockHeaders, + undefined, + undefined, + undefined, + undefined, + undefined, + false, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBeUndefined(); + }); + + it("should include cache: no-store when disableCache is true and runtime supports it", async () => { + // In Node.js test environment, Request supports the cache option + expect(isCacheNoStoreSupported()).toBe(true); + await makeRequest( + mockFetch, + mockGetUrl, + "GET", + mockHeaders, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBe("no-store"); + }); + + it("should cache the result of isCacheNoStoreSupported", () => { + const first = isCacheNoStoreSupported(); + const second = isCacheNoStoreSupported(); + expect(first).toBe(second); + }); + + it("should reset cache detection state with resetCacheNoStoreSupported", () => { + // First call caches the result + const first = isCacheNoStoreSupported(); + expect(first).toBe(true); + + // Reset clears the cache + resetCacheNoStoreSupported(); + + // After reset, it should re-detect (and still return true in Node.js) + const second = isCacheNoStoreSupported(); + expect(second).toBe(true); + }); + + it("should not include cache option when runtime does not support it (e.g. Cloudflare Workers)", async () => { + // Mock Request constructor to throw when cache option is passed, + // simulating runtimes like Cloudflare Workers + const OriginalRequest = globalThis.Request; + globalThis.Request = class MockRequest { + constructor(_url: string, init?: RequestInit) { + if (init?.cache != null) { + throw new TypeError("The 'cache' field on 'RequestInitializerDict' is not implemented."); + } + } + } as unknown as typeof Request; + + try { + // Reset so the detection runs fresh with the mocked Request + resetCacheNoStoreSupported(); + expect(isCacheNoStoreSupported()).toBe(false); + + await makeRequest( + mockFetch, + mockGetUrl, + "GET", + mockHeaders, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.cache).toBeUndefined(); + } finally { + // Restore original Request + globalThis.Request = OriginalRequest; + resetCacheNoStoreSupported(); + } + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts new file mode 100644 index 000000000000..d599376b9bcf --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts @@ -0,0 +1,1115 @@ +import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +function createMockLogger() { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(data), { + status, + statusText, + }), + ); +} + +describe("Redacting Logic", () => { + describe("Header Redaction", () => { + it("should redact authorization header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { Authorization: "Bearer secret-token-12345" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + Authorization: "[REDACTED]", + }), + }), + ); + }); + + it("should redact api-key header (case-insensitive)", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "X-API-KEY": "secret-api-key" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "X-API-KEY": "[REDACTED]", + }), + }), + ); + }); + + it("should redact cookie header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { Cookie: "session=abc123; token=xyz789" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + Cookie: "[REDACTED]", + }), + }), + ); + }); + + it("should redact x-auth-token header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "x-auth-token": "auth-token-12345" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "x-auth-token": "[REDACTED]", + }), + }), + ); + }); + + it("should redact proxy-authorization header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "Proxy-Authorization": "Basic credentials" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "Proxy-Authorization": "[REDACTED]", + }), + }), + ); + }); + + it("should redact x-csrf-token header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "X-CSRF-Token": "csrf-token-abc" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "X-CSRF-Token": "[REDACTED]", + }), + }), + ); + }); + + it("should redact www-authenticate header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "WWW-Authenticate": "Bearer realm=example" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "WWW-Authenticate": "[REDACTED]", + }), + }), + ); + }); + + it("should redact x-session-token header", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { "X-Session-Token": "session-token-xyz" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "X-Session-Token": "[REDACTED]", + }), + }), + ); + }); + + it("should not redact non-sensitive headers", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { + "Content-Type": "application/json", + "User-Agent": "Test/1.0", + Accept: "application/json", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + "Content-Type": "application/json", + "User-Agent": "Test/1.0", + Accept: "application/json", + }), + }), + ); + }); + + it("should redact multiple sensitive headers at once", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + headers: { + Authorization: "Bearer token", + "X-API-Key": "api-key", + Cookie: "session=123", + "Content-Type": "application/json", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + headers: expect.toContainHeaders({ + Authorization: "[REDACTED]", + "X-API-Key": "[REDACTED]", + Cookie: "[REDACTED]", + "Content-Type": "application/json", + }), + }), + ); + }); + }); + + describe("Response Header Redaction", () => { + it("should redact Set-Cookie in response headers", async () => { + const mockLogger = createMockLogger(); + + const mockHeaders = new Headers(); + mockHeaders.set("Set-Cookie", "session=abc123; HttpOnly; Secure"); + mockHeaders.set("Content-Type", "application/json"); + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + headers: mockHeaders, + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + responseHeaders: expect.toContainHeaders({ + "set-cookie": "[REDACTED]", + "content-type": "application/json", + }), + }), + ); + }); + + it("should redact authorization in response headers", async () => { + const mockLogger = createMockLogger(); + + const mockHeaders = new Headers(); + mockHeaders.set("Authorization", "Bearer token-123"); + mockHeaders.set("Content-Type", "application/json"); + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + headers: mockHeaders, + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "HTTP request succeeded", + expect.objectContaining({ + responseHeaders: expect.toContainHeaders({ + authorization: "[REDACTED]", + "content-type": "application/json", + }), + }), + ); + }); + + it("should redact response headers in error responses", async () => { + const mockLogger = createMockLogger(); + + const mockHeaders = new Headers(); + mockHeaders.set("WWW-Authenticate", "Bearer realm=example"); + mockHeaders.set("Content-Type", "application/json"); + + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + statusText: "Unauthorized", + headers: mockHeaders, + }), + ); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "error", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.error).toHaveBeenCalledWith( + "HTTP request failed with error status", + expect.objectContaining({ + responseHeaders: expect.toContainHeaders({ + "www-authenticate": "[REDACTED]", + "content-type": "application/json", + }), + }), + ); + }); + }); + + describe("Query Parameter Redaction", () => { + it("should redact api_key query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { api_key: "secret-key" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + api_key: "[REDACTED]", + }), + }), + ); + }); + + it("should redact token query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { token: "secret-token" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + token: "[REDACTED]", + }), + }), + ); + }); + + it("should redact access_token query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { access_token: "secret-access-token" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + access_token: "[REDACTED]", + }), + }), + ); + }); + + it("should redact password query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { password: "secret-password" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + password: "[REDACTED]", + }), + }), + ); + }); + + it("should redact secret query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { secret: "secret-value" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + secret: "[REDACTED]", + }), + }), + ); + }); + + it("should redact session_id query parameter", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { session_id: "session-123" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + session_id: "[REDACTED]", + }), + }), + ); + }); + + it("should not redact non-sensitive query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { + page: "1", + limit: "10", + sort: "name", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + page: "1", + limit: "10", + sort: "name", + }), + }), + ); + }); + + it("should not redact parameters containing 'auth' substring like 'author'", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { + author: "john", + authenticate: "false", + authorization_level: "user", + }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + author: "john", + authenticate: "false", + authorization_level: "user", + }), + }), + ); + }); + + it("should handle undefined query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: undefined, + }), + ); + }); + + it("should redact case-insensitive query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + queryParameters: { API_KEY: "secret-key", Token: "secret-token" }, + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + queryParameters: expect.objectContaining({ + API_KEY: "[REDACTED]", + Token: "[REDACTED]", + }), + }), + ); + }); + }); + + describe("URL Redaction", () => { + it("should redact credentials in URL", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user:password@example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@example.com/api", + }), + ); + }); + + it("should redact api_key in query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?api_key=secret-key&page=1", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?api_key=[REDACTED]&page=1", + }), + ); + }); + + it("should redact token in query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?token=secret-token", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?token=[REDACTED]", + }), + ); + }); + + it("should redact password in query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?username=user&password=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?username=user&password=[REDACTED]", + }), + ); + }); + + it("should not redact non-sensitive query strings", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?page=1&limit=10&sort=name", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?page=1&limit=10&sort=name", + }), + ); + }); + + it("should not redact URL parameters containing 'auth' substring like 'author'", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?author=john&authenticate=false&page=1", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?author=john&authenticate=false&page=1", + }), + ); + }); + + it("should handle URL with fragment", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?token=secret#section", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?token=[REDACTED]#section", + }), + ); + }); + + it("should redact URL-encoded query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?api%5Fkey=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?api%5Fkey=[REDACTED]", + }), + ); + }); + + it("should handle URL without query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api", + }), + ); + }); + + it("should handle empty query string", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?", + }), + ); + }); + + it("should redact multiple sensitive parameters in URL", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?api_key=secret1&token=secret2&page=1", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?api_key=[REDACTED]&token=[REDACTED]&page=1", + }), + ); + }); + + it("should redact both credentials and query parameters", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user:pass@example.com/api?token=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@example.com/api?token=[REDACTED]", + }), + ); + }); + + it("should use fast path for URLs without sensitive keywords", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", + }), + ); + }); + + it("should handle query parameter without value", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?flag&token=secret", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?flag&token=[REDACTED]", + }), + ); + }); + + it("should handle URL with multiple @ symbols in credentials", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user@example.com:pass@host.com/api", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@host.com/api", + }), + ); + }); + + it("should handle URL with @ in query parameter but not in credentials", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://example.com/api?email=user@example.com", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://example.com/api?email=user@example.com", + }), + ); + }); + + it("should handle URL with both credentials and @ in path", async () => { + const mockLogger = createMockLogger(); + mockSuccessResponse(); + + await fetcherImpl({ + url: "https://user:pass@example.com/users/@username", + method: "GET", + responseType: "json", + maxRetries: 0, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + expect(mockLogger.debug).toHaveBeenCalledWith( + "Making HTTP request", + expect.objectContaining({ + url: "https://[REDACTED]@example.com/users/@username", + }), + ); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts new file mode 100644 index 000000000000..d22661367f4e --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts @@ -0,0 +1,230 @@ +import type { Mock, MockInstance } from "vitest"; +import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; + +describe("requestWithRetries", () => { + let mockFetch: Mock; + let originalMathRandom: typeof Math.random; + let setTimeoutSpy: MockInstance; + + beforeEach(() => { + mockFetch = vi.fn(); + originalMathRandom = Math.random; + + Math.random = vi.fn(() => 0.5); + + vi.useFakeTimers({ + toFake: [ + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "Date", + "performance", + "requestAnimationFrame", + "cancelAnimationFrame", + "requestIdleCallback", + "cancelIdleCallback", + ], + }); + }); + + afterEach(() => { + Math.random = originalMathRandom; + vi.clearAllMocks(); + vi.clearAllTimers(); + }); + + it("should retry on retryable status codes", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const retryableStatuses = [408, 429, 500, 502]; + let callCount = 0; + + mockFetch.mockImplementation(async () => { + if (callCount < retryableStatuses.length) { + return new Response("", { status: retryableStatuses[callCount++] }); + } + return new Response("", { status: 200 }); + }); + + const responsePromise = requestWithRetries(() => mockFetch(), retryableStatuses.length); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(retryableStatuses.length + 1); + expect(response.status).toBe(200); + }); + + it("should respect maxRetries limit", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const maxRetries = 2; + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + expect(response.status).toBe(500); + }); + + it("should not retry on success status codes", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const successStatuses = [200, 201, 202]; + + for (const status of successStatuses) { + mockFetch.mockReset(); + setTimeoutSpy.mockClear(); + mockFetch.mockResolvedValueOnce(new Response("", { status })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + await vi.runAllTimersAsync(); + await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(setTimeoutSpy).not.toHaveBeenCalled(); + } + }); + + interface RetryHeaderTestCase { + description: string; + headerName: string; + headerValue: string | (() => string); + expectedDelayMin: number; + expectedDelayMax: number; + } + + const retryHeaderTests: RetryHeaderTestCase[] = [ + { + description: "should respect retry-after header with seconds value", + headerName: "retry-after", + headerValue: "5", + expectedDelayMin: 4000, + expectedDelayMax: 6000, + }, + { + description: "should respect retry-after header with HTTP date value", + headerName: "retry-after", + headerValue: () => new Date(Date.now() + 3000).toUTCString(), + expectedDelayMin: 2000, + expectedDelayMax: 4000, + }, + { + description: "should respect x-ratelimit-reset header", + headerName: "x-ratelimit-reset", + headerValue: () => Math.floor((Date.now() + 4000) / 1000).toString(), + expectedDelayMin: 3000, + expectedDelayMax: 6000, + }, + ]; + + retryHeaderTests.forEach(({ description, headerName, headerValue, expectedDelayMin, expectedDelayMax }) => { + it(description, async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const value = typeof headerValue === "function" ? headerValue() : headerValue; + mockFetch + .mockResolvedValueOnce( + new Response("", { + status: 429, + headers: new Headers({ [headerName]: value }), + }), + ) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 1); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), expect.any(Number)); + const actualDelay = setTimeoutSpy.mock.calls[0][1]; + expect(actualDelay).toBeGreaterThan(expectedDelayMin); + expect(actualDelay).toBeLessThan(expectedDelayMax); + expect(response.status).toBe(200); + }); + }); + + it("should apply correct exponential backoff with jitter", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + const maxRetries = 3; + const expectedDelays = [1000, 2000, 4000]; + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await vi.runAllTimersAsync(); + await responsePromise; + + expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length); + + expectedDelays.forEach((delay, index) => { + expect(setTimeoutSpy).toHaveBeenNthCalledWith(index + 1, expect.any(Function), delay); + }); + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + }); + + it("should handle concurrent retries independently", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const promise1 = requestWithRetries(() => mockFetch(), 1); + const promise2 = requestWithRetries(() => mockFetch(), 1); + + await vi.runAllTimersAsync(); + const [response1, response2] = await Promise.all([promise1, promise2]); + + expect(response1.status).toBe(200); + expect(response2.status).toBe(200); + }); + + it("should cap delay at MAX_RETRY_DELAY for large header values", async () => { + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch + .mockResolvedValueOnce( + new Response("", { + status: 429, + headers: new Headers({ "retry-after": "120" }), // 120 seconds = 120000ms > MAX_RETRY_DELAY (60000ms) + }), + ) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 1); + await vi.runAllTimersAsync(); + const response = await responsePromise; + + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 60000); + expect(response.status).toBe(200); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts new file mode 100644 index 000000000000..d7b6d1e63caa --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts @@ -0,0 +1,69 @@ +import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; + +describe("Test getTimeoutSignal", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should return an object with signal and abortId", () => { + const { signal, abortId } = getTimeoutSignal(1000); + + expect(signal).toBeDefined(); + expect(abortId).toBeDefined(); + expect(signal).toBeInstanceOf(AbortSignal); + expect(signal.aborted).toBe(false); + }); + + it("should create a signal that aborts after the specified timeout", () => { + const timeoutMs = 5000; + const { signal } = getTimeoutSignal(timeoutMs); + + expect(signal.aborted).toBe(false); + + vi.advanceTimersByTime(timeoutMs - 1); + expect(signal.aborted).toBe(false); + + vi.advanceTimersByTime(1); + expect(signal.aborted).toBe(true); + }); +}); + +describe("Test anySignal", () => { + it("should return an AbortSignal", () => { + const signal = anySignal(new AbortController().signal); + expect(signal).toBeInstanceOf(AbortSignal); + }); + + it("should abort when any of the input signals is aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal(controller1.signal, controller2.signal); + + expect(signal.aborted).toBe(false); + controller1.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should handle an array of signals", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal([controller1.signal, controller2.signal]); + + expect(signal.aborted).toBe(false); + controller2.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should abort immediately if one of the input signals is already aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + controller1.abort(); + + const signal = anySignal(controller1.signal, controller2.signal); + expect(signal.aborted).toBe(true); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt b/seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt new file mode 100644 index 000000000000..c66d471e359c --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt @@ -0,0 +1 @@ +This is a test file! diff --git a/seed/ts-sdk/allof/tests/unit/logging/logger.test.ts b/seed/ts-sdk/allof/tests/unit/logging/logger.test.ts new file mode 100644 index 000000000000..2e0b5fe5040c --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/logging/logger.test.ts @@ -0,0 +1,454 @@ +import { ConsoleLogger, createLogger, Logger, LogLevel } from "../../../src/core/logging/logger"; + +function createMockLogger() { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +describe("Logger", () => { + describe("LogLevel", () => { + it("should have correct log levels", () => { + expect(LogLevel.Debug).toBe("debug"); + expect(LogLevel.Info).toBe("info"); + expect(LogLevel.Warn).toBe("warn"); + expect(LogLevel.Error).toBe("error"); + }); + }); + + describe("ConsoleLogger", () => { + let consoleLogger: ConsoleLogger; + let consoleSpy: { + debug: ReturnType; + info: ReturnType; + warn: ReturnType; + error: ReturnType; + }; + + beforeEach(() => { + consoleLogger = new ConsoleLogger(); + consoleSpy = { + debug: vi.spyOn(console, "debug").mockImplementation(() => {}), + info: vi.spyOn(console, "info").mockImplementation(() => {}), + warn: vi.spyOn(console, "warn").mockImplementation(() => {}), + error: vi.spyOn(console, "error").mockImplementation(() => {}), + }; + }); + + afterEach(() => { + consoleSpy.debug.mockRestore(); + consoleSpy.info.mockRestore(); + consoleSpy.warn.mockRestore(); + consoleSpy.error.mockRestore(); + }); + + it("should log debug messages", () => { + consoleLogger.debug("debug message", { data: "test" }); + expect(consoleSpy.debug).toHaveBeenCalledWith("debug message", { data: "test" }); + }); + + it("should log info messages", () => { + consoleLogger.info("info message", { data: "test" }); + expect(consoleSpy.info).toHaveBeenCalledWith("info message", { data: "test" }); + }); + + it("should log warn messages", () => { + consoleLogger.warn("warn message", { data: "test" }); + expect(consoleSpy.warn).toHaveBeenCalledWith("warn message", { data: "test" }); + }); + + it("should log error messages", () => { + consoleLogger.error("error message", { data: "test" }); + expect(consoleSpy.error).toHaveBeenCalledWith("error message", { data: "test" }); + }); + + it("should handle multiple arguments", () => { + consoleLogger.debug("message", "arg1", "arg2", { key: "value" }); + expect(consoleSpy.debug).toHaveBeenCalledWith("message", "arg1", "arg2", { key: "value" }); + }); + }); + + describe("Logger with level filtering", () => { + let mockLogger: { + debug: ReturnType; + info: ReturnType; + warn: ReturnType; + error: ReturnType; + }; + + beforeEach(() => { + mockLogger = createMockLogger(); + }); + + describe("Debug level", () => { + it("should log all levels when set to debug", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).toHaveBeenCalledWith("debug"); + expect(mockLogger.info).toHaveBeenCalledWith("info"); + expect(mockLogger.warn).toHaveBeenCalledWith("warn"); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(true); + expect(logger.isInfo()).toBe(true); + expect(logger.isWarn()).toBe(true); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Info level", () => { + it("should log info, warn, and error when set to info", () => { + const logger = new Logger({ + level: LogLevel.Info, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).toHaveBeenCalledWith("info"); + expect(mockLogger.warn).toHaveBeenCalledWith("warn"); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Info, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(true); + expect(logger.isWarn()).toBe(true); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Warn level", () => { + it("should log warn and error when set to warn", () => { + const logger = new Logger({ + level: LogLevel.Warn, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalledWith("warn"); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Warn, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(false); + expect(logger.isWarn()).toBe(true); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Error level", () => { + it("should only log error when set to error", () => { + const logger = new Logger({ + level: LogLevel.Error, + logger: mockLogger, + silent: false, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalledWith("error"); + }); + + it("should report correct level checks", () => { + const logger = new Logger({ + level: LogLevel.Error, + logger: mockLogger, + silent: false, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(false); + expect(logger.isWarn()).toBe(false); + expect(logger.isError()).toBe(true); + }); + }); + + describe("Silent mode", () => { + it("should not log anything when silent is true", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: true, + }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); + + it("should report all level checks as false when silent", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: true, + }); + + expect(logger.isDebug()).toBe(false); + expect(logger.isInfo()).toBe(false); + expect(logger.isWarn()).toBe(false); + expect(logger.isError()).toBe(false); + }); + }); + + describe("shouldLog", () => { + it("should correctly determine if level should be logged", () => { + const logger = new Logger({ + level: LogLevel.Info, + logger: mockLogger, + silent: false, + }); + + expect(logger.shouldLog(LogLevel.Debug)).toBe(false); + expect(logger.shouldLog(LogLevel.Info)).toBe(true); + expect(logger.shouldLog(LogLevel.Warn)).toBe(true); + expect(logger.shouldLog(LogLevel.Error)).toBe(true); + }); + + it("should return false for all levels when silent", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: true, + }); + + expect(logger.shouldLog(LogLevel.Debug)).toBe(false); + expect(logger.shouldLog(LogLevel.Info)).toBe(false); + expect(logger.shouldLog(LogLevel.Warn)).toBe(false); + expect(logger.shouldLog(LogLevel.Error)).toBe(false); + }); + }); + + describe("Multiple arguments", () => { + it("should pass multiple arguments to logger", () => { + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("message", "arg1", { key: "value" }, 123); + expect(mockLogger.debug).toHaveBeenCalledWith("message", "arg1", { key: "value" }, 123); + }); + }); + }); + + describe("createLogger", () => { + it("should return default logger when no config provided", () => { + const logger = createLogger(); + expect(logger).toBeInstanceOf(Logger); + }); + + it("should return same logger instance when Logger is passed", () => { + const customLogger = new Logger({ + level: LogLevel.Debug, + logger: new ConsoleLogger(), + silent: false, + }); + + const result = createLogger(customLogger); + expect(result).toBe(customLogger); + }); + + it("should create logger with custom config", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + level: LogLevel.Warn, + logger: mockLogger, + silent: false, + }); + + expect(logger).toBeInstanceOf(Logger); + logger.warn("test"); + expect(mockLogger.warn).toHaveBeenCalledWith("test"); + }); + + it("should use default values for missing config", () => { + const logger = createLogger({}); + expect(logger).toBeInstanceOf(Logger); + }); + + it("should override default level", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("test"); + expect(mockLogger.debug).toHaveBeenCalledWith("test"); + }); + + it("should override default silent mode", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + logger: mockLogger, + silent: false, + }); + + logger.info("test"); + expect(mockLogger.info).toHaveBeenCalledWith("test"); + }); + + it("should use provided logger implementation", () => { + const customLogger = createMockLogger(); + + const logger = createLogger({ + logger: customLogger, + level: LogLevel.Debug, + silent: false, + }); + + logger.debug("test"); + expect(customLogger.debug).toHaveBeenCalledWith("test"); + }); + + it("should default to silent: true", () => { + const mockLogger = createMockLogger(); + + const logger = createLogger({ + logger: mockLogger, + level: LogLevel.Debug, + }); + + logger.debug("test"); + expect(mockLogger.debug).not.toHaveBeenCalled(); + }); + }); + + describe("Default logger", () => { + it("should have silent: true by default", () => { + const logger = createLogger(); + expect(logger.shouldLog(LogLevel.Info)).toBe(false); + }); + + it("should not log when using default logger", () => { + const logger = createLogger(); + + logger.info("test"); + expect(logger.isInfo()).toBe(false); + }); + }); + + describe("Edge cases", () => { + it("should handle empty message", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug(""); + expect(mockLogger.debug).toHaveBeenCalledWith(""); + }); + + it("should handle no arguments", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + logger.debug("message"); + expect(mockLogger.debug).toHaveBeenCalledWith("message"); + }); + + it("should handle complex objects", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Debug, + logger: mockLogger, + silent: false, + }); + + const complexObject = { + nested: { key: "value" }, + array: [1, 2, 3], + fn: () => "test", + }; + + logger.debug("message", complexObject); + expect(mockLogger.debug).toHaveBeenCalledWith("message", complexObject); + }); + + it("should handle errors as arguments", () => { + const mockLogger = createMockLogger(); + + const logger = new Logger({ + level: LogLevel.Error, + logger: mockLogger, + silent: false, + }); + + const error = new Error("Test error"); + logger.error("Error occurred", error); + expect(mockLogger.error).toHaveBeenCalledWith("Error occurred", error); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/url/join.test.ts b/seed/ts-sdk/allof/tests/unit/url/join.test.ts new file mode 100644 index 000000000000..123488f084ea --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/url/join.test.ts @@ -0,0 +1,284 @@ +import { join } from "../../../src/core/url/index"; + +describe("join", () => { + interface TestCase { + description: string; + base: string; + segments: string[]; + expected: string; + } + + describe("basic functionality", () => { + const basicTests: TestCase[] = [ + { description: "should return empty string for empty base", base: "", segments: [], expected: "" }, + { + description: "should return empty string for empty base with path", + base: "", + segments: ["path"], + expected: "", + }, + { + description: "should handle single segment", + base: "base", + segments: ["segment"], + expected: "base/segment", + }, + { + description: "should handle single segment with trailing slash on base", + base: "base/", + segments: ["segment"], + expected: "base/segment", + }, + { + description: "should handle single segment with leading slash", + base: "base", + segments: ["/segment"], + expected: "base/segment", + }, + { + description: "should handle single segment with both slashes", + base: "base/", + segments: ["/segment"], + expected: "base/segment", + }, + { + description: "should handle multiple segments", + base: "base", + segments: ["path1", "path2", "path3"], + expected: "base/path1/path2/path3", + }, + { + description: "should handle multiple segments with slashes", + base: "base/", + segments: ["/path1/", "/path2/", "/path3/"], + expected: "base/path1/path2/path3/", + }, + ]; + + basicTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("URL handling", () => { + const urlTests: TestCase[] = [ + { + description: "should handle absolute URLs", + base: "https://example.com", + segments: ["api", "v1"], + expected: "https://example.com/api/v1", + }, + { + description: "should handle absolute URLs with slashes", + base: "https://example.com/", + segments: ["/api/", "/v1/"], + expected: "https://example.com/api/v1/", + }, + { + description: "should handle absolute URLs with base path", + base: "https://example.com/base", + segments: ["api", "v1"], + expected: "https://example.com/base/api/v1", + }, + { + description: "should preserve URL query parameters", + base: "https://example.com?query=1", + segments: ["api"], + expected: "https://example.com/api?query=1", + }, + { + description: "should preserve URL fragments", + base: "https://example.com#fragment", + segments: ["api"], + expected: "https://example.com/api#fragment", + }, + { + description: "should preserve URL query and fragments", + base: "https://example.com?query=1#fragment", + segments: ["api"], + expected: "https://example.com/api?query=1#fragment", + }, + { + description: "should handle http protocol", + base: "http://example.com", + segments: ["api"], + expected: "http://example.com/api", + }, + { + description: "should handle ftp protocol", + base: "ftp://example.com", + segments: ["files"], + expected: "ftp://example.com/files", + }, + { + description: "should handle ws protocol", + base: "ws://example.com", + segments: ["socket"], + expected: "ws://example.com/socket", + }, + { + description: "should fallback to path joining for malformed URLs", + base: "not-a-url://", + segments: ["path"], + expected: "not-a-url:///path", + }, + ]; + + urlTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("edge cases", () => { + const edgeCaseTests: TestCase[] = [ + { + description: "should handle empty segments", + base: "base", + segments: ["", "path"], + expected: "base/path", + }, + { + description: "should handle null segments", + base: "base", + segments: [null as any, "path"], + expected: "base/path", + }, + { + description: "should handle undefined segments", + base: "base", + segments: [undefined as any, "path"], + expected: "base/path", + }, + { + description: "should handle segments with only single slash", + base: "base", + segments: ["/", "path"], + expected: "base/path", + }, + { + description: "should handle segments with only double slash", + base: "base", + segments: ["//", "path"], + expected: "base/path", + }, + { + description: "should handle base paths with trailing slashes", + base: "base/", + segments: ["path"], + expected: "base/path", + }, + { + description: "should handle complex nested paths", + base: "api/v1/", + segments: ["/users/", "/123/", "/profile"], + expected: "api/v1/users/123/profile", + }, + ]; + + edgeCaseTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("real-world scenarios", () => { + const realWorldTests: TestCase[] = [ + { + description: "should handle API endpoint construction", + base: "https://api.example.com/v1", + segments: ["users", "123", "posts"], + expected: "https://api.example.com/v1/users/123/posts", + }, + { + description: "should handle file path construction", + base: "/var/www", + segments: ["html", "assets", "images"], + expected: "/var/www/html/assets/images", + }, + { + description: "should handle relative path construction", + base: "../parent", + segments: ["child", "grandchild"], + expected: "../parent/child/grandchild", + }, + { + description: "should handle Windows-style paths", + base: "C:\\Users", + segments: ["Documents", "file.txt"], + expected: "C:\\Users/Documents/file.txt", + }, + ]; + + realWorldTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); + + describe("performance scenarios", () => { + it("should handle many segments efficiently", () => { + const segments = Array(100).fill("segment"); + const result = join("base", ...segments); + expect(result).toBe(`base/${segments.join("/")}`); + }); + + it("should handle long URLs", () => { + const longPath = "a".repeat(1000); + expect(join("https://example.com", longPath)).toBe(`https://example.com/${longPath}`); + }); + }); + + describe("trailing slash preservation", () => { + const trailingSlashTests: TestCase[] = [ + { + description: + "should preserve trailing slash on final result when base has trailing slash and no segments", + base: "https://api.example.com/", + segments: [], + expected: "https://api.example.com/", + }, + { + description: "should preserve trailing slash on v1 path", + base: "https://api.example.com/v1/", + segments: [], + expected: "https://api.example.com/v1/", + }, + { + description: "should preserve trailing slash when last segment has trailing slash", + base: "https://api.example.com", + segments: ["users/"], + expected: "https://api.example.com/users/", + }, + { + description: "should preserve trailing slash with relative path", + base: "api/v1", + segments: ["users/"], + expected: "api/v1/users/", + }, + { + description: "should preserve trailing slash with multiple segments", + base: "https://api.example.com", + segments: ["v1", "collections/"], + expected: "https://api.example.com/v1/collections/", + }, + { + description: "should preserve trailing slash with base path", + base: "base", + segments: ["path1", "path2/"], + expected: "base/path1/path2/", + }, + ]; + + trailingSlashTests.forEach(({ description, base, segments, expected }) => { + it(description, () => { + expect(join(base, ...segments)).toBe(expected); + }); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/unit/url/qs.test.ts b/seed/ts-sdk/allof/tests/unit/url/qs.test.ts new file mode 100644 index 000000000000..42cdffb9e5ea --- /dev/null +++ b/seed/ts-sdk/allof/tests/unit/url/qs.test.ts @@ -0,0 +1,278 @@ +import { toQueryString } from "../../../src/core/url/index"; + +describe("Test qs toQueryString", () => { + interface BasicTestCase { + description: string; + input: any; + expected: string; + } + + describe("Basic functionality", () => { + const basicTests: BasicTestCase[] = [ + { description: "should return empty string for null", input: null, expected: "" }, + { description: "should return empty string for undefined", input: undefined, expected: "" }, + { description: "should return empty string for string primitive", input: "hello", expected: "" }, + { description: "should return empty string for number primitive", input: 42, expected: "" }, + { description: "should return empty string for true boolean", input: true, expected: "" }, + { description: "should return empty string for false boolean", input: false, expected: "" }, + { description: "should handle empty objects", input: {}, expected: "" }, + { + description: "should handle simple key-value pairs", + input: { name: "John", age: 30 }, + expected: "name=John&age=30", + }, + ]; + + basicTests.forEach(({ description, input, expected }) => { + it(description, () => { + expect(toQueryString(input)).toBe(expected); + }); + }); + }); + + describe("Array handling", () => { + interface ArrayTestCase { + description: string; + input: any; + options?: { arrayFormat?: "repeat" | "indices" }; + expected: string; + } + + const arrayTests: ArrayTestCase[] = [ + { + description: "should handle arrays with indices format (default)", + input: { items: ["a", "b", "c"] }, + expected: "items%5B0%5D=a&items%5B1%5D=b&items%5B2%5D=c", + }, + { + description: "should handle arrays with repeat format", + input: { items: ["a", "b", "c"] }, + options: { arrayFormat: "repeat" }, + expected: "items=a&items=b&items=c", + }, + { + description: "should handle empty arrays", + input: { items: [] }, + expected: "", + }, + { + description: "should handle arrays with mixed types", + input: { mixed: ["string", 42, true, false] }, + expected: "mixed%5B0%5D=string&mixed%5B1%5D=42&mixed%5B2%5D=true&mixed%5B3%5D=false", + }, + { + description: "should handle arrays with objects", + input: { users: [{ name: "John" }, { name: "Jane" }] }, + expected: "users%5B0%5D%5Bname%5D=John&users%5B1%5D%5Bname%5D=Jane", + }, + { + description: "should handle arrays with objects in repeat format", + input: { users: [{ name: "John" }, { name: "Jane" }] }, + options: { arrayFormat: "repeat" }, + expected: "users%5Bname%5D=John&users%5Bname%5D=Jane", + }, + ]; + + arrayTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); + + describe("Nested objects", () => { + const nestedTests: BasicTestCase[] = [ + { + description: "should handle nested objects", + input: { user: { name: "John", age: 30 } }, + expected: "user%5Bname%5D=John&user%5Bage%5D=30", + }, + { + description: "should handle deeply nested objects", + input: { user: { profile: { name: "John", settings: { theme: "dark" } } } }, + expected: "user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", + }, + { + description: "should handle empty nested objects", + input: { user: {} }, + expected: "", + }, + ]; + + nestedTests.forEach(({ description, input, expected }) => { + it(description, () => { + expect(toQueryString(input)).toBe(expected); + }); + }); + }); + + describe("Encoding", () => { + interface EncodingTestCase { + description: string; + input: any; + options?: { encode?: boolean }; + expected: string; + } + + const encodingTests: EncodingTestCase[] = [ + { + description: "should encode by default", + input: { name: "John Doe", email: "john@example.com" }, + expected: "name=John%20Doe&email=john%40example.com", + }, + { + description: "should not encode when encode is false", + input: { name: "John Doe", email: "john@example.com" }, + options: { encode: false }, + expected: "name=John Doe&email=john@example.com", + }, + { + description: "should encode special characters in keys", + input: { "user name": "John", "email[primary]": "john@example.com" }, + expected: "user%20name=John&email%5Bprimary%5D=john%40example.com", + }, + { + description: "should not encode special characters in keys when encode is false", + input: { "user name": "John", "email[primary]": "john@example.com" }, + options: { encode: false }, + expected: "user name=John&email[primary]=john@example.com", + }, + ]; + + encodingTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); + + describe("Mixed scenarios", () => { + interface MixedTestCase { + description: string; + input: any; + options?: { arrayFormat?: "repeat" | "indices" }; + expected: string; + } + + const mixedTests: MixedTestCase[] = [ + { + description: "should handle complex nested structures", + input: { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }, + expected: + "filters%5Bstatus%5D%5B0%5D=active&filters%5Bstatus%5D%5B1%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D%5B0%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D%5B1%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + }, + { + description: "should handle complex nested structures with repeat format", + input: { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }, + options: { arrayFormat: "repeat" }, + expected: + "filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + }, + { + description: "should handle arrays with null/undefined values", + input: { items: ["a", null, "c", undefined, "e"] }, + expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e", + }, + { + description: "should handle objects with null/undefined values", + input: { name: "John", age: null, email: undefined, active: true }, + expected: "name=John&age=&active=true", + }, + ]; + + mixedTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); + + describe("Edge cases", () => { + const edgeCaseTests: BasicTestCase[] = [ + { + description: "should handle numeric keys", + input: { "0": "zero", "1": "one" }, + expected: "0=zero&1=one", + }, + { + description: "should handle boolean values in objects", + input: { enabled: true, disabled: false }, + expected: "enabled=true&disabled=false", + }, + { + description: "should handle empty strings", + input: { name: "", description: "test" }, + expected: "name=&description=test", + }, + { + description: "should handle zero values", + input: { count: 0, price: 0.0 }, + expected: "count=0&price=0", + }, + { + description: "should handle arrays with empty strings", + input: { items: ["a", "", "c"] }, + expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c", + }, + ]; + + edgeCaseTests.forEach(({ description, input, expected }) => { + it(description, () => { + expect(toQueryString(input)).toBe(expected); + }); + }); + }); + + describe("Options combinations", () => { + interface OptionsTestCase { + description: string; + input: any; + options?: { arrayFormat?: "repeat" | "indices"; encode?: boolean }; + expected: string; + } + + const optionsTests: OptionsTestCase[] = [ + { + description: "should respect both arrayFormat and encode options", + input: { items: ["a & b", "c & d"] }, + options: { arrayFormat: "repeat", encode: false }, + expected: "items=a & b&items=c & d", + }, + { + description: "should use default options when none provided", + input: { items: ["a", "b"] }, + expected: "items%5B0%5D=a&items%5B1%5D=b", + }, + { + description: "should merge provided options with defaults", + input: { items: ["a", "b"], name: "John Doe" }, + options: { encode: false }, + expected: "items[0]=a&items[1]=b&name=John Doe", + }, + ]; + + optionsTests.forEach(({ description, input, options, expected }) => { + it(description, () => { + expect(toQueryString(input, options)).toBe(expected); + }); + }); + }); +}); diff --git a/seed/ts-sdk/allof/tests/wire/.gitkeep b/seed/ts-sdk/allof/tests/wire/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/ts-sdk/allof/tests/wire/main.test.ts b/seed/ts-sdk/allof/tests/wire/main.test.ts new file mode 100644 index 000000000000..6af233ca61ca --- /dev/null +++ b/seed/ts-sdk/allof/tests/wire/main.test.ts @@ -0,0 +1,91 @@ +// This file was auto-generated by Fern from our API Definition. + +import { SeedApiClient } from "../../src/Client"; +import { mockServerPool } from "../mock-server/MockServerPool"; + +describe("SeedApiClient", () => { + test("searchRuleTypes", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { + paging: { next: "next", previous: "previous" }, + results: [{ id: "id", name: "name", description: "description" }], + }; + + server.mockEndpoint().get("/rule-types").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.searchRuleTypes(); + expect(response).toEqual(rawResponseBody); + }); + + test("createRule", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + const rawRequestBody = { name: "name", executionContext: "prod" }; + const rawResponseBody = { + createdBy: "createdBy", + createdDateTime: "2024-01-15T09:30:00Z", + modifiedBy: "modifiedBy", + modifiedDateTime: "2024-01-15T09:30:00Z", + id: "id", + name: "name", + status: "active", + executionContext: "prod", + }; + + server + .mockEndpoint() + .post("/rules") + .jsonBody(rawRequestBody) + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.createRule({ + name: "name", + executionContext: "prod", + }); + expect(response).toEqual(rawResponseBody); + }); + + test("listUsers", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { + paging: { next: "next", previous: "previous" }, + results: [{ id: "id", email: "email" }], + }; + + server.mockEndpoint().get("/users").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.listUsers(); + expect(response).toEqual(rawResponseBody); + }); + + test("getEntity", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { name: "name", summary: "summary", id: "id", status: "active" }; + + server.mockEndpoint().get("/entities").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.getEntity(); + expect(response).toEqual(rawResponseBody); + }); + + test("getOrganization", async () => { + const server = mockServerPool.createServer(); + const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); + + const rawResponseBody = { metadata: { region: "region", tier: "tier" }, id: "id", name: "name" }; + + server.mockEndpoint().get("/organizations").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.getOrganization(); + expect(response).toEqual(rawResponseBody); + }); +}); diff --git a/seed/ts-sdk/allof/tsconfig.base.json b/seed/ts-sdk/allof/tsconfig.base.json new file mode 100644 index 000000000000..93a92c0630b5 --- /dev/null +++ b/seed/ts-sdk/allof/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "extendedDiagnostics": true, + "strict": true, + "target": "ES6", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "isolatedModules": true, + "isolatedDeclarations": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof/tsconfig.cjs.json b/seed/ts-sdk/allof/tsconfig.cjs.json new file mode 100644 index 000000000000..5c11446f5984 --- /dev/null +++ b/seed/ts-sdk/allof/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "dist/cjs" + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof/tsconfig.esm.json b/seed/ts-sdk/allof/tsconfig.esm.json new file mode 100644 index 000000000000..6ce909748b2c --- /dev/null +++ b/seed/ts-sdk/allof/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/esm", + "verbatimModuleSyntax": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/allof/tsconfig.json b/seed/ts-sdk/allof/tsconfig.json new file mode 100644 index 000000000000..d77fdf00d259 --- /dev/null +++ b/seed/ts-sdk/allof/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.cjs.json" +} diff --git a/seed/ts-sdk/allof/vitest.config.mts b/seed/ts-sdk/allof/vitest.config.mts new file mode 100644 index 000000000000..0dee5a752d39 --- /dev/null +++ b/seed/ts-sdk/allof/vitest.config.mts @@ -0,0 +1,32 @@ +import { defineConfig } from "vitest/config"; +export default defineConfig({ + test: { + typecheck: { + enabled: true, + tsconfig: "./tests/tsconfig.json", + }, + projects: [ + { + test: { + globals: true, + name: "unit", + environment: "node", + root: "./tests", + include: ["**/*.test.{js,ts,jsx,tsx}"], + exclude: ["wire/**"], + setupFiles: ["./setup.ts"], + }, + }, + { + test: { + globals: true, + name: "wire", + environment: "node", + root: "./tests/wire", + setupFiles: ["../setup.ts", "../mock-server/setup.ts"], + }, + }, + ], + passWithNoTests: true, + }, +}); From edcfe09319fa150fac01673303a26018bf360ada Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 14:36:01 +0000 Subject: [PATCH 12/29] Automated update of seed files --- seed/csharp-model/allof-inline/.editorconfig | 35 + .../allof-inline/.fern/metadata.json | 7 + .../allof-inline/.github/workflows/ci.yml | 52 + seed/csharp-model/allof-inline/.gitignore | 484 +++++++ seed/csharp-model/allof-inline/SeedApi.slnx | 4 + seed/csharp-model/allof-inline/snippet.json | 0 .../Core/Json/AdditionalPropertiesTests.cs | 365 +++++ .../Core/Json/DateOnlyJsonTests.cs | 100 ++ .../Core/Json/DateTimeJsonTests.cs | 134 ++ .../Core/Json/JsonAccessAttributeTests.cs | 160 +++ .../Core/Json/OneOfSerializerTests.cs | 314 +++++ .../SeedApi.Test/SeedApi.Test.Custom.props | 6 + .../src/SeedApi.Test/SeedApi.Test.csproj | 36 + .../Utils/AdditionalPropertiesComparer.cs | 219 +++ .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 + .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 ++++ .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 + .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 ++ .../SeedApi.Test/Utils/OptionalComparer.cs | 104 ++ .../Utils/ReadOnlyMemoryComparer.cs | 87 ++ .../allof-inline/src/SeedApi/AuditInfo.cs | 56 + .../allof-inline/src/SeedApi/BaseOrg.cs | 31 + .../src/SeedApi/BaseOrgMetadata.cs | 37 + .../src/SeedApi/CombinedEntity.cs | 46 + .../src/SeedApi/CombinedEntityStatus.cs | 115 ++ .../SeedApi/Core/CollectionItemSerializer.cs | 91 ++ .../src/SeedApi/Core/Constants.cs | 7 + .../src/SeedApi/Core/DateOnlyConverter.cs | 747 ++++++++++ .../src/SeedApi/Core/DateTimeSerializer.cs | 40 + .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 + .../src/SeedApi/Core/JsonConfiguration.cs | 275 ++++ .../src/SeedApi/Core/NullableAttribute.cs | 18 + .../src/SeedApi/Core/OneOfSerializer.cs | 145 ++ .../allof-inline/src/SeedApi/Core/Optional.cs | 474 +++++++ .../src/SeedApi/Core/OptionalAttribute.cs | 17 + .../Core/Public/AdditionalProperties.cs | 353 +++++ .../src/SeedApi/Core/Public/FileParameter.cs | 63 + .../src/SeedApi/Core/Public/Version.cs | 7 + .../src/SeedApi/Core/StringEnum.cs | 6 + .../src/SeedApi/Core/StringEnumExtensions.cs | 6 + .../allof-inline/src/SeedApi/Describable.cs | 37 + .../allof-inline/src/SeedApi/DetailedOrg.cs | 28 + .../src/SeedApi/DetailedOrgMetadata.cs | 37 + .../allof-inline/src/SeedApi/Identifiable.cs | 37 + .../allof-inline/src/SeedApi/Organization.cs | 34 + .../src/SeedApi/OrganizationMetadata.cs | 37 + .../src/SeedApi/PaginatedResult.cs | 34 + .../allof-inline/src/SeedApi/PagingCursors.cs | 37 + .../src/SeedApi/RuleExecutionContext.cs | 119 ++ .../allof-inline/src/SeedApi/RuleResponse.cs | 65 + .../src/SeedApi/RuleResponseStatus.cs | 119 ++ .../allof-inline/src/SeedApi/RuleType.cs | 34 + .../src/SeedApi/RuleTypeSearchResponse.cs | 34 + .../src/SeedApi/SeedApi.Custom.props | 20 + .../allof-inline/src/SeedApi/SeedApi.csproj | 60 + .../allof-inline/src/SeedApi/User.cs | 31 + .../src/SeedApi/UserSearchResponse.cs | 34 + seed/csharp-model/allof/.editorconfig | 35 + seed/csharp-model/allof/.fern/metadata.json | 7 + .../allof/.github/workflows/ci.yml | 52 + seed/csharp-model/allof/.gitignore | 484 +++++++ seed/csharp-model/allof/SeedApi.slnx | 4 + seed/csharp-model/allof/snippet.json | 0 .../Core/Json/AdditionalPropertiesTests.cs | 365 +++++ .../Core/Json/DateOnlyJsonTests.cs | 100 ++ .../Core/Json/DateTimeJsonTests.cs | 134 ++ .../Core/Json/JsonAccessAttributeTests.cs | 160 +++ .../Core/Json/OneOfSerializerTests.cs | 314 +++++ .../SeedApi.Test/SeedApi.Test.Custom.props | 6 + .../src/SeedApi.Test/SeedApi.Test.csproj | 36 + .../Utils/AdditionalPropertiesComparer.cs | 219 +++ .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 + .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 ++++ .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 + .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 ++ .../SeedApi.Test/Utils/OptionalComparer.cs | 104 ++ .../Utils/ReadOnlyMemoryComparer.cs | 87 ++ .../allof/src/SeedApi/AuditInfo.cs | 56 + .../csharp-model/allof/src/SeedApi/BaseOrg.cs | 31 + .../allof/src/SeedApi/BaseOrgMetadata.cs | 37 + .../allof/src/SeedApi/CombinedEntity.cs | 46 + .../allof/src/SeedApi/CombinedEntityStatus.cs | 115 ++ .../SeedApi/Core/CollectionItemSerializer.cs | 91 ++ .../allof/src/SeedApi/Core/Constants.cs | 7 + .../src/SeedApi/Core/DateOnlyConverter.cs | 747 ++++++++++ .../src/SeedApi/Core/DateTimeSerializer.cs | 40 + .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 + .../src/SeedApi/Core/JsonConfiguration.cs | 275 ++++ .../src/SeedApi/Core/NullableAttribute.cs | 18 + .../allof/src/SeedApi/Core/OneOfSerializer.cs | 145 ++ .../allof/src/SeedApi/Core/Optional.cs | 474 +++++++ .../src/SeedApi/Core/OptionalAttribute.cs | 17 + .../Core/Public/AdditionalProperties.cs | 353 +++++ .../src/SeedApi/Core/Public/FileParameter.cs | 63 + .../allof/src/SeedApi/Core/Public/Version.cs | 7 + .../allof/src/SeedApi/Core/StringEnum.cs | 6 + .../src/SeedApi/Core/StringEnumExtensions.cs | 6 + .../allof/src/SeedApi/Describable.cs | 37 + .../allof/src/SeedApi/DetailedOrg.cs | 28 + .../allof/src/SeedApi/DetailedOrgMetadata.cs | 37 + .../allof/src/SeedApi/Identifiable.cs | 37 + .../allof/src/SeedApi/Organization.cs | 34 + .../allof/src/SeedApi/PaginatedResult.cs | 34 + .../allof/src/SeedApi/PagingCursors.cs | 37 + .../allof/src/SeedApi/RuleExecutionContext.cs | 119 ++ .../allof/src/SeedApi/RuleResponse.cs | 65 + .../allof/src/SeedApi/RuleResponseStatus.cs | 119 ++ .../allof/src/SeedApi/RuleType.cs | 34 + .../src/SeedApi/RuleTypeSearchResponse.cs | 34 + .../allof/src/SeedApi/SeedApi.Custom.props | 20 + .../allof/src/SeedApi/SeedApi.csproj | 60 + seed/csharp-model/allof/src/SeedApi/User.cs | 31 + .../allof/src/SeedApi/UserSearchResponse.cs | 34 + .../allof-inline/.fern/metadata.json | 2 +- .../Core/RawClientTests/MultipartFormTests.cs | 23 +- .../src/SeedApi.Test/SeedApi.Test.csproj | 3 + .../src/SeedApi/Core/RawClient.cs | 3 +- .../allof-inline/src/SeedApi/SeedApi.csproj | 4 + .../go-model/allof-inline/.fern/metadata.json | 7 + seed/go-model/allof-inline/doc.go | 1 + seed/go-model/allof-inline/go.mod | 14 + seed/go-model/allof-inline/go.sum | 10 + .../allof-inline/internal/extra_properties.go | 141 ++ .../internal/extra_properties_test.go | 228 +++ .../allof-inline/internal/stringer.go | 13 + seed/go-model/allof-inline/internal/time.go | 165 +++ seed/go-model/allof-inline/snippet.json | 0 seed/go-model/allof-inline/types.go | 1246 +++++++++++++++++ seed/go-model/allof/.fern/metadata.json | 7 + seed/go-model/allof/doc.go | 1 + seed/go-model/allof/go.mod | 14 + seed/go-model/allof/go.sum | 10 + .../allof/internal/extra_properties.go | 141 ++ .../allof/internal/extra_properties_test.go | 228 +++ seed/go-model/allof/internal/stringer.go | 13 + seed/go-model/allof/internal/time.go | 165 +++ seed/go-model/allof/snippet.json | 0 seed/go-model/allof/types.go | 1185 ++++++++++++++++ seed/go-sdk/allof-inline/.fern/metadata.json | 2 +- seed/go-sdk/allof/.fern/metadata.json | 2 +- .../allof-inline/.github/workflows/ci.yml | 65 + seed/java-model/allof-inline/.gitignore | 24 + seed/java-model/allof-inline/build.gradle | 98 ++ seed/java-model/allof-inline/settings.gradle | 2 + seed/java-model/allof-inline/snippet.json | 0 .../seed/api/core/DateTimeDeserializer.java | 56 + .../com/seed/api/core/DoubleSerializer.java | 44 + .../java/com/seed/api/core/ObjectMappers.java | 52 + .../api/core/Rfc2822DateTimeDeserializer.java | 26 + .../java/com/seed/api/model/AuditInfo.java | 192 +++ .../main/java/com/seed/api/model/BaseOrg.java | 127 ++ .../com/seed/api/model/BaseOrgMetadata.java | 151 ++ .../com/seed/api/model/CombinedEntity.java | 218 +++ .../seed/api/model/CombinedEntityStatus.java | 26 + .../java/com/seed/api/model/Describable.java | 128 ++ .../java/com/seed/api/model/DetailedOrg.java | 91 ++ .../seed/api/model/DetailedOrgMetadata.java | 151 ++ .../java/com/seed/api/model/Identifiable.java | 151 ++ .../java/com/seed/api/model/Organization.java | 149 ++ .../seed/api/model/OrganizationMetadata.java | 151 ++ .../com/seed/api/model/PaginatedResult.java | 158 +++ .../com/seed/api/model/PagingCursors.java | 151 ++ .../seed/api/model/RuleExecutionContext.java | 28 + .../java/com/seed/api/model/RuleResponse.java | 350 +++++ .../seed/api/model/RuleResponseStatus.java | 28 + .../java/com/seed/api/model/RuleType.java | 149 ++ .../api/model/RuleTypeSearchResponse.java | 141 ++ .../main/java/com/seed/api/model/User.java | 116 ++ .../seed/api/model/UserSearchResponse.java | 141 ++ .../java-model/allof/.github/workflows/ci.yml | 65 + seed/java-model/allof/.gitignore | 24 + seed/java-model/allof/build.gradle | 98 ++ seed/java-model/allof/settings.gradle | 2 + seed/java-model/allof/snippet.json | 0 .../seed/api/core/DateTimeDeserializer.java | 56 + .../com/seed/api/core/DoubleSerializer.java | 44 + .../java/com/seed/api/core/ObjectMappers.java | 52 + .../api/core/Rfc2822DateTimeDeserializer.java | 26 + .../java/com/seed/api/model/AuditInfo.java | 196 +++ .../main/java/com/seed/api/model/BaseOrg.java | 127 ++ .../com/seed/api/model/BaseOrgMetadata.java | 151 ++ .../com/seed/api/model/CombinedEntity.java | 218 +++ .../seed/api/model/CombinedEntityStatus.java | 26 + .../java/com/seed/api/model/Describable.java | 128 ++ .../java/com/seed/api/model/DetailedOrg.java | 91 ++ .../seed/api/model/DetailedOrgMetadata.java | 151 ++ .../java/com/seed/api/model/IAuditInfo.java | 19 + .../java/com/seed/api/model/Identifiable.java | 151 ++ .../java/com/seed/api/model/Organization.java | 149 ++ .../com/seed/api/model/PaginatedResult.java | 158 +++ .../com/seed/api/model/PagingCursors.java | 151 ++ .../seed/api/model/RuleExecutionContext.java | 28 + .../java/com/seed/api/model/RuleResponse.java | 354 +++++ .../seed/api/model/RuleResponseStatus.java | 28 + .../java/com/seed/api/model/RuleType.java | 149 ++ .../api/model/RuleTypeSearchResponse.java | 141 ++ .../main/java/com/seed/api/model/User.java | 116 ++ .../seed/api/model/UserSearchResponse.java | 141 ++ .../java-sdk/allof-inline/.fern/metadata.json | 2 +- seed/java-sdk/allof/.fern/metadata.json | 2 +- seed/openapi/allof-inline/openapi.yml | 432 ++++++ seed/openapi/allof-inline/snippet.json | 0 seed/openapi/allof/openapi.yml | 403 ++++++ seed/openapi/allof/snippet.json | 0 .../allof-inline/.fern/metadata.json | 7 + .../allof-inline/.github/workflows/ci.yml | 52 + seed/php-model/allof-inline/.gitignore | 5 + seed/php-model/allof-inline/composer.json | 46 + seed/php-model/allof-inline/phpstan.neon | 6 + seed/php-model/allof-inline/phpunit.xml | 7 + seed/php-model/allof-inline/snippet.json | 0 seed/php-model/allof-inline/src/AuditInfo.php | 63 + seed/php-model/allof-inline/src/BaseOrg.php | 42 + .../allof-inline/src/BaseOrgMetadata.php | 42 + .../allof-inline/src/CombinedEntity.php | 58 + .../allof-inline/src/CombinedEntityStatus.php | 9 + .../src/Core/Json/JsonDecoder.php | 161 +++ .../src/Core/Json/JsonDeserializer.php | 218 +++ .../src/Core/Json/JsonEncoder.php | 20 + .../src/Core/Json/JsonProperty.php | 13 + .../src/Core/Json/JsonSerializableType.php | 225 +++ .../src/Core/Json/JsonSerializer.php | 205 +++ .../allof-inline/src/Core/Json/Utils.php | 62 + .../allof-inline/src/Core/Types/ArrayType.php | 16 + .../allof-inline/src/Core/Types/Constant.php | 12 + .../allof-inline/src/Core/Types/Date.php | 16 + .../allof-inline/src/Core/Types/Union.php | 62 + .../allof-inline/src/Describable.php | 42 + .../allof-inline/src/DetailedOrg.php | 34 + .../allof-inline/src/DetailedOrgMetadata.php | 42 + .../allof-inline/src/Identifiable.php | 42 + .../allof-inline/src/Organization.php | 50 + .../allof-inline/src/OrganizationMetadata.php | 42 + .../allof-inline/src/PaginatedResult.php | 43 + .../allof-inline/src/PagingCursors.php | 42 + .../allof-inline/src/RuleExecutionContext.php | 10 + .../allof-inline/src/RuleResponse.php | 92 ++ .../allof-inline/src/RuleResponseStatus.php | 10 + seed/php-model/allof-inline/src/RuleType.php | 50 + .../src/RuleTypeSearchResponse.php | 43 + seed/php-model/allof-inline/src/User.php | 42 + .../allof-inline/src/UserSearchResponse.php | 43 + .../Core/Json/AdditionalPropertiesTest.php | 76 + .../tests/Core/Json/DateArrayTest.php | 54 + .../tests/Core/Json/EmptyArrayTest.php | 71 + .../allof-inline/tests/Core/Json/EnumTest.php | 77 + .../tests/Core/Json/ExhaustiveTest.php | 197 +++ .../tests/Core/Json/InvalidTest.php | 42 + .../tests/Core/Json/NestedUnionArrayTest.php | 89 ++ .../tests/Core/Json/NullPropertyTest.php | 53 + .../tests/Core/Json/NullableArrayTest.php | 49 + .../tests/Core/Json/ScalarTest.php | 116 ++ .../tests/Core/Json/TraitTest.php | 60 + .../tests/Core/Json/UnionArrayTest.php | 57 + .../tests/Core/Json/UnionPropertyTest.php | 111 ++ seed/php-model/allof/.fern/metadata.json | 7 + seed/php-model/allof/.github/workflows/ci.yml | 52 + seed/php-model/allof/.gitignore | 5 + seed/php-model/allof/composer.json | 46 + seed/php-model/allof/phpstan.neon | 6 + seed/php-model/allof/phpunit.xml | 7 + seed/php-model/allof/snippet.json | 0 seed/php-model/allof/src/AuditInfo.php | 63 + seed/php-model/allof/src/BaseOrg.php | 42 + seed/php-model/allof/src/BaseOrgMetadata.php | 42 + seed/php-model/allof/src/CombinedEntity.php | 58 + .../allof/src/CombinedEntityStatus.php | 9 + .../allof/src/Core/Json/JsonDecoder.php | 161 +++ .../allof/src/Core/Json/JsonDeserializer.php | 218 +++ .../allof/src/Core/Json/JsonEncoder.php | 20 + .../allof/src/Core/Json/JsonProperty.php | 13 + .../src/Core/Json/JsonSerializableType.php | 225 +++ .../allof/src/Core/Json/JsonSerializer.php | 205 +++ seed/php-model/allof/src/Core/Json/Utils.php | 62 + .../allof/src/Core/Types/ArrayType.php | 16 + .../allof/src/Core/Types/Constant.php | 12 + seed/php-model/allof/src/Core/Types/Date.php | 16 + seed/php-model/allof/src/Core/Types/Union.php | 62 + seed/php-model/allof/src/Describable.php | 42 + seed/php-model/allof/src/DetailedOrg.php | 34 + .../allof/src/DetailedOrgMetadata.php | 42 + seed/php-model/allof/src/Identifiable.php | 42 + seed/php-model/allof/src/Organization.php | 50 + seed/php-model/allof/src/PaginatedResult.php | 43 + seed/php-model/allof/src/PagingCursors.php | 42 + .../allof/src/RuleExecutionContext.php | 10 + seed/php-model/allof/src/RuleResponse.php | 70 + .../allof/src/RuleResponseStatus.php | 10 + seed/php-model/allof/src/RuleType.php | 50 + .../allof/src/RuleTypeSearchResponse.php | 43 + seed/php-model/allof/src/Traits/AuditInfo.php | 42 + seed/php-model/allof/src/User.php | 42 + .../allof/src/UserSearchResponse.php | 43 + .../Core/Json/AdditionalPropertiesTest.php | 76 + .../allof/tests/Core/Json/DateArrayTest.php | 54 + .../allof/tests/Core/Json/EmptyArrayTest.php | 71 + .../allof/tests/Core/Json/EnumTest.php | 77 + .../allof/tests/Core/Json/ExhaustiveTest.php | 197 +++ .../allof/tests/Core/Json/InvalidTest.php | 42 + .../tests/Core/Json/NestedUnionArrayTest.php | 89 ++ .../tests/Core/Json/NullPropertyTest.php | 53 + .../tests/Core/Json/NullableArrayTest.php | 49 + .../allof/tests/Core/Json/ScalarTest.php | 116 ++ .../allof/tests/Core/Json/TraitTest.php | 60 + .../allof/tests/Core/Json/UnionArrayTest.php | 57 + .../tests/Core/Json/UnionPropertyTest.php | 111 ++ seed/php-sdk/allof-inline/.fern/metadata.json | 2 +- seed/php-sdk/allof/.fern/metadata.json | 2 +- .../allof-inline/.github/workflows/ci.yml | 71 + seed/pydantic/allof-inline/.gitignore | 5 + seed/pydantic/allof-inline/README.md | 0 seed/pydantic/allof-inline/poetry.lock | 546 ++++++++ seed/pydantic/allof-inline/pyproject.toml | 93 ++ seed/pydantic/allof-inline/requirements.txt | 2 + seed/pydantic/allof-inline/snippet.json | 0 .../allof-inline/src/seed/api/__init__.py | 47 + .../allof-inline/src/seed/api/audit_info.py | 43 + .../allof-inline/src/seed/api/base_org.py | 19 + .../src/seed/api/base_org_metadata.py | 25 + .../src/seed/api/combined_entity.py | 33 + .../src/seed/api/combined_entity_status.py | 5 + .../src/seed/api/core/__init__.py | 29 + .../src/seed/api/core/datetime_utils.py | 70 + .../allof-inline/src/seed/api/core/enum.py | 20 + .../src/seed/api/core/pydantic_utilities.py | 515 +++++++ .../src/seed/api/core/serialization.py | 276 ++++ .../allof-inline/src/seed/api/describable.py | 25 + .../allof-inline/src/seed/api/detailed_org.py | 18 + .../src/seed/api/detailed_org_metadata.py | 25 + .../allof-inline/src/seed/api/identifiable.py | 25 + .../allof-inline/src/seed/api/organization.py | 20 + .../src/seed/api/organization_metadata.py | 25 + .../src/seed/api/paginated_result.py | 22 + .../src/seed/api/paging_cursors.py | 25 + .../allof-inline/src/seed/api/py.typed | 0 .../src/seed/api/rule_execution_context.py | 5 + .../src/seed/api/rule_response.py | 49 + .../src/seed/api/rule_response_status.py | 5 + .../allof-inline/src/seed/api/rule_type.py | 19 + .../src/seed/api/rule_type_search_response.py | 23 + .../allof-inline/src/seed/api/user.py | 18 + .../src/seed/api/user_search_response.py | 23 + .../allof-inline/tests/custom/test_client.py | 7 + seed/pydantic/allof/.github/workflows/ci.yml | 71 + seed/pydantic/allof/.gitignore | 5 + seed/pydantic/allof/README.md | 0 seed/pydantic/allof/poetry.lock | 546 ++++++++ seed/pydantic/allof/pyproject.toml | 93 ++ seed/pydantic/allof/requirements.txt | 2 + seed/pydantic/allof/snippet.json | 0 seed/pydantic/allof/src/seed/api/__init__.py | 45 + .../pydantic/allof/src/seed/api/audit_info.py | 43 + seed/pydantic/allof/src/seed/api/base_org.py | 19 + .../allof/src/seed/api/base_org_metadata.py | 25 + .../allof/src/seed/api/combined_entity.py | 32 + .../src/seed/api/combined_entity_status.py | 5 + .../allof/src/seed/api/core/__init__.py | 29 + .../allof/src/seed/api/core/datetime_utils.py | 70 + seed/pydantic/allof/src/seed/api/core/enum.py | 20 + .../src/seed/api/core/pydantic_utilities.py | 515 +++++++ .../allof/src/seed/api/core/serialization.py | 276 ++++ .../allof/src/seed/api/describable.py | 25 + .../allof/src/seed/api/detailed_org.py | 18 + .../src/seed/api/detailed_org_metadata.py | 25 + .../allof/src/seed/api/identifiable.py | 25 + .../allof/src/seed/api/organization.py | 20 + .../allof/src/seed/api/paginated_result.py | 22 + .../allof/src/seed/api/paging_cursors.py | 25 + seed/pydantic/allof/src/seed/api/py.typed | 0 .../src/seed/api/rule_execution_context.py | 5 + .../allof/src/seed/api/rule_response.py | 29 + .../src/seed/api/rule_response_status.py | 5 + seed/pydantic/allof/src/seed/api/rule_type.py | 19 + .../src/seed/api/rule_type_search_response.py | 24 + seed/pydantic/allof/src/seed/api/user.py | 18 + .../src/seed/api/user_search_response.py | 24 + .../allof/tests/custom/test_client.py | 7 + .../no-custom-config/.fern/metadata.json | 2 +- .../allof-inline/no-custom-config/poetry.lock | 319 ++--- .../no-custom-config/pyproject.toml | 2 +- .../no-custom-config/.fern/metadata.json | 2 +- .../allof/no-custom-config/poetry.lock | 319 ++--- .../allof/no-custom-config/pyproject.toml | 2 +- .../allof-inline/.fern/metadata.json | 2 +- seed/ruby-sdk-v2/allof/.fern/metadata.json | 2 +- .../allof-inline/.fern/metadata.json | 7 + seed/rust-model/allof-inline/snippet.json | 0 .../rust-model/allof-inline/src/audit_info.rs | 73 + seed/rust-model/allof-inline/src/base_org.rs | 44 + .../allof-inline/src/base_org_metadata.rs | 46 + .../allof-inline/src/combined_entity.rs | 65 + .../src/combined_entity_status.rs | 42 + .../allof-inline/src/describable.rs | 44 + .../allof-inline/src/detailed_org.rs | 33 + .../allof-inline/src/detailed_org_metadata.rs | 46 + seed/rust-model/allof-inline/src/error.rs | 19 + .../allof-inline/src/identifiable.rs | 46 + seed/rust-model/allof-inline/src/lib.rs | 7 + seed/rust-model/allof-inline/src/mod.rs | 56 + .../allof-inline/src/organization.rs | 54 + .../allof-inline/src/organization_metadata.rs | 46 + .../allof-inline/src/paginated_result.rs | 46 + .../allof-inline/src/paging_cursors.rs | 46 + .../allof-inline/src/rule_create_request.rs | 46 + .../src/rule_execution_context.rs | 47 + .../allof-inline/src/rule_response.rs | 112 ++ .../allof-inline/src/rule_response_status.rs | 46 + seed/rust-model/allof-inline/src/rule_type.rs | 54 + .../src/rule_type_search_response.rs | 45 + .../src/search_rule_types_query_request.rs | 35 + seed/rust-model/allof-inline/src/user.rs | 45 + .../allof-inline/src/user_search_response.rs | 45 + seed/rust-model/allof/.fern/metadata.json | 7 + seed/rust-model/allof/snippet.json | 0 seed/rust-model/allof/src/audit_info.rs | 73 + seed/rust-model/allof/src/base_org.rs | 44 + .../rust-model/allof/src/base_org_metadata.rs | 46 + seed/rust-model/allof/src/combined_entity.rs | 65 + .../allof/src/combined_entity_status.rs | 42 + seed/rust-model/allof/src/describable.rs | 44 + seed/rust-model/allof/src/detailed_org.rs | 33 + .../allof/src/detailed_org_metadata.rs | 46 + seed/rust-model/allof/src/error.rs | 19 + seed/rust-model/allof/src/identifiable.rs | 46 + seed/rust-model/allof/src/lib.rs | 7 + seed/rust-model/allof/src/mod.rs | 54 + seed/rust-model/allof/src/organization.rs | 54 + seed/rust-model/allof/src/paginated_result.rs | 46 + seed/rust-model/allof/src/paging_cursors.rs | 46 + .../allof/src/rule_create_request.rs | 46 + .../allof/src/rule_execution_context.rs | 47 + seed/rust-model/allof/src/rule_response.rs | 74 + .../allof/src/rule_response_status.rs | 46 + seed/rust-model/allof/src/rule_type.rs | 54 + .../allof/src/rule_type_search_response.rs | 45 + .../src/search_rule_types_query_request.rs | 35 + seed/rust-model/allof/src/user.rs | 45 + .../allof/src/user_search_response.rs | 45 + .../rust-sdk/allof-inline/.fern/metadata.json | 2 +- seed/rust-sdk/allof/.fern/metadata.json | 2 +- .../allof-inline/.fern/metadata.json | 2 +- seed/swift-sdk/allof/.fern/metadata.json | 2 +- seed/ts-sdk/allof-inline/.fern/metadata.json | 2 +- seed/ts-sdk/allof/.fern/metadata.json | 2 +- 444 files changed, 35177 insertions(+), 349 deletions(-) create mode 100644 seed/csharp-model/allof-inline/.editorconfig create mode 100644 seed/csharp-model/allof-inline/.fern/metadata.json create mode 100644 seed/csharp-model/allof-inline/.github/workflows/ci.yml create mode 100644 seed/csharp-model/allof-inline/.gitignore create mode 100644 seed/csharp-model/allof-inline/SeedApi.slnx create mode 100644 seed/csharp-model/allof-inline/snippet.json create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Describable.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Organization.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/User.cs create mode 100644 seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs create mode 100644 seed/csharp-model/allof/.editorconfig create mode 100644 seed/csharp-model/allof/.fern/metadata.json create mode 100644 seed/csharp-model/allof/.github/workflows/ci.yml create mode 100644 seed/csharp-model/allof/.gitignore create mode 100644 seed/csharp-model/allof/SeedApi.slnx create mode 100644 seed/csharp-model/allof/snippet.json create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/AuditInfo.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/BaseOrg.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Constants.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Optional.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Describable.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Identifiable.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/Organization.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/PagingCursors.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/RuleResponse.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/RuleType.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props create mode 100644 seed/csharp-model/allof/src/SeedApi/SeedApi.csproj create mode 100644 seed/csharp-model/allof/src/SeedApi/User.cs create mode 100644 seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs create mode 100644 seed/go-model/allof-inline/.fern/metadata.json create mode 100644 seed/go-model/allof-inline/doc.go create mode 100644 seed/go-model/allof-inline/go.mod create mode 100644 seed/go-model/allof-inline/go.sum create mode 100644 seed/go-model/allof-inline/internal/extra_properties.go create mode 100644 seed/go-model/allof-inline/internal/extra_properties_test.go create mode 100644 seed/go-model/allof-inline/internal/stringer.go create mode 100644 seed/go-model/allof-inline/internal/time.go create mode 100644 seed/go-model/allof-inline/snippet.json create mode 100644 seed/go-model/allof-inline/types.go create mode 100644 seed/go-model/allof/.fern/metadata.json create mode 100644 seed/go-model/allof/doc.go create mode 100644 seed/go-model/allof/go.mod create mode 100644 seed/go-model/allof/go.sum create mode 100644 seed/go-model/allof/internal/extra_properties.go create mode 100644 seed/go-model/allof/internal/extra_properties_test.go create mode 100644 seed/go-model/allof/internal/stringer.go create mode 100644 seed/go-model/allof/internal/time.go create mode 100644 seed/go-model/allof/snippet.json create mode 100644 seed/go-model/allof/types.go create mode 100644 seed/java-model/allof-inline/.github/workflows/ci.yml create mode 100644 seed/java-model/allof-inline/.gitignore create mode 100644 seed/java-model/allof-inline/build.gradle create mode 100644 seed/java-model/allof-inline/settings.gradle create mode 100644 seed/java-model/allof-inline/snippet.json create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java create mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java create mode 100644 seed/java-model/allof/.github/workflows/ci.yml create mode 100644 seed/java-model/allof/.gitignore create mode 100644 seed/java-model/allof/build.gradle create mode 100644 seed/java-model/allof/settings.gradle create mode 100644 seed/java-model/allof/snippet.json create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/User.java create mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java create mode 100644 seed/openapi/allof-inline/openapi.yml create mode 100644 seed/openapi/allof-inline/snippet.json create mode 100644 seed/openapi/allof/openapi.yml create mode 100644 seed/openapi/allof/snippet.json create mode 100644 seed/php-model/allof-inline/.fern/metadata.json create mode 100644 seed/php-model/allof-inline/.github/workflows/ci.yml create mode 100644 seed/php-model/allof-inline/.gitignore create mode 100644 seed/php-model/allof-inline/composer.json create mode 100644 seed/php-model/allof-inline/phpstan.neon create mode 100644 seed/php-model/allof-inline/phpunit.xml create mode 100644 seed/php-model/allof-inline/snippet.json create mode 100644 seed/php-model/allof-inline/src/AuditInfo.php create mode 100644 seed/php-model/allof-inline/src/BaseOrg.php create mode 100644 seed/php-model/allof-inline/src/BaseOrgMetadata.php create mode 100644 seed/php-model/allof-inline/src/CombinedEntity.php create mode 100644 seed/php-model/allof-inline/src/CombinedEntityStatus.php create mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonSerializableType.php create mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/allof-inline/src/Core/Json/Utils.php create mode 100644 seed/php-model/allof-inline/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/allof-inline/src/Core/Types/Constant.php create mode 100644 seed/php-model/allof-inline/src/Core/Types/Date.php create mode 100644 seed/php-model/allof-inline/src/Core/Types/Union.php create mode 100644 seed/php-model/allof-inline/src/Describable.php create mode 100644 seed/php-model/allof-inline/src/DetailedOrg.php create mode 100644 seed/php-model/allof-inline/src/DetailedOrgMetadata.php create mode 100644 seed/php-model/allof-inline/src/Identifiable.php create mode 100644 seed/php-model/allof-inline/src/Organization.php create mode 100644 seed/php-model/allof-inline/src/OrganizationMetadata.php create mode 100644 seed/php-model/allof-inline/src/PaginatedResult.php create mode 100644 seed/php-model/allof-inline/src/PagingCursors.php create mode 100644 seed/php-model/allof-inline/src/RuleExecutionContext.php create mode 100644 seed/php-model/allof-inline/src/RuleResponse.php create mode 100644 seed/php-model/allof-inline/src/RuleResponseStatus.php create mode 100644 seed/php-model/allof-inline/src/RuleType.php create mode 100644 seed/php-model/allof-inline/src/RuleTypeSearchResponse.php create mode 100644 seed/php-model/allof-inline/src/User.php create mode 100644 seed/php-model/allof-inline/src/UserSearchResponse.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/EnumTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/TraitTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php create mode 100644 seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php create mode 100644 seed/php-model/allof/.fern/metadata.json create mode 100644 seed/php-model/allof/.github/workflows/ci.yml create mode 100644 seed/php-model/allof/.gitignore create mode 100644 seed/php-model/allof/composer.json create mode 100644 seed/php-model/allof/phpstan.neon create mode 100644 seed/php-model/allof/phpunit.xml create mode 100644 seed/php-model/allof/snippet.json create mode 100644 seed/php-model/allof/src/AuditInfo.php create mode 100644 seed/php-model/allof/src/BaseOrg.php create mode 100644 seed/php-model/allof/src/BaseOrgMetadata.php create mode 100644 seed/php-model/allof/src/CombinedEntity.php create mode 100644 seed/php-model/allof/src/CombinedEntityStatus.php create mode 100644 seed/php-model/allof/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/allof/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/allof/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/allof/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/allof/src/Core/Json/JsonSerializableType.php create mode 100644 seed/php-model/allof/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/allof/src/Core/Json/Utils.php create mode 100644 seed/php-model/allof/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/allof/src/Core/Types/Constant.php create mode 100644 seed/php-model/allof/src/Core/Types/Date.php create mode 100644 seed/php-model/allof/src/Core/Types/Union.php create mode 100644 seed/php-model/allof/src/Describable.php create mode 100644 seed/php-model/allof/src/DetailedOrg.php create mode 100644 seed/php-model/allof/src/DetailedOrgMetadata.php create mode 100644 seed/php-model/allof/src/Identifiable.php create mode 100644 seed/php-model/allof/src/Organization.php create mode 100644 seed/php-model/allof/src/PaginatedResult.php create mode 100644 seed/php-model/allof/src/PagingCursors.php create mode 100644 seed/php-model/allof/src/RuleExecutionContext.php create mode 100644 seed/php-model/allof/src/RuleResponse.php create mode 100644 seed/php-model/allof/src/RuleResponseStatus.php create mode 100644 seed/php-model/allof/src/RuleType.php create mode 100644 seed/php-model/allof/src/RuleTypeSearchResponse.php create mode 100644 seed/php-model/allof/src/Traits/AuditInfo.php create mode 100644 seed/php-model/allof/src/User.php create mode 100644 seed/php-model/allof/src/UserSearchResponse.php create mode 100644 seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/DateArrayTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/EnumTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/InvalidTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/NullPropertyTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/NullableArrayTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/ScalarTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/TraitTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/UnionArrayTest.php create mode 100644 seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php create mode 100644 seed/pydantic/allof-inline/.github/workflows/ci.yml create mode 100644 seed/pydantic/allof-inline/.gitignore create mode 100644 seed/pydantic/allof-inline/README.md create mode 100644 seed/pydantic/allof-inline/poetry.lock create mode 100644 seed/pydantic/allof-inline/pyproject.toml create mode 100644 seed/pydantic/allof-inline/requirements.txt create mode 100644 seed/pydantic/allof-inline/snippet.json create mode 100644 seed/pydantic/allof-inline/src/seed/api/__init__.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/audit_info.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/base_org.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/combined_entity.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/core/__init__.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/core/enum.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/core/serialization.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/describable.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/detailed_org.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/identifiable.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/organization.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/organization_metadata.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/paginated_result.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/paging_cursors.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/py.typed create mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_response.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_response_status.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_type.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/user.py create mode 100644 seed/pydantic/allof-inline/src/seed/api/user_search_response.py create mode 100644 seed/pydantic/allof-inline/tests/custom/test_client.py create mode 100644 seed/pydantic/allof/.github/workflows/ci.yml create mode 100644 seed/pydantic/allof/.gitignore create mode 100644 seed/pydantic/allof/README.md create mode 100644 seed/pydantic/allof/poetry.lock create mode 100644 seed/pydantic/allof/pyproject.toml create mode 100644 seed/pydantic/allof/requirements.txt create mode 100644 seed/pydantic/allof/snippet.json create mode 100644 seed/pydantic/allof/src/seed/api/__init__.py create mode 100644 seed/pydantic/allof/src/seed/api/audit_info.py create mode 100644 seed/pydantic/allof/src/seed/api/base_org.py create mode 100644 seed/pydantic/allof/src/seed/api/base_org_metadata.py create mode 100644 seed/pydantic/allof/src/seed/api/combined_entity.py create mode 100644 seed/pydantic/allof/src/seed/api/combined_entity_status.py create mode 100644 seed/pydantic/allof/src/seed/api/core/__init__.py create mode 100644 seed/pydantic/allof/src/seed/api/core/datetime_utils.py create mode 100644 seed/pydantic/allof/src/seed/api/core/enum.py create mode 100644 seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py create mode 100644 seed/pydantic/allof/src/seed/api/core/serialization.py create mode 100644 seed/pydantic/allof/src/seed/api/describable.py create mode 100644 seed/pydantic/allof/src/seed/api/detailed_org.py create mode 100644 seed/pydantic/allof/src/seed/api/detailed_org_metadata.py create mode 100644 seed/pydantic/allof/src/seed/api/identifiable.py create mode 100644 seed/pydantic/allof/src/seed/api/organization.py create mode 100644 seed/pydantic/allof/src/seed/api/paginated_result.py create mode 100644 seed/pydantic/allof/src/seed/api/paging_cursors.py create mode 100644 seed/pydantic/allof/src/seed/api/py.typed create mode 100644 seed/pydantic/allof/src/seed/api/rule_execution_context.py create mode 100644 seed/pydantic/allof/src/seed/api/rule_response.py create mode 100644 seed/pydantic/allof/src/seed/api/rule_response_status.py create mode 100644 seed/pydantic/allof/src/seed/api/rule_type.py create mode 100644 seed/pydantic/allof/src/seed/api/rule_type_search_response.py create mode 100644 seed/pydantic/allof/src/seed/api/user.py create mode 100644 seed/pydantic/allof/src/seed/api/user_search_response.py create mode 100644 seed/pydantic/allof/tests/custom/test_client.py create mode 100644 seed/rust-model/allof-inline/.fern/metadata.json create mode 100644 seed/rust-model/allof-inline/snippet.json create mode 100644 seed/rust-model/allof-inline/src/audit_info.rs create mode 100644 seed/rust-model/allof-inline/src/base_org.rs create mode 100644 seed/rust-model/allof-inline/src/base_org_metadata.rs create mode 100644 seed/rust-model/allof-inline/src/combined_entity.rs create mode 100644 seed/rust-model/allof-inline/src/combined_entity_status.rs create mode 100644 seed/rust-model/allof-inline/src/describable.rs create mode 100644 seed/rust-model/allof-inline/src/detailed_org.rs create mode 100644 seed/rust-model/allof-inline/src/detailed_org_metadata.rs create mode 100644 seed/rust-model/allof-inline/src/error.rs create mode 100644 seed/rust-model/allof-inline/src/identifiable.rs create mode 100644 seed/rust-model/allof-inline/src/lib.rs create mode 100644 seed/rust-model/allof-inline/src/mod.rs create mode 100644 seed/rust-model/allof-inline/src/organization.rs create mode 100644 seed/rust-model/allof-inline/src/organization_metadata.rs create mode 100644 seed/rust-model/allof-inline/src/paginated_result.rs create mode 100644 seed/rust-model/allof-inline/src/paging_cursors.rs create mode 100644 seed/rust-model/allof-inline/src/rule_create_request.rs create mode 100644 seed/rust-model/allof-inline/src/rule_execution_context.rs create mode 100644 seed/rust-model/allof-inline/src/rule_response.rs create mode 100644 seed/rust-model/allof-inline/src/rule_response_status.rs create mode 100644 seed/rust-model/allof-inline/src/rule_type.rs create mode 100644 seed/rust-model/allof-inline/src/rule_type_search_response.rs create mode 100644 seed/rust-model/allof-inline/src/search_rule_types_query_request.rs create mode 100644 seed/rust-model/allof-inline/src/user.rs create mode 100644 seed/rust-model/allof-inline/src/user_search_response.rs create mode 100644 seed/rust-model/allof/.fern/metadata.json create mode 100644 seed/rust-model/allof/snippet.json create mode 100644 seed/rust-model/allof/src/audit_info.rs create mode 100644 seed/rust-model/allof/src/base_org.rs create mode 100644 seed/rust-model/allof/src/base_org_metadata.rs create mode 100644 seed/rust-model/allof/src/combined_entity.rs create mode 100644 seed/rust-model/allof/src/combined_entity_status.rs create mode 100644 seed/rust-model/allof/src/describable.rs create mode 100644 seed/rust-model/allof/src/detailed_org.rs create mode 100644 seed/rust-model/allof/src/detailed_org_metadata.rs create mode 100644 seed/rust-model/allof/src/error.rs create mode 100644 seed/rust-model/allof/src/identifiable.rs create mode 100644 seed/rust-model/allof/src/lib.rs create mode 100644 seed/rust-model/allof/src/mod.rs create mode 100644 seed/rust-model/allof/src/organization.rs create mode 100644 seed/rust-model/allof/src/paginated_result.rs create mode 100644 seed/rust-model/allof/src/paging_cursors.rs create mode 100644 seed/rust-model/allof/src/rule_create_request.rs create mode 100644 seed/rust-model/allof/src/rule_execution_context.rs create mode 100644 seed/rust-model/allof/src/rule_response.rs create mode 100644 seed/rust-model/allof/src/rule_response_status.rs create mode 100644 seed/rust-model/allof/src/rule_type.rs create mode 100644 seed/rust-model/allof/src/rule_type_search_response.rs create mode 100644 seed/rust-model/allof/src/search_rule_types_query_request.rs create mode 100644 seed/rust-model/allof/src/user.rs create mode 100644 seed/rust-model/allof/src/user_search_response.rs diff --git a/seed/csharp-model/allof-inline/.editorconfig b/seed/csharp-model/allof-inline/.editorconfig new file mode 100644 index 000000000000..1e7a0adbac80 --- /dev/null +++ b/seed/csharp-model/allof-inline/.editorconfig @@ -0,0 +1,35 @@ +root = true + +[*.cs] +resharper_arrange_object_creation_when_type_evident_highlighting = hint +resharper_auto_property_can_be_made_get_only_global_highlighting = hint +resharper_check_namespace_highlighting = hint +resharper_class_never_instantiated_global_highlighting = hint +resharper_class_never_instantiated_local_highlighting = hint +resharper_collection_never_updated_global_highlighting = hint +resharper_convert_type_check_pattern_to_null_check_highlighting = hint +resharper_inconsistent_naming_highlighting = hint +resharper_member_can_be_private_global_highlighting = hint +resharper_member_hides_static_from_outer_class_highlighting = hint +resharper_not_accessed_field_local_highlighting = hint +resharper_nullable_warning_suppression_is_used_highlighting = suggestion +resharper_partial_type_with_single_part_highlighting = hint +resharper_prefer_concrete_value_over_default_highlighting = none +resharper_private_field_can_be_converted_to_local_variable_highlighting = hint +resharper_property_can_be_made_init_only_global_highlighting = hint +resharper_property_can_be_made_init_only_local_highlighting = hint +resharper_redundant_name_qualifier_highlighting = none +resharper_redundant_using_directive_highlighting = hint +resharper_replace_slice_with_range_indexer_highlighting = none +resharper_unused_auto_property_accessor_global_highlighting = hint +resharper_unused_auto_property_accessor_local_highlighting = hint +resharper_unused_member_global_highlighting = hint +resharper_unused_type_global_highlighting = hint +resharper_use_string_interpolation_highlighting = hint +dotnet_diagnostic.CS1591.severity = suggestion + +[src/**/Types/*.cs] +resharper_check_namespace_highlighting = none + +[src/**/Core/Public/*.cs] +resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-model/allof-inline/.fern/metadata.json b/seed/csharp-model/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..bdf3cbf8c785 --- /dev/null +++ b/seed/csharp-model/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-csharp-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/csharp-model/allof-inline/.github/workflows/ci.yml b/seed/csharp-model/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..87068349b616 --- /dev/null +++ b/seed/csharp-model/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + DOTNET_NOLOGO: true + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Install tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore src/SeedApi/SeedApi.csproj + + - name: Build + run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release + + - name: Restore test dependencies + run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj + + - name: Build tests + run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release + + - name: Test + run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release + + - name: Pack + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release + + - name: Publish to NuGet.org + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" + diff --git a/seed/csharp-model/allof-inline/.gitignore b/seed/csharp-model/allof-inline/.gitignore new file mode 100644 index 000000000000..11014f2b33d7 --- /dev/null +++ b/seed/csharp-model/allof-inline/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +## This is based on `dotnet new gitignore` and customized by Fern + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +# [Rr]elease/ (Ignored by Fern) +# [Rr]eleases/ (Ignored by Fern) +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +# [Ll]og/ (Ignored by Fern) +# [Ll]ogs/ (Ignored by Fern) + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/seed/csharp-model/allof-inline/SeedApi.slnx b/seed/csharp-model/allof-inline/SeedApi.slnx new file mode 100644 index 000000000000..d4c63c241aad --- /dev/null +++ b/seed/csharp-model/allof-inline/SeedApi.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/seed/csharp-model/allof-inline/snippet.json b/seed/csharp-model/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs new file mode 100644 index 000000000000..a12183113312 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs @@ -0,0 +1,365 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class AdditionalPropertiesTests +{ + [Test] + public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); + Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); + }); + } + + [Test] + public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecord + { + Id = "1", + AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.Id, Is.EqualTo("1")); + Assert.That( + deserializedRecord.AdditionalProperties["category"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), + Is.EqualTo("fiction") + ); + Assert.That( + deserializedRecord.AdditionalProperties["title"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() + { + // Arrange + var extensionData = new Dictionary + { + ["key1"] = JsonUtils.SerializeToElement("value1"), + ["key2"] = JsonUtils.SerializeToElement(123), + }; + var readOnlyProps = new ReadOnlyAdditionalProperties(); + readOnlyProps.CopyFromExtensionData(extensionData); + + // Act & Assert + Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); + Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); + } + + [Test] + public void AdditionalProperties_ShouldBehaveAsDictionary() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + additionalProps["key3"] = true; + + // Assert + Assert.Multiple(() => + { + Assert.That(additionalProps["key1"], Is.EqualTo("value1")); + Assert.That(additionalProps["key2"], Is.EqualTo(123)); + Assert.That((bool)additionalProps["key3"]!, Is.True); + Assert.That(additionalProps.Count, Is.EqualTo(3)); + }); + } + + [Test] + public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + var jsonObject = additionalProps.ToJsonObject(); + + Assert.Multiple(() => + { + // Assert + Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); + Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); + }); + } + + [Test] + public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + var record = JsonUtils.Deserialize(json); + + // Act + record.AdditionalProperties["category"] = "non-fiction"; + + // Assert + Assert.Multiple(() => + { + Assert.That(record, Is.Not.Null); + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); + Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); + Assert.That( + ((JsonElement)record.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": 42, + "extra2": 99 + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithInts + { + AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": { "key1": true, "key2": false }, + "extra2": { "key3": true } + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithDictionaries + { + AdditionalProperties = + { + ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, + ["extra2"] = new Dictionary { { "key3", true } }, + }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + private record Record : IJsonOnDeserialized + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithInts : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithDictionaries : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties< + Dictionary + > AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties> AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs new file mode 100644 index 000000000000..c0f258680b78 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs @@ -0,0 +1,100 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateOnlyJsonTests +{ + [Test] + public void SerializeDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly? dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly? expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateOnlyKey() + { + var key = new DateOnly(2023, 10, 5); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateOnlyKey() + { + var json = """ + { + "2023-10-05": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateOnly(2023, 10, 5); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs new file mode 100644 index 000000000000..1dde45a8e939 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs @@ -0,0 +1,134 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateTimeJsonTests +{ + [Test] + public void SerializeDateTime_ShouldMatchExpectedFormat() + { + (DateTime dateTime, string expected)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + foreach (var (dateTime, expected) in testCases) + { + var json = JsonUtils.Serialize(dateTime); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateTime_ShouldMatchExpectedDateTime() + { + (DateTime expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateTime_ShouldMatchExpectedFormat() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateTimeKey() + { + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateTimeKey() + { + var json = """ + { + "2023-10-05T14:30:00.000Z": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs new file mode 100644 index 000000000000..969acd620998 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs @@ -0,0 +1,160 @@ +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class JsonAccessAttributeTests +{ + private class MyClass + { + [JsonPropertyName("read_only_prop")] + [JsonAccess(JsonAccessType.ReadOnly)] + public string? ReadOnlyProp { get; set; } + + [JsonPropertyName("write_only_prop")] + [JsonAccess(JsonAccessType.WriteOnly)] + public string? WriteOnlyProp { get; set; } + + [JsonPropertyName("normal_prop")] + public string? NormalProp { get; set; } + + [JsonPropertyName("read_only_nullable_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable? ReadOnlyNullableList { get; set; } + + [JsonPropertyName("read_only_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable ReadOnlyList { get; set; } = []; + + [JsonPropertyName("write_only_nullable_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable? WriteOnlyNullableList { get; set; } + + [JsonPropertyName("write_only_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable WriteOnlyList { get; set; } = []; + + [JsonPropertyName("normal_list")] + public IEnumerable NormalList { get; set; } = []; + + [JsonPropertyName("normal_nullable_list")] + public IEnumerable? NullableNormalList { get; set; } + } + + [Test] + public void JsonAccessAttribute_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "write_only_prop": "write", + "normal_prop": "normal_prop", + "read_only_nullable_list": ["item1", "item2"], + "read_only_list": ["item3", "item4"], + "write_only_nullable_list": ["item5", "item6"], + "write_only_list": ["item7", "item8"], + "normal_list": ["normal1", "normal2"], + "normal_nullable_list": ["normal1", "normal2"] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // String properties + Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); + Assert.That(obj.WriteOnlyProp, Is.Null); + Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); + + // List properties - read only + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Not.Null); + Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); + Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); + Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); + + var readOnlyList = obj.ReadOnlyList.ToArray(); + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Has.Length.EqualTo(2)); + Assert.That(readOnlyList[0], Is.EqualTo("item3")); + Assert.That(readOnlyList[1], Is.EqualTo("item4")); + + // List properties - write only + Assert.That(obj.WriteOnlyNullableList, Is.Null); + Assert.That(obj.WriteOnlyList, Is.Not.Null); + Assert.That(obj.WriteOnlyList, Is.Empty); + + // Normal list property + var normalList = obj.NormalList.ToArray(); + Assert.That(normalList, Is.Not.Null); + Assert.That(normalList, Has.Length.EqualTo(2)); + Assert.That(normalList[0], Is.EqualTo("normal1")); + Assert.That(normalList[1], Is.EqualTo("normal2")); + }); + + // Set up values for serialization + obj.WriteOnlyProp = "write"; + obj.NormalProp = "new_value"; + obj.WriteOnlyNullableList = new List { "write1", "write2" }; + obj.WriteOnlyList = new List { "write3", "write4" }; + obj.NormalList = new List { "new_normal" }; + obj.NullableNormalList = new List { "new_normal" }; + + var serializedJson = JsonUtils.Serialize(obj); + const string expectedJson = """ + { + "write_only_prop": "write", + "normal_prop": "new_value", + "write_only_nullable_list": [ + "write1", + "write2" + ], + "write_only_list": [ + "write3", + "write4" + ], + "normal_list": [ + "new_normal" + ], + "normal_nullable_list": [ + "new_normal" + ] + } + """; + Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); + } + + [Test] + public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "normal_prop": "normal_prop", + "read_only_nullable_list": null, + "read_only_list": [] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // Read-only nullable list should be null when JSON contains null + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Null); + + // Read-only non-nullable list should never be null, but empty when JSON contains null + var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Is.Empty); + }); + + // Serialize and verify read-only lists are not included + var serializedJson = JsonUtils.Serialize(obj); + Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); + Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); + Assert.That(serializedJson, Does.Not.Contain("read_only_list")); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs new file mode 100644 index 000000000000..42d165830baf --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs @@ -0,0 +1,314 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using OneOf; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class OneOfSerializerTests +{ + private class Foo + { + [JsonPropertyName("string_prop")] + public required string StringProp { get; set; } + } + + private class Bar + { + [JsonPropertyName("int_prop")] + public required int IntProp { get; set; } + } + + private static readonly OneOf OneOf1 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT2(new { }); + private const string OneOf1String = "{}"; + + private static readonly OneOf OneOf2 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT0("test"); + private const string OneOf2String = "\"test\""; + + private static readonly OneOf OneOf3 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT1(123); + private const string OneOf3String = "123"; + + private static readonly OneOf OneOf4 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT3(new Foo { StringProp = "test" }); + private const string OneOf4String = "{\"string_prop\": \"test\"}"; + + private static readonly OneOf OneOf5 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT4(new Bar { IntProp = 5 }); + private const string OneOf5String = "{\"int_prop\": 5}"; + + [Test] + public void Serialize_OneOfs_Should_Return_Expected_String() + { + (OneOf, string)[] testData = + [ + (OneOf1, OneOf1String), + (OneOf2, OneOf2String), + (OneOf3, OneOf3String), + (OneOf4, OneOf4String), + (OneOf5, OneOf5String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, expected) in testData) + { + var result = JsonUtils.Serialize(oneOf); + Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void OneOfs_Should_Deserialize_From_String() + { + (OneOf, string)[] testData = + [ + (OneOf1, OneOf1String), + (OneOf2, OneOf2String), + (OneOf3, OneOf3String), + (OneOf4, OneOf4String), + (OneOf5, OneOf5String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, json) in testData) + { + var result = JsonUtils.Deserialize>(json); + Assert.That(result.Index, Is.EqualTo(oneOf.Index)); + Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); + } + }); + } + + private static readonly OneOf? NullableOneOf1 = null; + private const string NullableOneOf1String = "null"; + + private static readonly OneOf? NullableOneOf2 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT4(new Bar { IntProp = 5 }); + private const string NullableOneOf2String = "{\"int_prop\": 5}"; + + [Test] + public void Serialize_NullableOneOfs_Should_Return_Expected_String() + { + (OneOf?, string)[] testData = + [ + (NullableOneOf1, NullableOneOf1String), + (NullableOneOf2, NullableOneOf2String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, expected) in testData) + { + var result = JsonUtils.Serialize(oneOf); + Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void NullableOneOfs_Should_Deserialize_From_String() + { + (OneOf?, string)[] testData = + [ + (NullableOneOf1, NullableOneOf1String), + (NullableOneOf2, NullableOneOf2String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, json) in testData) + { + var result = JsonUtils.Deserialize?>(json); + Assert.That(result?.Index, Is.EqualTo(oneOf?.Index)); + Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result?.Value)).IgnoreWhiteSpace); + } + }); + } + + private static readonly OneOf OneOfWithNullable1 = OneOf< + string, + int, + Foo? + >.FromT2(null); + private const string OneOfWithNullable1String = "null"; + + private static readonly OneOf OneOfWithNullable2 = OneOf< + string, + int, + Foo? + >.FromT2(new Foo { StringProp = "test" }); + private const string OneOfWithNullable2String = "{\"string_prop\": \"test\"}"; + + private static readonly OneOf OneOfWithNullable3 = OneOf< + string, + int, + Foo? + >.FromT0("test"); + private const string OneOfWithNullable3String = "\"test\""; + + [Test] + public void Serialize_OneOfWithNullables_Should_Return_Expected_String() + { + (OneOf, string)[] testData = + [ + (OneOfWithNullable1, OneOfWithNullable1String), + (OneOfWithNullable2, OneOfWithNullable2String), + (OneOfWithNullable3, OneOfWithNullable3String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, expected) in testData) + { + var result = JsonUtils.Serialize(oneOf); + Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void OneOfWithNullables_Should_Deserialize_From_String() + { + (OneOf, string)[] testData = + [ + // (OneOfWithNullable1, OneOfWithNullable1String), // not possible with .NET's JSON serializer + (OneOfWithNullable2, OneOfWithNullable2String), + (OneOfWithNullable3, OneOfWithNullable3String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, json) in testData) + { + var result = JsonUtils.Deserialize>(json); + Assert.That(result.Index, Is.EqualTo(oneOf.Index)); + Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void Serialize_OneOfWithObjectLast_Should_Return_Expected_String() + { + var oneOfWithObjectLast = OneOf.FromT4( + new { random = "data" } + ); + const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; + + var result = JsonUtils.Serialize(oneOfWithObjectLast); + Assert.That(result, Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace); + } + + [Test] + public void OneOfWithObjectLast_Should_Deserialize_From_String() + { + const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; + var result = JsonUtils.Deserialize>( + oneOfWithObjectLastString + ); + Assert.Multiple(() => + { + Assert.That(result.Index, Is.EqualTo(4)); + Assert.That(result.Value, Is.InstanceOf()); + Assert.That( + JsonUtils.Serialize(result.Value), + Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace + ); + }); + } + + [Test] + public void Serialize_OneOfWithObjectNotLast_Should_Return_Expected_String() + { + var oneOfWithObjectNotLast = OneOf.FromT1( + new { random = "data" } + ); + const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; + + var result = JsonUtils.Serialize(oneOfWithObjectNotLast); + Assert.That(result, Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace); + } + + [Test] + public void OneOfWithObjectNotLast_Should_Deserialize_From_String() + { + const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; + var result = JsonUtils.Deserialize>( + oneOfWithObjectNotLastString + ); + Assert.Multiple(() => + { + Assert.That(result.Index, Is.EqualTo(1)); + Assert.That(result.Value, Is.InstanceOf()); + Assert.That( + JsonUtils.Serialize(result.Value), + Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace + ); + }); + } + + [Test] + public void Serialize_OneOfSingleType_Should_Return_Expected_String() + { + var oneOfSingle = OneOf.FromT0("single"); + const string oneOfSingleString = "\"single\""; + + var result = JsonUtils.Serialize(oneOfSingle); + Assert.That(result, Is.EqualTo(oneOfSingleString).IgnoreWhiteSpace); + } + + [Test] + public void OneOfSingleType_Should_Deserialize_From_String() + { + const string oneOfSingleString = "\"single\""; + var result = JsonUtils.Deserialize>(oneOfSingleString); + Assert.Multiple(() => + { + Assert.That(result.Index, Is.EqualTo(0)); + Assert.That(result.Value, Is.EqualTo("single")); + }); + } + + [Test] + public void Deserialize_InvalidData_Should_Throw_Exception() + { + const string invalidJson = "{\"invalid\": \"data\"}"; + + Assert.Throws(() => + { + JsonUtils.Deserialize>(invalidJson); + }); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props new file mode 100644 index 000000000000..aac9b5020d80 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props @@ -0,0 +1,6 @@ + + diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj new file mode 100644 index 000000000000..77e1a9943739 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj @@ -0,0 +1,36 @@ + + + net9.0 + 12 + enable + enable + false + true + true + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs new file mode 100644 index 000000000000..3ac7e5310f95 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs @@ -0,0 +1,219 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; +using SeedApi; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle AdditionalProperties values. +/// +public static class AdditionalPropertiesComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their + /// serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) + { + constraint.Using( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + /// + /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing + /// their serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) + { + constraint.Using>( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => + JsonElementsAreEqual(x, y); + + private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) + { + if (x.ValueKind != y.ValueKind) + { + return false; + } + + return x.ValueKind switch + { + JsonValueKind.Object => CompareJsonObjects(x, y), + JsonValueKind.Array => CompareJsonArrays(x, y), + JsonValueKind.String => x.GetString() == y.GetString(), + JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), + JsonValueKind.True => true, + JsonValueKind.False => true, + JsonValueKind.Null => true, + _ => false, + }; + } + + private static bool CompareJsonObjects(JsonElement x, JsonElement y) + { + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + if (xProps.Count != yProps.Count) + { + return false; + } + + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + return false; + } + + if (!JsonElementsAreEqual(xProps[key], yProps[key])) + { + return false; + } + } + + return true; + } + + private static bool CompareJsonArrays(JsonElement x, JsonElement y) + { + var xArray = x.EnumerateArray().ToList(); + var yArray = y.EnumerateArray().ToList(); + + if (xArray.Count != yArray.Count) + { + return false; + } + + for (var i = 0; i < xArray.Count; i++) + { + if (!JsonElementsAreEqual(xArray[i], yArray[i])) + { + return false; + } + } + + return true; + } + + /// + /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. + /// When UsingPropertiesComparer() walks object properties and encounters a property typed as + /// 'object', the expected side may be a Dictionary<object, object?> while the actual + /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing + /// the non-JsonElement side and comparing JSON representations. + /// + /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic + /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only + /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all + /// same-type comparisons normally. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) + { + // Handle: expected is non-JsonElement, actual is JsonElement + constraint.Using( + (actualJsonElement, expectedObj) => + { + try + { + var expectedElement = JsonUtils.SerializeToElement(expectedObj); + return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); + } + catch + { + return false; + } + } + ); + // Handle reverse: expected is JsonElement, actual is non-JsonElement + constraint.Using( + (actualObj, expectedJsonElement) => + { + try + { + var actualElement = JsonUtils.SerializeToElement(actualObj); + return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); + } + catch + { + return false; + } + } + ); + return constraint; + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs new file mode 100644 index 000000000000..3f4b5eb602b2 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs @@ -0,0 +1,29 @@ +using global::System.Text.Json; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Utils; + +internal static class JsonAssert +{ + /// + /// Asserts that the serialized JSON of an object equals the expected JSON string. + /// Uses JsonElement comparison for reliable deep equality of collections and union types. + /// + internal static void AreEqual(object actual, string expectedJson) + { + var actualElement = JsonUtils.SerializeToElement(actual); + var expectedElement = JsonUtils.Deserialize(expectedJson); + Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); + } + + /// + /// Asserts that the given JSON string survives a deserialization/serialization round-trip + /// intact: deserializes to T then re-serializes and compares to the original JSON. + /// + internal static void Roundtrips(string json) + { + var deserialized = JsonUtils.Deserialize(json); + AreEqual(deserialized!, json); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs new file mode 100644 index 000000000000..a37ef402c1ac --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs @@ -0,0 +1,236 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle JsonElement objects. +/// +public static class JsonElementComparerExtensions +{ + /// + /// Extension method for comparing JsonElement objects in NUnit tests. + /// Property order doesn't matter, but array order does matter. + /// Includes special handling for DateTime string formats. + /// + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare JsonElements with detailed diffs. + public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) + { + return constraint.Using(new JsonElementComparer()); + } +} + +/// +/// Equality comparer for JsonElement with detailed reporting. +/// Property order doesn't matter, but array order does matter. +/// Now includes special handling for DateTime string formats with improved null handling. +/// +public class JsonElementComparer : IEqualityComparer +{ + private string _failurePath = string.Empty; + + /// + public bool Equals(JsonElement x, JsonElement y) + { + _failurePath = string.Empty; + return CompareJsonElements(x, y, string.Empty); + } + + /// + public int GetHashCode(JsonElement obj) + { + return JsonSerializer.Serialize(obj).GetHashCode(); + } + + private bool CompareJsonElements(JsonElement x, JsonElement y, string path) + { + // If value kinds don't match, they're not equivalent + if (x.ValueKind != y.ValueKind) + { + _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; + return false; + } + + switch (x.ValueKind) + { + case JsonValueKind.Object: + return CompareJsonObjects(x, y, path); + + case JsonValueKind.Array: + return CompareJsonArraysInOrder(x, y, path); + + case JsonValueKind.String: + string? xStr = x.GetString(); + string? yStr = y.GetString(); + + // Handle null strings + if (xStr is null && yStr is null) + return true; + + if (xStr is null || yStr is null) + { + _failurePath = + $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; + return false; + } + + // Check if they are identical strings + if (xStr == yStr) + return true; + + // Try to handle DateTime strings + if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) + { + if (AreEquivalentDateTimeStrings(xStr, yStr)) + return true; + } + + _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; + return false; + + case JsonValueKind.Number: + if (x.GetDecimal() != y.GetDecimal()) + { + _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; + return false; + } + + return true; + + case JsonValueKind.True: + case JsonValueKind.False: + if (x.GetBoolean() != y.GetBoolean()) + { + _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; + return false; + } + + return true; + + case JsonValueKind.Null: + return true; + + default: + _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; + return false; + } + } + + private bool IsLikelyDateTimeString(string? str) + { + // Simple heuristic to identify likely ISO date time strings + return str is not null + && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); + } + + private bool AreEquivalentDateTimeStrings(string str1, string str2) + { + // Try to parse both as DateTime + if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) + { + return dt1 == dt2; + } + + return false; + } + + private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) + { + // Create dictionaries for both JSON objects + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + // Check if all properties in x exist in y + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + _failurePath = $"{path}: Missing property '{key}'"; + return false; + } + } + + // Check if y has extra properties + foreach (var key in yProps.Keys) + { + if (!xProps.ContainsKey(key)) + { + _failurePath = $"{path}: Unexpected property '{key}'"; + return false; + } + } + + // Compare each property value + foreach (var key in xProps.Keys) + { + var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; + if (!CompareJsonElements(xProps[key], yProps[key], propPath)) + { + return false; + } + } + + return true; + } + + private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) + { + var xArray = x.EnumerateArray(); + var yArray = y.EnumerateArray(); + + // Count x elements + var xCount = 0; + var xElements = new List(); + foreach (var item in xArray) + { + xElements.Add(item); + xCount++; + } + + // Count y elements + var yCount = 0; + var yElements = new List(); + foreach (var item in yArray) + { + yElements.Add(item); + yCount++; + } + + // Check if counts match + if (xCount != yCount) + { + _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; + return false; + } + + // Compare elements in order + for (var i = 0; i < xCount; i++) + { + var itemPath = $"{path}[{i}]"; + if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) + { + return false; + } + } + + return true; + } + + /// + public override string ToString() + { + if (!string.IsNullOrEmpty(_failurePath)) + { + return $"JSON comparison failed at {_failurePath}"; + } + + return "JsonElementEqualityComparer"; + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs new file mode 100644 index 000000000000..816f4c010e6e --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs @@ -0,0 +1,32 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class NUnitExtensions +{ + /// + /// Modifies the EqualConstraint to use our own set of default comparers. + /// + /// + /// + public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => + constraint + .UsingPropertiesComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingOneOfComparer() + .UsingJsonElementComparer() + .UsingOptionalComparer() + .UsingObjectDictionaryComparer() + .UsingAdditionalPropertiesComparer() + .UsingJsonSerializationComparer(); +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs new file mode 100644 index 000000000000..767439174363 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs @@ -0,0 +1,86 @@ +using NUnit.Framework.Constraints; +using OneOf; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle OneOf values. +/// +public static class EqualConstraintExtensions +{ + /// + /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOneOf types + constraint.Using( + (x, y) => + { + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (x.Value is null && y.Value is null) + { + return true; + } + + if (x.Value is null) + { + return false; + } + + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs new file mode 100644 index 000000000000..98bfcac477b8 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs @@ -0,0 +1,104 @@ +using NUnit.Framework.Constraints; +using OneOf; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle Optional values. +/// +public static class OptionalComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOptional types + constraint.Using( + (x, y) => + { + // Both must have the same IsDefined state + if (x.IsDefined != y.IsDefined) + { + return false; + } + + // If both are undefined, they're equal + if (!x.IsDefined) + { + return true; + } + + // Both are defined, compare their boxed values + var xValue = x.GetBoxedValue(); + var yValue = y.GetBoxedValue(); + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (xValue is null && yValue is null) + { + return true; + } + + if (xValue is null || yValue is null) + { + return false; + } + + // Use NUnit's property comparer for the inner values + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values within Optional types. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs new file mode 100644 index 000000000000..fc0b595a5e54 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs @@ -0,0 +1,87 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class ReadOnlyMemoryComparerExtensions +{ + /// + /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. + /// + /// The type of elements in the ReadOnlyMemory. + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare ReadOnlyMemory<T>. + public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) + where T : IComparable + { + return constraint.Using(new ReadOnlyMemoryComparer()); + } +} + +/// +/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. +/// +/// +/// The type of elements in the ReadOnlyMemory. +/// +public class ReadOnlyMemoryComparer : IComparer> + where T : IComparable +{ + /// + public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) + { + // Check if sequences are equal + var xSpan = x.Span; + var ySpan = y.Span; + + // Optimized case for IEquatable implementations + if (typeof(IEquatable).IsAssignableFrom(typeof(T))) + { + var areEqual = xSpan.SequenceEqual(ySpan); + if (areEqual) + { + return 0; // Sequences are equal + } + } + else + { + // Manual equality check for non-IEquatable types + if (xSpan.Length == ySpan.Length) + { + var areEqual = true; + for (var i = 0; i < xSpan.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + areEqual = false; + break; + } + } + + if (areEqual) + { + return 0; // Sequences are equal + } + } + } + + // For non-equal sequences, we need to return a consistent ordering + // First compare lengths + if (x.Length != y.Length) + return x.Length.CompareTo(y.Length); + + // Same length but different content - compare first differing element + for (var i = 0; i < x.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + return xSpan[i].CompareTo(ySpan[i]); + } + } + + // Should never reach here if not equal + return 0; + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs b/seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs new file mode 100644 index 000000000000..c0d843281678 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs @@ -0,0 +1,56 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +/// +/// Common audit metadata. +/// +[Serializable] +public record AuditInfo : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs b/seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs new file mode 100644 index 000000000000..eb944d0030da --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public BaseOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs b/seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs new file mode 100644 index 000000000000..fcb0efec5fea --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from BaseOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Subscription tier. + /// + [JsonPropertyName("tier")] + public string? Tier { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs b/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs new file mode 100644 index 000000000000..633fb725fb81 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs @@ -0,0 +1,46 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record CombinedEntity : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Describable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonPropertyName("status")] + public required CombinedEntityStatus Status { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs b/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs new file mode 100644 index 000000000000..0ab2467f6bd7 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs @@ -0,0 +1,115 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] +[Serializable] +public readonly record struct CombinedEntityStatus : IStringEnum +{ + public static readonly CombinedEntityStatus Active = new(Values.Active); + + public static readonly CombinedEntityStatus Archived = new(Values.Archived); + + public CombinedEntityStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static CombinedEntityStatus FromCustom(string value) + { + return new CombinedEntityStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(CombinedEntityStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(CombinedEntityStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(CombinedEntityStatus value) => value.Value; + + public static explicit operator CombinedEntityStatus(string value) => new(value); + + internal class CombinedEntityStatusSerializer : JsonConverter + { + public override CombinedEntityStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override CombinedEntityStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Archived = "archived"; + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs new file mode 100644 index 000000000000..b684f33d750e --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +internal class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter, new() +{ + private static readonly TConverterType _converter = new TConverterType(); + + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new global::System.Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs new file mode 100644 index 000000000000..ccf4e963cc89 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace SeedApi.Core; + +internal static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; + public const string DateFormat = "yyyy-MM-dd"; +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs new file mode 100644 index 000000000000..af61cc061ae5 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs @@ -0,0 +1,747 @@ +// ReSharper disable All +#pragma warning disable + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using global::System.Diagnostics; +using global::System.Diagnostics.CodeAnalysis; +using global::System.Globalization; +using global::System.Runtime.CompilerServices; +using global::System.Runtime.InteropServices; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +// ReSharper disable SuggestVarOrType_SimpleTypes +// ReSharper disable SuggestVarOrType_BuiltInTypes + +namespace SeedApi.Core +{ + /// + /// Custom converter for handling the data type with the System.Text.Json library. + /// + /// + /// This class backported from: + /// + /// System.Text.Json.Serialization.Converters.DateOnlyConverter + /// + public sealed class DateOnlyConverter : JsonConverter + { + private const int FormatLength = 10; // YYYY-MM-DD + + private const int MaxEscapedFormatLength = + FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; + + /// + public override DateOnly Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType != JsonTokenType.String) + { + ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); + } + + return ReadCore(ref reader); + } + + /// + public override DateOnly ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + return ReadCore(ref reader); + } + + private static DateOnly ReadCore(ref Utf8JsonReader reader) + { + if ( + !JsonHelpers.IsInRangeInclusive( + reader.ValueLength(), + FormatLength, + MaxEscapedFormatLength + ) + ) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + scoped ReadOnlySpan source; + if (!reader.HasValueSequence && !reader.ValueIsEscaped) + { + source = reader.ValueSpan; + } + else + { + Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; + int bytesWritten = reader.CopyString(stackSpan); + source = stackSpan.Slice(0, bytesWritten); + } + + if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + return value; + } + + /// + public override void Write( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WriteStringValue(buffer); + } + + /// + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WritePropertyName(buffer); + } + } + + internal static class JsonConstants + { + // The maximum number of fraction digits the Json DateTime parser allows + public const int DateTimeParseNumFractionDigits = 16; + + // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. + public const int MaxExpansionFactorWhileEscaping = 6; + + // The largest fraction expressible by TimeSpan and DateTime formats + public const int MaxDateTimeFraction = 9_999_999; + + // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. + public const int DateTimeNumFractionDigits = 7; + + public const byte UtcOffsetToken = (byte)'Z'; + + public const byte TimePrefix = (byte)'T'; + + public const byte Period = (byte)'.'; + + public const byte Hyphen = (byte)'-'; + + public const byte Colon = (byte)':'; + + public const byte Plus = (byte)'+'; + } + + // ReSharper disable SuggestVarOrType_Elsewhere + // ReSharper disable SuggestVarOrType_SimpleTypes + // ReSharper disable SuggestVarOrType_BuiltInTypes + + internal static class JsonHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => + (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); + + public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; + + [StructLayout(LayoutKind.Auto)] + private struct DateTimeParseData + { + public int Year; + public int Month; + public int Day; + public bool IsCalendarDateOnly; + public int Hour; + public int Minute; + public int Second; + public int Fraction; // This value should never be greater than 9_999_999. + public int OffsetHours; + public int OffsetMinutes; + + // ReSharper disable once NotAccessedField.Local + public byte OffsetToken; + } + + public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) + { + if ( + TryParseDateTimeOffset(source, out DateTimeParseData parseData) + && parseData.IsCalendarDateOnly + && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) + ) + { + value = DateOnly.FromDateTime(dateTime); + return true; + } + + value = default; + return false; + } + + /// + /// ISO 8601 date time parser (ISO 8601-1:2019). + /// + /// The date/time to parse in UTF-8 format. + /// The parsed for the given . + /// + /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day + /// representations with optional specification of seconds and fractional seconds. + /// + /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). + /// If unspecified they are considered to be local per spec. + /// + /// Examples: (TZD is either "Z" or hh:mm offset from UTC) + /// + /// YYYY-MM-DD (e.g. 1997-07-16) + /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) + /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) + /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) + /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) + /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) + /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) + /// + /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). + /// The extended variants have separator characters between components ('-', ':', '.', etc.). + /// Spaces are not permitted. + /// + /// "true" if successfully parsed. + private static bool TryParseDateTimeOffset( + ReadOnlySpan source, + out DateTimeParseData parseData + ) + { + parseData = default; + + // too short datetime + Debug.Assert(source.Length >= 10); + + // Parse the calendar date + // ----------------------- + // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" + // [dateX] = [year]["-"][month]["-"][day] + // [year] = [YYYY] [0000 - 9999] (4.3.2) + // [month] = [MM] [01 - 12] (4.3.3) + // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) + // + // Note: 5.2.2.2 "Representations with reduced precision" allows for + // just [year]["-"][month] (a) and just [year] (b), but we currently + // don't permit it. + + { + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + uint digit3 = source[2] - (uint)'0'; + uint digit4 = source[3] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) + { + return false; + } + + parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); + } + + if ( + source[4] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) + || source[7] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) + ) + { + return false; + } + + // We now have YYYY-MM-DD [dateX] + // ReSharper disable once ConvertIfStatementToSwitchStatement + if (source.Length == 10) + { + parseData.IsCalendarDateOnly = true; + return true; + } + + // Parse the time of day + // --------------------- + // + // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" + // [timeX] = ["T"][hour][":"][min][":"][sec] + // [hour] = [hh] [00 - 23] (4.3.8a) + // [minute] = [mm] [00 - 59] (4.3.9a) + // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) + // + // ISO 8601-1:2019 5.3.3 "UTC of day" + // [timeX]["Z"] + // + // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between + // local timescale and UTC" (Extended format) + // + // [shiftX] = ["+"|"-"][hour][":"][min] + // + // Notes: + // + // "T" is optional per spec, but _only_ when times are used alone. In our + // case, we're reading out a complete date & time and as such require "T". + // (5.4.2.1b). + // + // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations + // with reduced precision". 5.3.1.3b allows just specifying the hour, but + // we currently don't permit this. + // + // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). + // We only allow fractions for seconds currently. Lower order components + // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be + // one digit, but the max number of digits is implementation defined. We + // currently allow up to 16 digits of fractional seconds only. While we + // support 16 fractional digits we only parse the first seven, anything + // past that is considered a zero. This is to stay compatible with the + // DateTime implementation which is limited to this resolution. + + if (source.Length < 16) + { + // Source does not have enough characters for YYYY-MM-DDThh:mm + return false; + } + + // Parse THH:MM (e.g. "T10:32") + if ( + source[10] != JsonConstants.TimePrefix + || source[13] != JsonConstants.Colon + || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) + || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm + Debug.Assert(source.Length >= 16); + if (source.Length == 16) + { + return true; + } + + byte curByte = source[16]; + int sourceIndex = 17; + + // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Colon: + break; + default: + return false; + } + + // Try reading the seconds + if ( + source.Length < 19 + || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss + Debug.Assert(source.Length >= 19); + if (source.Length == 19) + { + return true; + } + + curByte = source[19]; + sourceIndex = 20; + + // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Period: + break; + default: + return false; + } + + // Source does not have enough characters for second fractions (i.e. ".s") + // YYYY-MM-DDThh:mm:ss.s + if (source.Length < 21) + { + return false; + } + + // Parse fraction. This value should never be greater than 9_999_999 + int numDigitsRead = 0; + int fractionEnd = Math.Min( + sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, + source.Length + ); + + while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) + { + if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); + numDigitsRead++; + } + + sourceIndex++; + } + + if (parseData.Fraction != 0) + { + while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction *= 10; + numDigitsRead++; + } + } + + // We now have YYYY-MM-DDThh:mm:ss.s + Debug.Assert(sourceIndex <= source.Length); + if (sourceIndex == source.Length) + { + return true; + } + + curByte = source[sourceIndex++]; + + // TZD ['Z'|'+'|'-'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + default: + return false; + } + + static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) + { + // Parse the hours for the offset + if ( + offsetData.Length < 2 + || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss.s+|-hh + + if (offsetData.Length == 2) + { + // Just hours offset specified + return true; + } + + // Ensure we have enough for ":mm" + return offsetData.Length == 5 + && offsetData[2] == JsonConstants.Colon + && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantAssignment + private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) + { + Debug.Assert(source.Length == 2); + + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9) + { + value = 0; + return false; + } + + value = (int)(digit1 * 10 + digit2); + return true; + } + + // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs + + /// + /// Overflow-safe DateTime factory. + /// + private static bool TryCreateDateTime( + DateTimeParseData parseData, + DateTimeKind kind, + out DateTime value + ) + { + if (parseData.Year == 0) + { + value = default; + return false; + } + + Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. + + if ((uint)parseData.Month - 1 >= 12) + { + value = default; + return false; + } + + uint dayMinusOne = (uint)parseData.Day - 1; + if ( + dayMinusOne >= 28 + && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) + ) + { + value = default; + return false; + } + + if ((uint)parseData.Hour > 23) + { + value = default; + return false; + } + + if ((uint)parseData.Minute > 59) + { + value = default; + return false; + } + + // This needs to allow leap seconds when appropriate. + // See https://github.com/dotnet/runtime/issues/30135. + if ((uint)parseData.Second > 59) + { + value = default; + return false; + } + + Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. + + ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) + ? DaysToMonth366 + : DaysToMonth365; + int yearMinusOne = parseData.Year - 1; + int totalDays = + yearMinusOne * 365 + + yearMinusOne / 4 + - yearMinusOne / 100 + + yearMinusOne / 400 + + days[parseData.Month - 1] + + parseData.Day + - 1; + long ticks = totalDays * TimeSpan.TicksPerDay; + int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; + ticks += totalSeconds * TimeSpan.TicksPerSecond; + ticks += parseData.Fraction; + value = new DateTime(ticks: ticks, kind: kind); + return true; + } + + private static ReadOnlySpan DaysToMonth365 => + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + private static ReadOnlySpan DaysToMonth366 => + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + } + + internal static class ThrowHelper + { + private const string ExceptionSourceValueToRethrowAsJsonException = + "System.Text.Json.Rethrowable"; + + [DoesNotReturn] + public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) + { + throw GetInvalidOperationException("string", tokenType); + } + + public static void ThrowFormatException(DataType dataType) + { + throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + + private static global::System.Exception GetInvalidOperationException( + string message, + JsonTokenType tokenType + ) + { + return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); + } + + private static InvalidOperationException GetInvalidOperationException(string message) + { + return new InvalidOperationException(message) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + } + + internal static class Utf8JsonReaderExtensions + { + internal static int ValueLength(this Utf8JsonReader reader) => + reader.HasValueSequence + ? checked((int)reader.ValueSequence.Length) + : reader.ValueSpan.Length; + } + + internal enum DataType + { + TimeOnly, + DateOnly, + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal static class SR + { + private static readonly bool s_usingResourceKeys = + AppContext.TryGetSwitch( + "System.Resources.UseSystemResourceKeys", + out bool usingResourceKeys + ) && usingResourceKeys; + + public static string UnsupportedFormat => Strings.UnsupportedFormat; + + public static string InvalidCast => Strings.InvalidCast; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1) + : string.Format(resourceFormat, p1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1, object? p2) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1, p2) + : string.Format(resourceFormat, p1, p2); + } + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( + "System.Resources.Tools.StronglyTypedResourceBuilder", + "17.0.0.0" + )] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings + { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode" + )] + internal Strings() { } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager( + "System.Text.Json.Resources.Strings", + typeof(Strings).Assembly + ); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Globalization.CultureInfo Culture + { + get { return resourceCulture; } + set { resourceCulture = value; } + } + + /// + /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. + /// + internal static string InvalidCast + { + get { return ResourceManager.GetString("InvalidCast", resourceCulture); } + } + + /// + /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. + /// + internal static string UnsupportedFormat + { + get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } + } + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs new file mode 100644 index 000000000000..d7dedc7f165b --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs @@ -0,0 +1,40 @@ +using global::System.Globalization; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +internal class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } + + public override DateTime ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateTime value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs new file mode 100644 index 000000000000..93dcc6dd6bca --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs @@ -0,0 +1,15 @@ +namespace SeedApi.Core; + +[global::System.AttributeUsage( + global::System.AttributeTargets.Property | global::System.AttributeTargets.Field +)] +internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute +{ + internal JsonAccessType AccessType { get; init; } = accessType; +} + +internal enum JsonAccessType +{ + ReadOnly, + WriteOnly, +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs new file mode 100644 index 000000000000..2fa8cfb6ad8c --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs @@ -0,0 +1,275 @@ +using global::System.Reflection; +using global::System.Text.Encodings.Web; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using global::System.Text.Json.Serialization; +using global::System.Text.Json.Serialization.Metadata; + +namespace SeedApi.Core; + +internal static partial class JsonOptions +{ + internal static readonly JsonSerializerOptions JsonSerializerOptions; + internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; + + static JsonOptions() + { + var options = new JsonSerializerOptions + { + Converters = + { + new DateTimeSerializer(), +#if USE_PORTABLE_DATE_ONLY + new DateOnlyConverter(), +#endif + new OneOfSerializer(), + new OptionalJsonConverterFactory(), + }, +#if DEBUG + WriteIndented = true, +#endif + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + NullableOptionalModifier, + JsonAccessAndIgnoreModifier, + HandleExtensionDataFields, + }, + }, + }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; + + var relaxedOptions = new JsonSerializerOptions(options) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + JsonSerializerOptionsRelaxedEscaping = relaxedOptions; + } + + private static void NullableOptionalModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var property in typeInfo.Properties) + { + var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; + + if (propertyInfo is null) + continue; + + // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior + var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); + if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) + { + // ReadOnly means "never serialize", which completely overrides Optional/Nullable. + // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier + // will set ShouldSerialize = false anyway. + continue; + } + // Note: WriteOnly doesn't conflict with Optional/Nullable since it only + // affects deserialization (Set), not serialization (ShouldSerialize) + + var isOptionalType = + property.PropertyType.IsGenericType + && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); + + var hasOptionalAttribute = + propertyInfo.GetCustomAttribute() is not null; + var hasNullableAttribute = + propertyInfo.GetCustomAttribute() is not null; + + if (isOptionalType && hasOptionalAttribute) + { + var originalGetter = property.Get; + if (originalGetter is not null) + { + var capturedIsNullable = hasNullableAttribute; + + property.ShouldSerialize = (obj, value) => + { + var optionalValue = originalGetter(obj); + if (optionalValue is not IOptional optional) + return false; + + if (!optional.IsDefined) + return false; + + if (!capturedIsNullable) + { + var innerValue = optional.GetBoxedValue(); + if (innerValue is null) + return false; + } + + return true; + }; + } + } + else if (hasNullableAttribute) + { + // Force serialization of nullable properties even when null + property.ShouldSerialize = (obj, value) => true; + } + } + } + + private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var propertyInfo in typeInfo.Properties) + { + var jsonAccessAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonAccessAttribute is not null) + { + propertyInfo.IsRequired = false; + switch (jsonAccessAttribute.AccessType) + { + case JsonAccessType.ReadOnly: + propertyInfo.ShouldSerialize = (_, _) => false; + break; + case JsonAccessType.WriteOnly: + propertyInfo.Set = null; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + var jsonIgnoreAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonIgnoreAttribute is not null) + { + propertyInfo.IsRequired = false; + } + } + } + + private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) + { + if ( + typeInfo.Kind == JsonTypeInfoKind.Object + && typeInfo.Properties.All(prop => !prop.IsExtensionData) + ) + { + var extensionProp = typeInfo + .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) + .FirstOrDefault(prop => + prop.GetCustomAttribute() is not null + ); + + if (extensionProp is not null) + { + var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( + extensionProp.FieldType, + extensionProp.Name + ); + jsonPropertyInfo.Get = extensionProp.GetValue; + jsonPropertyInfo.Set = extensionProp.SetValue; + jsonPropertyInfo.IsExtensionData = true; + typeInfo.Properties.Add(jsonPropertyInfo); + } + } + } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); +} + +internal static class JsonUtils +{ + internal static string Serialize(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + + internal static string Serialize(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); + + internal static string SerializeRelaxedEscaping(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static JsonElement SerializeToElement(T obj) => + JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonElement SerializeToElement(object obj, global::System.Type type) => + JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); + + internal static JsonDocument SerializeToDocument(T obj) => + JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonNode? SerializeToNode(T obj) => + JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); + + internal static byte[] SerializeToUtf8Bytes(T obj) => + JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); + + internal static string SerializeWithAdditionalProperties( + T obj, + object? additionalProperties = null + ) + { + if (additionalProperties is null) + { + return Serialize(obj); + } + var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); + if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) + { + throw new InvalidOperationException( + "The additional properties must serialize to a JSON object." + ); + } + var jsonNode = SerializeToNode(obj); + if (jsonNode is not JsonObject jsonObject) + { + throw new InvalidOperationException( + "The serialized object must be a JSON object to add properties." + ); + } + MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); + return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); + } + + private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) + { + foreach (var property in overrideObject) + { + if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) + { + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + continue; + } + if ( + existingValue is JsonObject nestedBaseObject + && property.Value is JsonObject nestedOverrideObject + ) + { + // If both values are objects, recursively merge them. + MergeJsonObjects(nestedBaseObject, nestedOverrideObject); + continue; + } + // Otherwise, the overrideObject takes precedence. + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + } + } + + internal static T Deserialize(string json) => + JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs new file mode 100644 index 000000000000..a1d30328bf9a --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs @@ -0,0 +1,18 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as nullable in the OpenAPI specification. +/// When applied to Optional properties, this indicates that null values should be +/// written to JSON when the optional is defined with null. +/// +/// +/// For regular (required) properties: +/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) +/// - With [Nullable]: null values are written to JSON +/// +/// For Optional properties (also marked with [Optional]): +/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) +/// - With [Nullable]: Optional.Of(null) → write null to JSON +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs new file mode 100644 index 000000000000..6eeb68fcba46 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs @@ -0,0 +1,145 @@ +using global::System.Reflection; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using OneOf; + +namespace SeedApi.Core; + +internal class OneOfSerializer : JsonConverter +{ + public override IOneOf? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (IOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + public override IOneOf ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = reader.GetString(); + if (stringValue == null) + throw new JsonException("Cannot deserialize null property name into OneOf type"); + + // Try to deserialize the string value into one of the supported types + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + // For primitive types, try direct conversion + if (type == typeof(string)) + { + return (IOneOf)cast.Invoke(null, [stringValue])!; + } + + // For other types, try to deserialize from JSON string + var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); + if (result != null) + { + return (IOneOf)cast.Invoke(null, [result])!; + } + } + catch { } + } + + // If no type-specific deserialization worked, default to string if available + var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); + if (stringType != default) + { + return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; + } + + throw new JsonException( + $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" + ); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + IOneOf value, + JsonSerializerOptions options + ) + { + // Serialize the underlying value to a string suitable for use as a dictionary key + var stringValue = value.Value?.ToString() ?? "null"; + writer.WritePropertyName(stringValue); + } + + private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( + global::System.Type typeToConvert + ) + { + var type = typeToConvert; + if (Nullable.GetUnderlyingType(type) is { } underlyingType) + { + type = underlyingType; + } + + var casts = type.GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + while (type is not null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + var genericArguments = type.GetGenericArguments(); + if (genericArguments.Length == 1) + { + return [(genericArguments[0], casts[0])]; + } + + // if object type is present, make sure it is last + var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); + if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) + { + genericArguments = genericArguments + .OrderBy(t => t == typeof(object) ? 1 : 0) + .ToArray(); + } + + return genericArguments + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(global::System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs new file mode 100644 index 000000000000..d174943cb2cf --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs @@ -0,0 +1,474 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Non-generic interface for Optional types to enable reflection-free checks. +/// +public interface IOptional +{ + /// + /// Returns true if the value is defined (set), even if the value is null. + /// + bool IsDefined { get; } + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + object? GetBoxedValue(); +} + +/// +/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). +/// Use this for HTTP PATCH requests where you need to distinguish between: +/// +/// Undefined: Don't send this field (leave it unchanged on the server) +/// Defined with null: Send null (clear the field on the server) +/// Defined with value: Send the value (update the field on the server) +/// +/// +/// The type of the value. Use nullable types (T?) for fields that can be null. +/// +/// For nullable string fields, use Optional<string?>: +/// +/// public class UpdateUserRequest +/// { +/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; +/// } +/// +/// var request = new UpdateUserRequest +/// { +/// Name = "John" // Will send: { "name": "John" } +/// }; +/// +/// var request2 = new UpdateUserRequest +/// { +/// Name = Optional<string?>.Of(null) // Will send: { "name": null } +/// }; +/// +/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) +/// +/// +public readonly struct Optional : IOptional, IEquatable> +{ + private readonly T _value; + private readonly bool _isDefined; + + private Optional(T value, bool isDefined) + { + _value = value; + _isDefined = isDefined; + } + + /// + /// Creates an undefined value - the field will not be included in the HTTP request. + /// Use this as the default value for optional fields. + /// + /// + /// + /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; + /// + /// + public static Optional Undefined => new(default!, false); + + /// + /// Creates a defined value - the field will be included in the HTTP request. + /// The value can be null if T is a nullable type. + /// + /// The value to set. Can be null if T is nullable (e.g., string?, int?). + /// + /// + /// // Set to a value + /// request.Name = Optional<string?>.Of("John"); + /// + /// // Set to null (clears the field) + /// request.Email = Optional<string?>.Of(null); + /// + /// // Or use implicit conversion + /// request.Name = "John"; // Same as Of("John") + /// request.Email = null; // Same as Of(null) + /// + /// + public static Optional Of(T value) => new(value, true); + + /// + /// Returns true if the field is defined (set), even if the value is null. + /// Use this to determine if the field should be included in the HTTP request. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// requestBody["name"] = request.Name.Value; // Include in request (can be null) + /// } + /// + /// + public bool IsDefined => _isDefined; + + /// + /// Returns true if the field is undefined (not set). + /// Use this to check if the field should be excluded from the HTTP request. + /// + /// + /// + /// if (request.Email.IsUndefined) + /// { + /// // Don't include email in the request - leave it unchanged + /// } + /// + /// + public bool IsUndefined => !_isDefined; + + /// + /// Gets the value. The value may be null if T is a nullable type. + /// + /// Thrown if the value is undefined. + /// + /// Always check before accessing Value, or use instead. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> + /// } + /// + /// // Or check for null explicitly + /// if (request.Email.IsDefined && request.Email.Value is null) + /// { + /// // Email is explicitly set to null (clear it) + /// } + /// + /// + public T Value + { + get + { + if (!_isDefined) + throw new InvalidOperationException("Optional value is undefined"); + return _value; + } + } + + /// + /// Gets the value if defined, otherwise returns the specified default value. + /// Note: If the value is defined as null, this returns null (not the default). + /// + /// The value to return if undefined. + /// The actual value if defined (can be null), otherwise the default value. + /// + /// + /// string name = request.Name.GetValueOrDefault("Anonymous"); + /// // If Name is undefined: returns "Anonymous" + /// // If Name is Of(null): returns null + /// // If Name is Of("John"): returns "John" + /// + /// + public T GetValueOrDefault(T defaultValue = default!) + { + return _isDefined ? _value : defaultValue; + } + + /// + /// Tries to get the value. Returns true if the value is defined (even if null). + /// + /// + /// When this method returns, contains the value if defined, or default(T) if undefined. + /// The value may be null if T is nullable. + /// + /// True if the value is defined; otherwise, false. + /// + /// + /// if (request.Email.TryGetValue(out var email)) + /// { + /// requestBody["email"] = email; // email can be null + /// } + /// else + /// { + /// // Email is undefined - don't include in request + /// } + /// + /// + public bool TryGetValue(out T value) + { + if (_isDefined) + { + value = _value; + return true; + } + value = default!; + return false; + } + + /// + /// Implicitly converts a value to Optional<T>.Of(value). + /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). + /// + /// The value to convert (can be null if T is nullable). + public static implicit operator Optional(T value) => Of(value); + + /// + /// Returns a string representation of this Optional value. + /// + /// "Undefined" if not set, or "Defined(value)" if set. + public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + public object? GetBoxedValue() + { + if (!_isDefined) + return null; + return _value; + } + + /// + public bool Equals(Optional other) => + _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); + + /// + public override bool Equals(object? obj) => obj is Optional other && Equals(other); + + /// + public override int GetHashCode() + { + if (!_isDefined) + return 0; + unchecked + { + int hash = 17; + hash = hash * 31 + 1; // _isDefined = true + hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); + return hash; + } + } + + /// + /// Determines whether two Optional values are equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are equal; otherwise, false. + public static bool operator ==(Optional left, Optional right) => left.Equals(right); + + /// + /// Determines whether two Optional values are not equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are not equal; otherwise, false. + public static bool operator !=(Optional left, Optional right) => !left.Equals(right); +} + +/// +/// Extension methods for Optional to simplify common operations. +/// +public static class OptionalExtensions +{ + /// + /// Adds the value to a dictionary if the optional is defined (even if the value is null). + /// This is useful for building JSON request payloads where null values should be included. + /// + /// The type of the optional value. + /// The optional value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined + /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined + /// + /// + public static void AddTo( + this Optional optional, + Dictionary dictionary, + string key + ) + { + if (optional.IsDefined) + { + dictionary[key] = optional.Value; + } + } + + /// + /// Executes an action if the optional is defined. + /// + /// The type of the optional value. + /// The optional value. + /// The action to execute with the value. + /// + /// + /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); + /// + /// + public static void IfDefined(this Optional optional, Action action) + { + if (optional.IsDefined) + { + action(optional.Value); + } + } + + /// + /// Maps the value to a new type if the optional is defined, otherwise returns undefined. + /// + /// The type of the original value. + /// The type to map to. + /// The optional value to map. + /// The mapping function. + /// An optional containing the mapped value if defined, otherwise undefined. + /// + /// + /// Optional<string?> name = Optional<string?>.Of("John"); + /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) + /// + /// + public static Optional Map( + this Optional optional, + Func mapper + ) + { + return optional.IsDefined + ? Optional.Of(mapper(optional.Value)) + : Optional.Undefined; + } + + /// + /// Adds a nullable value to a dictionary only if it is not null. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The type of the value (must be a reference type or Nullable). + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : class + { + if (value is not null) + { + dictionary[key] = value; + } + } + + /// + /// Adds a nullable value type to a dictionary only if it has a value. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The underlying value type. + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : struct + { + if (value.HasValue) + { + dictionary[key] = value.Value; + } + } +} + +/// +/// JSON converter factory for Optional that handles undefined vs null correctly. +/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. +/// +public class OptionalJsonConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(global::System.Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + return false; + + return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); + } + + public override JsonConverter? CreateConverter( + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var valueType = typeToConvert.GetGenericArguments()[0]; + var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); + return (JsonConverter?)global::System.Activator.CreateInstance(converterType); + } +} + +/// +/// JSON converter for Optional that unwraps the value during serialization. +/// The actual property skipping is handled by the OptionalTypeInfoResolver. +/// +public class OptionalJsonConverter : JsonConverter> +{ + public override Optional Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return Optional.Of(default!); + } + + var value = JsonSerializer.Deserialize(ref reader, options); + return Optional.Of(value!); + } + + public override void Write( + Utf8JsonWriter writer, + Optional value, + JsonSerializerOptions options + ) + { + // This will be called by the serializer + // We need to unwrap and serialize the inner value + // The TypeInfoResolver will handle skipping undefined values + + if (value.IsUndefined) + { + // This shouldn't be called for undefined values due to ShouldSerialize + // But if it is, write null and let the resolver filter it + writer.WriteNullValue(); + return; + } + + // Get the inner value + var innerValue = value.Value; + + // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) + if (innerValue is null) + { + writer.WriteNullValue(); + return; + } + + // Serialize the unwrapped value + JsonSerializer.Serialize(writer, innerValue, options); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs new file mode 100644 index 000000000000..4c4c4073a0ae --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs @@ -0,0 +1,17 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as optional in the OpenAPI specification. +/// Optional properties use the Optional type and can be undefined (not present in JSON). +/// +/// +/// Properties marked with [Optional] should use the Optional type: +/// - Undefined: Optional.Undefined → omitted from JSON +/// - Defined: Optional.Of(value) → written to JSON +/// +/// Combine with [Nullable] to allow null values: +/// - [Optional, Nullable] Optional → can be undefined, null, or a value +/// - [Optional] Optional → can be undefined or a value (null is invalid) +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs new file mode 100644 index 000000000000..8b43322350bd --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs @@ -0,0 +1,353 @@ +using global::System.Collections; +using global::System.Collections.ObjectModel; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using SeedApi.Core; + +namespace SeedApi; + +public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties +{ + internal ReadOnlyAdditionalProperties() { } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record ReadOnlyAdditionalProperties : IReadOnlyDictionary +{ + private readonly Dictionary _extensionData = new(); + private readonly Dictionary _convertedCache = new(); + + internal ReadOnlyAdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + if (kvp.Value is JsonElement element) + { + _extensionData.Add(kvp.Key, element); + } + else + { + _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); + } + + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(JsonElement value) + { + if (typeof(T) == typeof(JsonElement)) + { + return (T)(object)value; + } + + return value.Deserialize(JsonOptions.JsonSerializerOptions)!; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var cached)) + { + return cached; + } + + var value = ConvertToT(_extensionData[key]); + _convertedCache[key] = value; + return value; + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _extensionData.Count; + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var element)) + { + value = ConvertToT(element); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public T this[string key] => GetCached(key); + + public IEnumerable Keys => _extensionData.Keys; + + public IEnumerable Values => Keys.Select(GetCached); +} + +public record AdditionalProperties : AdditionalProperties +{ + public AdditionalProperties() { } + + public AdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record AdditionalProperties : IDictionary +{ + private readonly Dictionary _extensionData; + private readonly Dictionary _convertedCache; + + public AdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + public AdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + _extensionData[kvp.Key] = kvp.Value; + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(object? extensionDataValue) + { + return extensionDataValue switch + { + T value => value, + JsonElement jsonElement => jsonElement.Deserialize( + JsonOptions.JsonSerializerOptions + )!, + JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, + _ => JsonUtils + .SerializeToElement(extensionDataValue) + .Deserialize(JsonOptions.JsonSerializerOptions)!, + }; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + internal void CopyToExtensionData(IDictionary extensionData) + { + extensionData.Clear(); + foreach (var kvp in _extensionData) + { + extensionData[kvp.Key] = kvp.Value; + } + } + + public JsonObject ToJsonObject() => + ( + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ) + ).AsObject(); + + public JsonNode ToJsonNode() => + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ); + + public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); + + public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); + + public IReadOnlyDictionary ToJsonElementDictionary() + { + return new ReadOnlyDictionary( + _extensionData.ToDictionary( + kvp => kvp.Key, + kvp => + { + if (kvp.Value is JsonElement jsonElement) + { + return jsonElement; + } + + return JsonUtils.SerializeToElement(kvp.Value); + } + ) + ); + } + + public ICollection Keys => _extensionData.Keys; + + public ICollection Values + { + get + { + var values = new T[_extensionData.Count]; + var i = 0; + foreach (var key in Keys) + { + values[i++] = GetCached(key); + } + + return values; + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var value)) + { + return value; + } + + value = ConvertToT(_extensionData[key]); + _convertedCache.Add(key, value); + return value; + } + + private void SetCached(string key, T value) + { + _extensionData[key] = value; + _convertedCache[key] = value; + } + + private void AddCached(string key, T value) + { + _extensionData.Add(key, value); + _convertedCache.Add(key, value); + } + + private bool RemoveCached(string key) + { + var isRemoved = _extensionData.Remove(key); + _convertedCache.Remove(key); + return isRemoved; + } + + public int Count => _extensionData.Count; + public bool IsReadOnly => false; + + public T this[string key] + { + get => GetCached(key); + set => SetCached(key, value); + } + + public void Add(string key, T value) => AddCached(key, value); + + public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); + + public bool Remove(string key) => RemoveCached(key); + + public bool Remove(KeyValuePair item) => RemoveCached(item.Key); + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool Contains(KeyValuePair item) + { + return _extensionData.ContainsKey(item.Key) + && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); + } + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var extensionDataValue)) + { + value = ConvertToT(extensionDataValue); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public void Clear() + { + _extensionData.Clear(); + _convertedCache.Clear(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (array.Length - arrayIndex < _extensionData.Count) + { + throw new ArgumentException( + "The array does not have enough space to copy the elements." + ); + } + + foreach (var kvp in _extensionData) + { + array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); + } + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs new file mode 100644 index 000000000000..f33d49028884 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs @@ -0,0 +1,63 @@ +namespace SeedApi; + +/// +/// File parameter for uploading files. +/// +public record FileParameter : IDisposable +#if NET6_0_OR_GREATER + , IAsyncDisposable +#endif +{ + private bool _disposed; + + /// + /// The name of the file to be uploaded. + /// + public string? FileName { get; set; } + + /// + /// The content type of the file to be uploaded. + /// + public string? ContentType { get; set; } + + /// + /// The content of the file to be uploaded. + /// + public required Stream Stream { get; set; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + if (disposing) + { + Stream.Dispose(); + } + + _disposed = true; + } + +#if NET6_0_OR_GREATER + /// + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + await Stream.DisposeAsync().ConfigureAwait(false); + _disposed = true; + } + + GC.SuppressFinalize(this); + } +#endif + + public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs new file mode 100644 index 000000000000..3d210b7e0b4c --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +[Serializable] +internal class Version +{ + public const string Current = "0.0.1"; +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs new file mode 100644 index 000000000000..9f1f4a1c1181 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +public interface IStringEnum : IEquatable +{ + public string Value { get; } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs new file mode 100644 index 000000000000..704cb6836ab8 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +internal static class StringEnumExtensions +{ + public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Describable.cs b/seed/csharp-model/allof-inline/src/SeedApi/Describable.cs new file mode 100644 index 000000000000..f521e9082be7 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Describable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Describable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Display name from Describable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs b/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs new file mode 100644 index 000000000000..7bc064682236 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs @@ -0,0 +1,28 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("metadata")] + public DetailedOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs b/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs new file mode 100644 index 000000000000..798903997238 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from DetailedOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Custom domain name. + /// + [JsonPropertyName("domain")] + public string? Domain { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs b/seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs new file mode 100644 index 000000000000..05b3808b610b --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Identifiable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Identifiable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Organization.cs b/seed/csharp-model/allof-inline/src/SeedApi/Organization.cs new file mode 100644 index 000000000000..602a7e16c442 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/Organization.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Organization : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public OrganizationMetadata? Metadata { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs b/seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs new file mode 100644 index 000000000000..084daaac298a --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record OrganizationMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from DetailedOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Custom domain name. + /// + [JsonPropertyName("domain")] + public string? Domain { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs b/seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs new file mode 100644 index 000000000000..9f4b1b5c0820 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PaginatedResult : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable Results { get; set; } = new List(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs b/seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs new file mode 100644 index 000000000000..b1f0001a4733 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PagingCursors : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Cursor for the next page of results. + /// + [JsonPropertyName("next")] + public required string Next { get; set; } + + /// + /// Cursor for the previous page of results. + /// + [JsonPropertyName("previous")] + public string? Previous { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs new file mode 100644 index 000000000000..d22a26079f4e --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] +[Serializable] +public readonly record struct RuleExecutionContext : IStringEnum +{ + public static readonly RuleExecutionContext Prod = new(Values.Prod); + + public static readonly RuleExecutionContext Staging = new(Values.Staging); + + public static readonly RuleExecutionContext Dev = new(Values.Dev); + + public RuleExecutionContext(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleExecutionContext FromCustom(string value) + { + return new RuleExecutionContext(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleExecutionContext value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleExecutionContext value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleExecutionContext value) => value.Value; + + public static explicit operator RuleExecutionContext(string value) => new(value); + + internal class RuleExecutionContextSerializer : JsonConverter + { + public override RuleExecutionContext Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleExecutionContext ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Prod = "prod"; + + public const string Staging = "staging"; + + public const string Dev = "dev"; + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs new file mode 100644 index 000000000000..78e1a0e6d50e --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs @@ -0,0 +1,65 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("status")] + public required RuleResponseStatus Status { get; set; } + + [JsonPropertyName("executionContext")] + public RuleExecutionContext? ExecutionContext { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs new file mode 100644 index 000000000000..9b587cdbcba0 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] +[Serializable] +public readonly record struct RuleResponseStatus : IStringEnum +{ + public static readonly RuleResponseStatus Active = new(Values.Active); + + public static readonly RuleResponseStatus Inactive = new(Values.Inactive); + + public static readonly RuleResponseStatus Draft = new(Values.Draft); + + public RuleResponseStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleResponseStatus FromCustom(string value) + { + return new RuleResponseStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleResponseStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleResponseStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleResponseStatus value) => value.Value; + + public static explicit operator RuleResponseStatus(string value) => new(value); + + internal class RuleResponseStatusSerializer : JsonConverter + { + public override RuleResponseStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleResponseStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Inactive = "inactive"; + + public const string Draft = "draft"; + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs new file mode 100644 index 000000000000..578b90315dde --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleType : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs new file mode 100644 index 000000000000..e05899151a38 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleTypeSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props b/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props new file mode 100644 index 000000000000..17a84cada530 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props @@ -0,0 +1,20 @@ + + + + diff --git a/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj b/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj new file mode 100644 index 000000000000..6ba194fdf143 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj @@ -0,0 +1,60 @@ + + + net462;net8.0;net9.0;netstandard2.0 + enable + 12 + enable + 0.0.1 + $(Version) + $(Version) + README.md + https://github.com/allof-inline/fern + true + + + + false + + + $(DefineConstants);USE_PORTABLE_DATE_ONLY + true + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + <_Parameter1>SeedApi.Test + + + + + diff --git a/seed/csharp-model/allof-inline/src/SeedApi/User.cs b/seed/csharp-model/allof-inline/src/SeedApi/User.cs new file mode 100644 index 000000000000..abc389c0a6b6 --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/User.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record User : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("email")] + public required string Email { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs b/seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs new file mode 100644 index 000000000000..55c5368d774e --- /dev/null +++ b/seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record UserSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/.editorconfig b/seed/csharp-model/allof/.editorconfig new file mode 100644 index 000000000000..1e7a0adbac80 --- /dev/null +++ b/seed/csharp-model/allof/.editorconfig @@ -0,0 +1,35 @@ +root = true + +[*.cs] +resharper_arrange_object_creation_when_type_evident_highlighting = hint +resharper_auto_property_can_be_made_get_only_global_highlighting = hint +resharper_check_namespace_highlighting = hint +resharper_class_never_instantiated_global_highlighting = hint +resharper_class_never_instantiated_local_highlighting = hint +resharper_collection_never_updated_global_highlighting = hint +resharper_convert_type_check_pattern_to_null_check_highlighting = hint +resharper_inconsistent_naming_highlighting = hint +resharper_member_can_be_private_global_highlighting = hint +resharper_member_hides_static_from_outer_class_highlighting = hint +resharper_not_accessed_field_local_highlighting = hint +resharper_nullable_warning_suppression_is_used_highlighting = suggestion +resharper_partial_type_with_single_part_highlighting = hint +resharper_prefer_concrete_value_over_default_highlighting = none +resharper_private_field_can_be_converted_to_local_variable_highlighting = hint +resharper_property_can_be_made_init_only_global_highlighting = hint +resharper_property_can_be_made_init_only_local_highlighting = hint +resharper_redundant_name_qualifier_highlighting = none +resharper_redundant_using_directive_highlighting = hint +resharper_replace_slice_with_range_indexer_highlighting = none +resharper_unused_auto_property_accessor_global_highlighting = hint +resharper_unused_auto_property_accessor_local_highlighting = hint +resharper_unused_member_global_highlighting = hint +resharper_unused_type_global_highlighting = hint +resharper_use_string_interpolation_highlighting = hint +dotnet_diagnostic.CS1591.severity = suggestion + +[src/**/Types/*.cs] +resharper_check_namespace_highlighting = none + +[src/**/Core/Public/*.cs] +resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-model/allof/.fern/metadata.json b/seed/csharp-model/allof/.fern/metadata.json new file mode 100644 index 000000000000..bdf3cbf8c785 --- /dev/null +++ b/seed/csharp-model/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-csharp-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/csharp-model/allof/.github/workflows/ci.yml b/seed/csharp-model/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..87068349b616 --- /dev/null +++ b/seed/csharp-model/allof/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + DOTNET_NOLOGO: true + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Install tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore src/SeedApi/SeedApi.csproj + + - name: Build + run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release + + - name: Restore test dependencies + run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj + + - name: Build tests + run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release + + - name: Test + run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release + + - name: Pack + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release + + - name: Publish to NuGet.org + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" + diff --git a/seed/csharp-model/allof/.gitignore b/seed/csharp-model/allof/.gitignore new file mode 100644 index 000000000000..11014f2b33d7 --- /dev/null +++ b/seed/csharp-model/allof/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +## This is based on `dotnet new gitignore` and customized by Fern + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +# [Rr]elease/ (Ignored by Fern) +# [Rr]eleases/ (Ignored by Fern) +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +# [Ll]og/ (Ignored by Fern) +# [Ll]ogs/ (Ignored by Fern) + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/seed/csharp-model/allof/SeedApi.slnx b/seed/csharp-model/allof/SeedApi.slnx new file mode 100644 index 000000000000..d4c63c241aad --- /dev/null +++ b/seed/csharp-model/allof/SeedApi.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/seed/csharp-model/allof/snippet.json b/seed/csharp-model/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs new file mode 100644 index 000000000000..a12183113312 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs @@ -0,0 +1,365 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class AdditionalPropertiesTests +{ + [Test] + public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); + Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); + }); + } + + [Test] + public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecord + { + Id = "1", + AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.Id, Is.EqualTo("1")); + Assert.That( + deserializedRecord.AdditionalProperties["category"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), + Is.EqualTo("fiction") + ); + Assert.That( + deserializedRecord.AdditionalProperties["title"], + Is.InstanceOf() + ); + Assert.That( + ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() + { + // Arrange + var extensionData = new Dictionary + { + ["key1"] = JsonUtils.SerializeToElement("value1"), + ["key2"] = JsonUtils.SerializeToElement(123), + }; + var readOnlyProps = new ReadOnlyAdditionalProperties(); + readOnlyProps.CopyFromExtensionData(extensionData); + + // Act & Assert + Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); + Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); + } + + [Test] + public void AdditionalProperties_ShouldBehaveAsDictionary() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + additionalProps["key3"] = true; + + // Assert + Assert.Multiple(() => + { + Assert.That(additionalProps["key1"], Is.EqualTo("value1")); + Assert.That(additionalProps["key2"], Is.EqualTo(123)); + Assert.That((bool)additionalProps["key3"]!, Is.True); + Assert.That(additionalProps.Count, Is.EqualTo(3)); + }); + } + + [Test] + public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() + { + // Arrange + var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; + + // Act + var jsonObject = additionalProps.ToJsonObject(); + + Assert.Multiple(() => + { + // Assert + Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); + Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); + }); + } + + [Test] + public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() + { + // Arrange + const string json = """ + { + "id": "1", + "category": "fiction", + "title": "The Hobbit" + } + """; + var record = JsonUtils.Deserialize(json); + + // Act + record.AdditionalProperties["category"] = "non-fiction"; + + // Assert + Assert.Multiple(() => + { + Assert.That(record, Is.Not.Null); + Assert.That(record.Id, Is.EqualTo("1")); + Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); + Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); + Assert.That( + ((JsonElement)record.AdditionalProperties["title"]!).GetString(), + Is.EqualTo("The Hobbit") + ); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": 42, + "extra2": 99 + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithInts + { + AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); + Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); + }); + } + + [Test] + public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() + { + // Arrange + const string json = """ + { + "extra1": { "key1": true, "key2": false }, + "extra2": { "key3": true } + } + """; + + // Act + var record = JsonUtils.Deserialize(json); + + // Assert + Assert.That(record, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + [Test] + public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() + { + // Arrange + var record = new WriteableRecordWithDictionaries + { + AdditionalProperties = + { + ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, + ["extra2"] = new Dictionary { { "key3", true } }, + }, + }; + + // Act + var json = JsonUtils.Serialize(record); + var deserializedRecord = JsonUtils.Deserialize(json); + + // Assert + Assert.That(deserializedRecord, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); + Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); + Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); + }); + } + + private record Record : IJsonOnDeserialized + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithInts : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } + + private record RecordWithDictionaries : IJsonOnDeserialized + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties< + Dictionary + > AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + } + + private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing + { + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonIgnore] + public AdditionalProperties> AdditionalProperties { get; } = new(); + + void IJsonOnDeserialized.OnDeserialized() + { + AdditionalProperties.CopyFromExtensionData(_extensionData); + } + + void IJsonOnSerializing.OnSerializing() + { + AdditionalProperties.CopyToExtensionData(_extensionData); + } + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs new file mode 100644 index 000000000000..c0f258680b78 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs @@ -0,0 +1,100 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateOnlyJsonTests +{ + [Test] + public void SerializeDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (new DateOnly(2023, 1, 1), "\"2023-01-01\""), + (new DateOnly(2023, 12, 31), "\"2023-12-31\""), + (new DateOnly(2023, 6, 15), "\"2023-06-15\""), + (new DateOnly(2023, 3, 10), "\"2023-03-10\""), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() + { + (DateOnly? dateOnly, string expected)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + foreach (var (dateOnly, expected) in testCases) + { + var json = JsonUtils.Serialize(dateOnly); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() + { + (DateOnly? expected, string json)[] testCases = + [ + (new DateOnly(2023, 10, 5), "\"2023-10-05\""), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateOnly = JsonUtils.Deserialize(json); + Assert.That(dateOnly, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateOnlyKey() + { + var key = new DateOnly(2023, 10, 5); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateOnlyKey() + { + var json = """ + { + "2023-10-05": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateOnly(2023, 10, 5); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs new file mode 100644 index 000000000000..1dde45a8e939 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs @@ -0,0 +1,134 @@ +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class DateTimeJsonTests +{ + [Test] + public void SerializeDateTime_ShouldMatchExpectedFormat() + { + (DateTime dateTime, string expected)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + foreach (var (dateTime, expected) in testCases) + { + var json = JsonUtils.Serialize(dateTime); + Assert.That(json, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeDateTime_ShouldMatchExpectedDateTime() + { + (DateTime expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), + ( + new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), + "\"2023-12-31T23:59:59.000Z\"" + ), + (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.000Z\"" + ), + (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), + ( + new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), + "\"2023-03-10T08:45:30.123Z\"" + ), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void SerializeNullableDateTime_ShouldMatchExpectedFormat() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() + { + (DateTime? expected, string json)[] testCases = + [ + ( + new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), + "\"2023-10-05T14:30:00.000Z\"" + ), + (null, "null"), + ]; + + foreach (var (expected, json) in testCases) + { + var dateTime = JsonUtils.Deserialize(json); + Assert.That(dateTime, Is.EqualTo(expected)); + } + } + + [Test] + public void ShouldSerializeDictionaryWithDateTimeKey() + { + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + var dict = new Dictionary { { key, "value_a" } }; + var json = JsonUtils.Serialize(dict); + Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); + Assert.That(json, Does.Contain("value_a")); + } + + [Test] + public void ShouldDeserializeDictionaryWithDateTimeKey() + { + var json = """ + { + "2023-10-05T14:30:00.000Z": "value_a" + } + """; + var dict = JsonUtils.Deserialize>(json); + Assert.That(dict, Is.Not.Null); + var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); + Assert.That(dict![key], Is.EqualTo("value_a")); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs new file mode 100644 index 000000000000..969acd620998 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs @@ -0,0 +1,160 @@ +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +public class JsonAccessAttributeTests +{ + private class MyClass + { + [JsonPropertyName("read_only_prop")] + [JsonAccess(JsonAccessType.ReadOnly)] + public string? ReadOnlyProp { get; set; } + + [JsonPropertyName("write_only_prop")] + [JsonAccess(JsonAccessType.WriteOnly)] + public string? WriteOnlyProp { get; set; } + + [JsonPropertyName("normal_prop")] + public string? NormalProp { get; set; } + + [JsonPropertyName("read_only_nullable_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable? ReadOnlyNullableList { get; set; } + + [JsonPropertyName("read_only_list")] + [JsonAccess(JsonAccessType.ReadOnly)] + public IEnumerable ReadOnlyList { get; set; } = []; + + [JsonPropertyName("write_only_nullable_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable? WriteOnlyNullableList { get; set; } + + [JsonPropertyName("write_only_list")] + [JsonAccess(JsonAccessType.WriteOnly)] + public IEnumerable WriteOnlyList { get; set; } = []; + + [JsonPropertyName("normal_list")] + public IEnumerable NormalList { get; set; } = []; + + [JsonPropertyName("normal_nullable_list")] + public IEnumerable? NullableNormalList { get; set; } + } + + [Test] + public void JsonAccessAttribute_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "write_only_prop": "write", + "normal_prop": "normal_prop", + "read_only_nullable_list": ["item1", "item2"], + "read_only_list": ["item3", "item4"], + "write_only_nullable_list": ["item5", "item6"], + "write_only_list": ["item7", "item8"], + "normal_list": ["normal1", "normal2"], + "normal_nullable_list": ["normal1", "normal2"] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // String properties + Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); + Assert.That(obj.WriteOnlyProp, Is.Null); + Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); + + // List properties - read only + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Not.Null); + Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); + Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); + Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); + + var readOnlyList = obj.ReadOnlyList.ToArray(); + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Has.Length.EqualTo(2)); + Assert.That(readOnlyList[0], Is.EqualTo("item3")); + Assert.That(readOnlyList[1], Is.EqualTo("item4")); + + // List properties - write only + Assert.That(obj.WriteOnlyNullableList, Is.Null); + Assert.That(obj.WriteOnlyList, Is.Not.Null); + Assert.That(obj.WriteOnlyList, Is.Empty); + + // Normal list property + var normalList = obj.NormalList.ToArray(); + Assert.That(normalList, Is.Not.Null); + Assert.That(normalList, Has.Length.EqualTo(2)); + Assert.That(normalList[0], Is.EqualTo("normal1")); + Assert.That(normalList[1], Is.EqualTo("normal2")); + }); + + // Set up values for serialization + obj.WriteOnlyProp = "write"; + obj.NormalProp = "new_value"; + obj.WriteOnlyNullableList = new List { "write1", "write2" }; + obj.WriteOnlyList = new List { "write3", "write4" }; + obj.NormalList = new List { "new_normal" }; + obj.NullableNormalList = new List { "new_normal" }; + + var serializedJson = JsonUtils.Serialize(obj); + const string expectedJson = """ + { + "write_only_prop": "write", + "normal_prop": "new_value", + "write_only_nullable_list": [ + "write1", + "write2" + ], + "write_only_list": [ + "write3", + "write4" + ], + "normal_list": [ + "new_normal" + ], + "normal_nullable_list": [ + "new_normal" + ] + } + """; + Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); + } + + [Test] + public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() + { + const string json = """ + { + "read_only_prop": "read", + "normal_prop": "normal_prop", + "read_only_nullable_list": null, + "read_only_list": [] + } + """; + var obj = JsonUtils.Deserialize(json); + + Assert.Multiple(() => + { + // Read-only nullable list should be null when JSON contains null + var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); + Assert.That(nullableReadOnlyList, Is.Null); + + // Read-only non-nullable list should never be null, but empty when JSON contains null + var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default + Assert.That(readOnlyList, Is.Not.Null); + Assert.That(readOnlyList, Is.Empty); + }); + + // Serialize and verify read-only lists are not included + var serializedJson = JsonUtils.Serialize(obj); + Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); + Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); + Assert.That(serializedJson, Does.Not.Contain("read_only_list")); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs new file mode 100644 index 000000000000..42d165830baf --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs @@ -0,0 +1,314 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using NUnit.Framework; +using OneOf; +using SeedApi.Core; + +namespace SeedApi.Test.Core.Json; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class OneOfSerializerTests +{ + private class Foo + { + [JsonPropertyName("string_prop")] + public required string StringProp { get; set; } + } + + private class Bar + { + [JsonPropertyName("int_prop")] + public required int IntProp { get; set; } + } + + private static readonly OneOf OneOf1 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT2(new { }); + private const string OneOf1String = "{}"; + + private static readonly OneOf OneOf2 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT0("test"); + private const string OneOf2String = "\"test\""; + + private static readonly OneOf OneOf3 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT1(123); + private const string OneOf3String = "123"; + + private static readonly OneOf OneOf4 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT3(new Foo { StringProp = "test" }); + private const string OneOf4String = "{\"string_prop\": \"test\"}"; + + private static readonly OneOf OneOf5 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT4(new Bar { IntProp = 5 }); + private const string OneOf5String = "{\"int_prop\": 5}"; + + [Test] + public void Serialize_OneOfs_Should_Return_Expected_String() + { + (OneOf, string)[] testData = + [ + (OneOf1, OneOf1String), + (OneOf2, OneOf2String), + (OneOf3, OneOf3String), + (OneOf4, OneOf4String), + (OneOf5, OneOf5String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, expected) in testData) + { + var result = JsonUtils.Serialize(oneOf); + Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void OneOfs_Should_Deserialize_From_String() + { + (OneOf, string)[] testData = + [ + (OneOf1, OneOf1String), + (OneOf2, OneOf2String), + (OneOf3, OneOf3String), + (OneOf4, OneOf4String), + (OneOf5, OneOf5String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, json) in testData) + { + var result = JsonUtils.Deserialize>(json); + Assert.That(result.Index, Is.EqualTo(oneOf.Index)); + Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); + } + }); + } + + private static readonly OneOf? NullableOneOf1 = null; + private const string NullableOneOf1String = "null"; + + private static readonly OneOf? NullableOneOf2 = OneOf< + string, + int, + object, + Foo, + Bar + >.FromT4(new Bar { IntProp = 5 }); + private const string NullableOneOf2String = "{\"int_prop\": 5}"; + + [Test] + public void Serialize_NullableOneOfs_Should_Return_Expected_String() + { + (OneOf?, string)[] testData = + [ + (NullableOneOf1, NullableOneOf1String), + (NullableOneOf2, NullableOneOf2String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, expected) in testData) + { + var result = JsonUtils.Serialize(oneOf); + Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void NullableOneOfs_Should_Deserialize_From_String() + { + (OneOf?, string)[] testData = + [ + (NullableOneOf1, NullableOneOf1String), + (NullableOneOf2, NullableOneOf2String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, json) in testData) + { + var result = JsonUtils.Deserialize?>(json); + Assert.That(result?.Index, Is.EqualTo(oneOf?.Index)); + Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result?.Value)).IgnoreWhiteSpace); + } + }); + } + + private static readonly OneOf OneOfWithNullable1 = OneOf< + string, + int, + Foo? + >.FromT2(null); + private const string OneOfWithNullable1String = "null"; + + private static readonly OneOf OneOfWithNullable2 = OneOf< + string, + int, + Foo? + >.FromT2(new Foo { StringProp = "test" }); + private const string OneOfWithNullable2String = "{\"string_prop\": \"test\"}"; + + private static readonly OneOf OneOfWithNullable3 = OneOf< + string, + int, + Foo? + >.FromT0("test"); + private const string OneOfWithNullable3String = "\"test\""; + + [Test] + public void Serialize_OneOfWithNullables_Should_Return_Expected_String() + { + (OneOf, string)[] testData = + [ + (OneOfWithNullable1, OneOfWithNullable1String), + (OneOfWithNullable2, OneOfWithNullable2String), + (OneOfWithNullable3, OneOfWithNullable3String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, expected) in testData) + { + var result = JsonUtils.Serialize(oneOf); + Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void OneOfWithNullables_Should_Deserialize_From_String() + { + (OneOf, string)[] testData = + [ + // (OneOfWithNullable1, OneOfWithNullable1String), // not possible with .NET's JSON serializer + (OneOfWithNullable2, OneOfWithNullable2String), + (OneOfWithNullable3, OneOfWithNullable3String), + ]; + Assert.Multiple(() => + { + foreach (var (oneOf, json) in testData) + { + var result = JsonUtils.Deserialize>(json); + Assert.That(result.Index, Is.EqualTo(oneOf.Index)); + Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); + } + }); + } + + [Test] + public void Serialize_OneOfWithObjectLast_Should_Return_Expected_String() + { + var oneOfWithObjectLast = OneOf.FromT4( + new { random = "data" } + ); + const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; + + var result = JsonUtils.Serialize(oneOfWithObjectLast); + Assert.That(result, Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace); + } + + [Test] + public void OneOfWithObjectLast_Should_Deserialize_From_String() + { + const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; + var result = JsonUtils.Deserialize>( + oneOfWithObjectLastString + ); + Assert.Multiple(() => + { + Assert.That(result.Index, Is.EqualTo(4)); + Assert.That(result.Value, Is.InstanceOf()); + Assert.That( + JsonUtils.Serialize(result.Value), + Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace + ); + }); + } + + [Test] + public void Serialize_OneOfWithObjectNotLast_Should_Return_Expected_String() + { + var oneOfWithObjectNotLast = OneOf.FromT1( + new { random = "data" } + ); + const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; + + var result = JsonUtils.Serialize(oneOfWithObjectNotLast); + Assert.That(result, Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace); + } + + [Test] + public void OneOfWithObjectNotLast_Should_Deserialize_From_String() + { + const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; + var result = JsonUtils.Deserialize>( + oneOfWithObjectNotLastString + ); + Assert.Multiple(() => + { + Assert.That(result.Index, Is.EqualTo(1)); + Assert.That(result.Value, Is.InstanceOf()); + Assert.That( + JsonUtils.Serialize(result.Value), + Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace + ); + }); + } + + [Test] + public void Serialize_OneOfSingleType_Should_Return_Expected_String() + { + var oneOfSingle = OneOf.FromT0("single"); + const string oneOfSingleString = "\"single\""; + + var result = JsonUtils.Serialize(oneOfSingle); + Assert.That(result, Is.EqualTo(oneOfSingleString).IgnoreWhiteSpace); + } + + [Test] + public void OneOfSingleType_Should_Deserialize_From_String() + { + const string oneOfSingleString = "\"single\""; + var result = JsonUtils.Deserialize>(oneOfSingleString); + Assert.Multiple(() => + { + Assert.That(result.Index, Is.EqualTo(0)); + Assert.That(result.Value, Is.EqualTo("single")); + }); + } + + [Test] + public void Deserialize_InvalidData_Should_Throw_Exception() + { + const string invalidJson = "{\"invalid\": \"data\"}"; + + Assert.Throws(() => + { + JsonUtils.Deserialize>(invalidJson); + }); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props new file mode 100644 index 000000000000..aac9b5020d80 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props @@ -0,0 +1,6 @@ + + diff --git a/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj new file mode 100644 index 000000000000..77e1a9943739 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj @@ -0,0 +1,36 @@ + + + net9.0 + 12 + enable + enable + false + true + true + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs new file mode 100644 index 000000000000..3ac7e5310f95 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs @@ -0,0 +1,219 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; +using SeedApi; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle AdditionalProperties values. +/// +public static class AdditionalPropertiesComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their + /// serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) + { + constraint.Using( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + /// + /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing + /// their serialized JSON representations. This handles the type mismatch between native C# types + /// and JsonElement values that occur when comparing manually constructed objects with + /// deserialized objects. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) + { + constraint.Using>( + (x, y) => + { + if (x.Count != y.Count) + { + return false; + } + + foreach (var key in x.Keys) + { + if (!y.ContainsKey(key)) + { + return false; + } + + var xElement = JsonUtils.SerializeToElement(x[key]); + var yElement = JsonUtils.SerializeToElement(y[key]); + + if (!JsonElementsAreEqual(xElement, yElement)) + { + return false; + } + } + + return true; + } + ); + + return constraint; + } + + internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => + JsonElementsAreEqual(x, y); + + private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) + { + if (x.ValueKind != y.ValueKind) + { + return false; + } + + return x.ValueKind switch + { + JsonValueKind.Object => CompareJsonObjects(x, y), + JsonValueKind.Array => CompareJsonArrays(x, y), + JsonValueKind.String => x.GetString() == y.GetString(), + JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), + JsonValueKind.True => true, + JsonValueKind.False => true, + JsonValueKind.Null => true, + _ => false, + }; + } + + private static bool CompareJsonObjects(JsonElement x, JsonElement y) + { + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + if (xProps.Count != yProps.Count) + { + return false; + } + + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + return false; + } + + if (!JsonElementsAreEqual(xProps[key], yProps[key])) + { + return false; + } + } + + return true; + } + + private static bool CompareJsonArrays(JsonElement x, JsonElement y) + { + var xArray = x.EnumerateArray().ToList(); + var yArray = y.EnumerateArray().ToList(); + + if (xArray.Count != yArray.Count) + { + return false; + } + + for (var i = 0; i < xArray.Count; i++) + { + if (!JsonElementsAreEqual(xArray[i], yArray[i])) + { + return false; + } + } + + return true; + } + + /// + /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. + /// When UsingPropertiesComparer() walks object properties and encounters a property typed as + /// 'object', the expected side may be a Dictionary<object, object?> while the actual + /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing + /// the non-JsonElement side and comparing JSON representations. + /// + /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic + /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only + /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all + /// same-type comparisons normally. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) + { + // Handle: expected is non-JsonElement, actual is JsonElement + constraint.Using( + (actualJsonElement, expectedObj) => + { + try + { + var expectedElement = JsonUtils.SerializeToElement(expectedObj); + return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); + } + catch + { + return false; + } + } + ); + // Handle reverse: expected is JsonElement, actual is non-JsonElement + constraint.Using( + (actualObj, expectedJsonElement) => + { + try + { + var actualElement = JsonUtils.SerializeToElement(actualObj); + return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); + } + catch + { + return false; + } + } + ); + return constraint; + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs new file mode 100644 index 000000000000..3f4b5eb602b2 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs @@ -0,0 +1,29 @@ +using global::System.Text.Json; +using NUnit.Framework; +using SeedApi.Core; + +namespace SeedApi.Test.Utils; + +internal static class JsonAssert +{ + /// + /// Asserts that the serialized JSON of an object equals the expected JSON string. + /// Uses JsonElement comparison for reliable deep equality of collections and union types. + /// + internal static void AreEqual(object actual, string expectedJson) + { + var actualElement = JsonUtils.SerializeToElement(actual); + var expectedElement = JsonUtils.Deserialize(expectedJson); + Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); + } + + /// + /// Asserts that the given JSON string survives a deserialization/serialization round-trip + /// intact: deserializes to T then re-serializes and compares to the original JSON. + /// + internal static void Roundtrips(string json) + { + var deserialized = JsonUtils.Deserialize(json); + AreEqual(deserialized!, json); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs new file mode 100644 index 000000000000..a37ef402c1ac --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs @@ -0,0 +1,236 @@ +using global::System.Text.Json; +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle JsonElement objects. +/// +public static class JsonElementComparerExtensions +{ + /// + /// Extension method for comparing JsonElement objects in NUnit tests. + /// Property order doesn't matter, but array order does matter. + /// Includes special handling for DateTime string formats. + /// + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare JsonElements with detailed diffs. + public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) + { + return constraint.Using(new JsonElementComparer()); + } +} + +/// +/// Equality comparer for JsonElement with detailed reporting. +/// Property order doesn't matter, but array order does matter. +/// Now includes special handling for DateTime string formats with improved null handling. +/// +public class JsonElementComparer : IEqualityComparer +{ + private string _failurePath = string.Empty; + + /// + public bool Equals(JsonElement x, JsonElement y) + { + _failurePath = string.Empty; + return CompareJsonElements(x, y, string.Empty); + } + + /// + public int GetHashCode(JsonElement obj) + { + return JsonSerializer.Serialize(obj).GetHashCode(); + } + + private bool CompareJsonElements(JsonElement x, JsonElement y, string path) + { + // If value kinds don't match, they're not equivalent + if (x.ValueKind != y.ValueKind) + { + _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; + return false; + } + + switch (x.ValueKind) + { + case JsonValueKind.Object: + return CompareJsonObjects(x, y, path); + + case JsonValueKind.Array: + return CompareJsonArraysInOrder(x, y, path); + + case JsonValueKind.String: + string? xStr = x.GetString(); + string? yStr = y.GetString(); + + // Handle null strings + if (xStr is null && yStr is null) + return true; + + if (xStr is null || yStr is null) + { + _failurePath = + $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; + return false; + } + + // Check if they are identical strings + if (xStr == yStr) + return true; + + // Try to handle DateTime strings + if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) + { + if (AreEquivalentDateTimeStrings(xStr, yStr)) + return true; + } + + _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; + return false; + + case JsonValueKind.Number: + if (x.GetDecimal() != y.GetDecimal()) + { + _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; + return false; + } + + return true; + + case JsonValueKind.True: + case JsonValueKind.False: + if (x.GetBoolean() != y.GetBoolean()) + { + _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; + return false; + } + + return true; + + case JsonValueKind.Null: + return true; + + default: + _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; + return false; + } + } + + private bool IsLikelyDateTimeString(string? str) + { + // Simple heuristic to identify likely ISO date time strings + return str is not null + && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); + } + + private bool AreEquivalentDateTimeStrings(string str1, string str2) + { + // Try to parse both as DateTime + if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) + { + return dt1 == dt2; + } + + return false; + } + + private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) + { + // Create dictionaries for both JSON objects + var xProps = new Dictionary(); + var yProps = new Dictionary(); + + foreach (var prop in x.EnumerateObject()) + xProps[prop.Name] = prop.Value; + + foreach (var prop in y.EnumerateObject()) + yProps[prop.Name] = prop.Value; + + // Check if all properties in x exist in y + foreach (var key in xProps.Keys) + { + if (!yProps.ContainsKey(key)) + { + _failurePath = $"{path}: Missing property '{key}'"; + return false; + } + } + + // Check if y has extra properties + foreach (var key in yProps.Keys) + { + if (!xProps.ContainsKey(key)) + { + _failurePath = $"{path}: Unexpected property '{key}'"; + return false; + } + } + + // Compare each property value + foreach (var key in xProps.Keys) + { + var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; + if (!CompareJsonElements(xProps[key], yProps[key], propPath)) + { + return false; + } + } + + return true; + } + + private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) + { + var xArray = x.EnumerateArray(); + var yArray = y.EnumerateArray(); + + // Count x elements + var xCount = 0; + var xElements = new List(); + foreach (var item in xArray) + { + xElements.Add(item); + xCount++; + } + + // Count y elements + var yCount = 0; + var yElements = new List(); + foreach (var item in yArray) + { + yElements.Add(item); + yCount++; + } + + // Check if counts match + if (xCount != yCount) + { + _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; + return false; + } + + // Compare elements in order + for (var i = 0; i < xCount; i++) + { + var itemPath = $"{path}[{i}]"; + if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) + { + return false; + } + } + + return true; + } + + /// + public override string ToString() + { + if (!string.IsNullOrEmpty(_failurePath)) + { + return $"JSON comparison failed at {_failurePath}"; + } + + return "JsonElementEqualityComparer"; + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs new file mode 100644 index 000000000000..816f4c010e6e --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs @@ -0,0 +1,32 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class NUnitExtensions +{ + /// + /// Modifies the EqualConstraint to use our own set of default comparers. + /// + /// + /// + public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => + constraint + .UsingPropertiesComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingReadOnlyMemoryComparer() + .UsingOneOfComparer() + .UsingJsonElementComparer() + .UsingOptionalComparer() + .UsingObjectDictionaryComparer() + .UsingAdditionalPropertiesComparer() + .UsingJsonSerializationComparer(); +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs new file mode 100644 index 000000000000..767439174363 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs @@ -0,0 +1,86 @@ +using NUnit.Framework.Constraints; +using OneOf; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle OneOf values. +/// +public static class EqualConstraintExtensions +{ + /// + /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOneOf types + constraint.Using( + (x, y) => + { + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (x.Value is null && y.Value is null) + { + return true; + } + + if (x.Value is null) + { + return false; + } + + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs new file mode 100644 index 000000000000..98bfcac477b8 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs @@ -0,0 +1,104 @@ +using NUnit.Framework.Constraints; +using OneOf; +using SeedApi.Core; + +namespace NUnit.Framework; + +/// +/// Extensions for EqualConstraint to handle Optional values. +/// +public static class OptionalComparerExtensions +{ + /// + /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. + /// This works alongside other comparison modifiers like UsingPropertiesComparer. + /// + /// The EqualConstraint to modify. + /// The same constraint instance for method chaining. + public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) + { + // Register a comparer factory for IOptional types + constraint.Using( + (x, y) => + { + // Both must have the same IsDefined state + if (x.IsDefined != y.IsDefined) + { + return false; + } + + // If both are undefined, they're equal + if (!x.IsDefined) + { + return true; + } + + // Both are defined, compare their boxed values + var xValue = x.GetBoxedValue(); + var yValue = y.GetBoxedValue(); + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (xValue is null && yValue is null) + { + return true; + } + + if (xValue is null || yValue is null) + { + return false; + } + + // Use NUnit's property comparer for the inner values + var propertiesComparer = new NUnitEqualityComparer(); + var tolerance = Tolerance.Default; + propertiesComparer.CompareProperties = true; + // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) + propertiesComparer.ExternalComparers.Add( + new OneOfEqualityAdapter(propertiesComparer) + ); + return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); + } + ); + + return constraint; + } + + /// + /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. + /// This enables recursive comparison of nested OneOf values within Optional types. + /// + private class OneOfEqualityAdapter : EqualityAdapter + { + private readonly NUnitEqualityComparer _comparer; + + public OneOfEqualityAdapter(NUnitEqualityComparer comparer) + { + _comparer = comparer; + } + + public override bool CanCompare(object? x, object? y) + { + return x is IOneOf && y is IOneOf; + } + + public override bool AreEqual(object? x, object? y) + { + var oneOfX = (IOneOf?)x; + var oneOfY = (IOneOf?)y; + + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (oneOfX?.Value is null && oneOfY?.Value is null) + { + return true; + } + + if (oneOfX?.Value is null || oneOfY?.Value is null) + { + return false; + } + + var tolerance = Tolerance.Default; + return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); + } + } +} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs new file mode 100644 index 000000000000..fc0b595a5e54 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs @@ -0,0 +1,87 @@ +using NUnit.Framework.Constraints; + +namespace NUnit.Framework; + +/// +/// Extensions for NUnit constraints. +/// +public static class ReadOnlyMemoryComparerExtensions +{ + /// + /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. + /// + /// The type of elements in the ReadOnlyMemory. + /// The Is.EqualTo() constraint instance. + /// A constraint that can compare ReadOnlyMemory<T>. + public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) + where T : IComparable + { + return constraint.Using(new ReadOnlyMemoryComparer()); + } +} + +/// +/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. +/// +/// +/// The type of elements in the ReadOnlyMemory. +/// +public class ReadOnlyMemoryComparer : IComparer> + where T : IComparable +{ + /// + public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) + { + // Check if sequences are equal + var xSpan = x.Span; + var ySpan = y.Span; + + // Optimized case for IEquatable implementations + if (typeof(IEquatable).IsAssignableFrom(typeof(T))) + { + var areEqual = xSpan.SequenceEqual(ySpan); + if (areEqual) + { + return 0; // Sequences are equal + } + } + else + { + // Manual equality check for non-IEquatable types + if (xSpan.Length == ySpan.Length) + { + var areEqual = true; + for (var i = 0; i < xSpan.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + areEqual = false; + break; + } + } + + if (areEqual) + { + return 0; // Sequences are equal + } + } + } + + // For non-equal sequences, we need to return a consistent ordering + // First compare lengths + if (x.Length != y.Length) + return x.Length.CompareTo(y.Length); + + // Same length but different content - compare first differing element + for (var i = 0; i < x.Length; i++) + { + if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) + { + return xSpan[i].CompareTo(ySpan[i]); + } + } + + // Should never reach here if not equal + return 0; + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/AuditInfo.cs b/seed/csharp-model/allof/src/SeedApi/AuditInfo.cs new file mode 100644 index 000000000000..c0d843281678 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/AuditInfo.cs @@ -0,0 +1,56 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +/// +/// Common audit metadata. +/// +[Serializable] +public record AuditInfo : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/BaseOrg.cs b/seed/csharp-model/allof/src/SeedApi/BaseOrg.cs new file mode 100644 index 000000000000..eb944d0030da --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/BaseOrg.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public BaseOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs b/seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs new file mode 100644 index 000000000000..fcb0efec5fea --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record BaseOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from BaseOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Subscription tier. + /// + [JsonPropertyName("tier")] + public string? Tier { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs b/seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs new file mode 100644 index 000000000000..757711bdb703 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs @@ -0,0 +1,46 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record CombinedEntity : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("status")] + public required CombinedEntityStatus Status { get; set; } + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Identifiable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs b/seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs new file mode 100644 index 000000000000..0ab2467f6bd7 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs @@ -0,0 +1,115 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] +[Serializable] +public readonly record struct CombinedEntityStatus : IStringEnum +{ + public static readonly CombinedEntityStatus Active = new(Values.Active); + + public static readonly CombinedEntityStatus Archived = new(Values.Archived); + + public CombinedEntityStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static CombinedEntityStatus FromCustom(string value) + { + return new CombinedEntityStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(CombinedEntityStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(CombinedEntityStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(CombinedEntityStatus value) => value.Value; + + public static explicit operator CombinedEntityStatus(string value) => new(value); + + internal class CombinedEntityStatusSerializer : JsonConverter + { + public override CombinedEntityStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override CombinedEntityStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new CombinedEntityStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + CombinedEntityStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Archived = "archived"; + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs new file mode 100644 index 000000000000..b684f33d750e --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +internal class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter, new() +{ + private static readonly TConverterType _converter = new TConverterType(); + + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new global::System.Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(_converter); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Constants.cs b/seed/csharp-model/allof/src/SeedApi/Core/Constants.cs new file mode 100644 index 000000000000..ccf4e963cc89 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace SeedApi.Core; + +internal static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; + public const string DateFormat = "yyyy-MM-dd"; +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs new file mode 100644 index 000000000000..af61cc061ae5 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs @@ -0,0 +1,747 @@ +// ReSharper disable All +#pragma warning disable + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using global::System.Diagnostics; +using global::System.Diagnostics.CodeAnalysis; +using global::System.Globalization; +using global::System.Runtime.CompilerServices; +using global::System.Runtime.InteropServices; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +// ReSharper disable SuggestVarOrType_SimpleTypes +// ReSharper disable SuggestVarOrType_BuiltInTypes + +namespace SeedApi.Core +{ + /// + /// Custom converter for handling the data type with the System.Text.Json library. + /// + /// + /// This class backported from: + /// + /// System.Text.Json.Serialization.Converters.DateOnlyConverter + /// + public sealed class DateOnlyConverter : JsonConverter + { + private const int FormatLength = 10; // YYYY-MM-DD + + private const int MaxEscapedFormatLength = + FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; + + /// + public override DateOnly Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType != JsonTokenType.String) + { + ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); + } + + return ReadCore(ref reader); + } + + /// + public override DateOnly ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + return ReadCore(ref reader); + } + + private static DateOnly ReadCore(ref Utf8JsonReader reader) + { + if ( + !JsonHelpers.IsInRangeInclusive( + reader.ValueLength(), + FormatLength, + MaxEscapedFormatLength + ) + ) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + scoped ReadOnlySpan source; + if (!reader.HasValueSequence && !reader.ValueIsEscaped) + { + source = reader.ValueSpan; + } + else + { + Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; + int bytesWritten = reader.CopyString(stackSpan); + source = stackSpan.Slice(0, bytesWritten); + } + + if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) + { + ThrowHelper.ThrowFormatException(DataType.DateOnly); + } + + return value; + } + + /// + public override void Write( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WriteStringValue(buffer); + } + + /// + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateOnly value, + JsonSerializerOptions options + ) + { +#if NET8_0_OR_GREATER + Span buffer = stackalloc byte[FormatLength]; +#else + Span buffer = stackalloc char[FormatLength]; +#endif + // ReSharper disable once RedundantAssignment + bool formattedSuccessfully = value.TryFormat( + buffer, + out int charsWritten, + "O".AsSpan(), + CultureInfo.InvariantCulture + ); + Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); + writer.WritePropertyName(buffer); + } + } + + internal static class JsonConstants + { + // The maximum number of fraction digits the Json DateTime parser allows + public const int DateTimeParseNumFractionDigits = 16; + + // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. + public const int MaxExpansionFactorWhileEscaping = 6; + + // The largest fraction expressible by TimeSpan and DateTime formats + public const int MaxDateTimeFraction = 9_999_999; + + // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. + public const int DateTimeNumFractionDigits = 7; + + public const byte UtcOffsetToken = (byte)'Z'; + + public const byte TimePrefix = (byte)'T'; + + public const byte Period = (byte)'.'; + + public const byte Hyphen = (byte)'-'; + + public const byte Colon = (byte)':'; + + public const byte Plus = (byte)'+'; + } + + // ReSharper disable SuggestVarOrType_Elsewhere + // ReSharper disable SuggestVarOrType_SimpleTypes + // ReSharper disable SuggestVarOrType_BuiltInTypes + + internal static class JsonHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => + (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); + + public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; + + [StructLayout(LayoutKind.Auto)] + private struct DateTimeParseData + { + public int Year; + public int Month; + public int Day; + public bool IsCalendarDateOnly; + public int Hour; + public int Minute; + public int Second; + public int Fraction; // This value should never be greater than 9_999_999. + public int OffsetHours; + public int OffsetMinutes; + + // ReSharper disable once NotAccessedField.Local + public byte OffsetToken; + } + + public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) + { + if ( + TryParseDateTimeOffset(source, out DateTimeParseData parseData) + && parseData.IsCalendarDateOnly + && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) + ) + { + value = DateOnly.FromDateTime(dateTime); + return true; + } + + value = default; + return false; + } + + /// + /// ISO 8601 date time parser (ISO 8601-1:2019). + /// + /// The date/time to parse in UTF-8 format. + /// The parsed for the given . + /// + /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day + /// representations with optional specification of seconds and fractional seconds. + /// + /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). + /// If unspecified they are considered to be local per spec. + /// + /// Examples: (TZD is either "Z" or hh:mm offset from UTC) + /// + /// YYYY-MM-DD (e.g. 1997-07-16) + /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) + /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) + /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) + /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) + /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) + /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) + /// + /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). + /// The extended variants have separator characters between components ('-', ':', '.', etc.). + /// Spaces are not permitted. + /// + /// "true" if successfully parsed. + private static bool TryParseDateTimeOffset( + ReadOnlySpan source, + out DateTimeParseData parseData + ) + { + parseData = default; + + // too short datetime + Debug.Assert(source.Length >= 10); + + // Parse the calendar date + // ----------------------- + // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" + // [dateX] = [year]["-"][month]["-"][day] + // [year] = [YYYY] [0000 - 9999] (4.3.2) + // [month] = [MM] [01 - 12] (4.3.3) + // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) + // + // Note: 5.2.2.2 "Representations with reduced precision" allows for + // just [year]["-"][month] (a) and just [year] (b), but we currently + // don't permit it. + + { + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + uint digit3 = source[2] - (uint)'0'; + uint digit4 = source[3] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) + { + return false; + } + + parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); + } + + if ( + source[4] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) + || source[7] != JsonConstants.Hyphen + || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) + ) + { + return false; + } + + // We now have YYYY-MM-DD [dateX] + // ReSharper disable once ConvertIfStatementToSwitchStatement + if (source.Length == 10) + { + parseData.IsCalendarDateOnly = true; + return true; + } + + // Parse the time of day + // --------------------- + // + // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" + // [timeX] = ["T"][hour][":"][min][":"][sec] + // [hour] = [hh] [00 - 23] (4.3.8a) + // [minute] = [mm] [00 - 59] (4.3.9a) + // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) + // + // ISO 8601-1:2019 5.3.3 "UTC of day" + // [timeX]["Z"] + // + // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between + // local timescale and UTC" (Extended format) + // + // [shiftX] = ["+"|"-"][hour][":"][min] + // + // Notes: + // + // "T" is optional per spec, but _only_ when times are used alone. In our + // case, we're reading out a complete date & time and as such require "T". + // (5.4.2.1b). + // + // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations + // with reduced precision". 5.3.1.3b allows just specifying the hour, but + // we currently don't permit this. + // + // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). + // We only allow fractions for seconds currently. Lower order components + // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be + // one digit, but the max number of digits is implementation defined. We + // currently allow up to 16 digits of fractional seconds only. While we + // support 16 fractional digits we only parse the first seven, anything + // past that is considered a zero. This is to stay compatible with the + // DateTime implementation which is limited to this resolution. + + if (source.Length < 16) + { + // Source does not have enough characters for YYYY-MM-DDThh:mm + return false; + } + + // Parse THH:MM (e.g. "T10:32") + if ( + source[10] != JsonConstants.TimePrefix + || source[13] != JsonConstants.Colon + || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) + || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm + Debug.Assert(source.Length >= 16); + if (source.Length == 16) + { + return true; + } + + byte curByte = source[16]; + int sourceIndex = 17; + + // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Colon: + break; + default: + return false; + } + + // Try reading the seconds + if ( + source.Length < 19 + || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss + Debug.Assert(source.Length >= 19); + if (source.Length == 19) + { + return true; + } + + curByte = source[19]; + sourceIndex = 20; + + // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + case JsonConstants.Period: + break; + default: + return false; + } + + // Source does not have enough characters for second fractions (i.e. ".s") + // YYYY-MM-DDThh:mm:ss.s + if (source.Length < 21) + { + return false; + } + + // Parse fraction. This value should never be greater than 9_999_999 + int numDigitsRead = 0; + int fractionEnd = Math.Min( + sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, + source.Length + ); + + while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) + { + if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); + numDigitsRead++; + } + + sourceIndex++; + } + + if (parseData.Fraction != 0) + { + while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) + { + parseData.Fraction *= 10; + numDigitsRead++; + } + } + + // We now have YYYY-MM-DDThh:mm:ss.s + Debug.Assert(sourceIndex <= source.Length); + if (sourceIndex == source.Length) + { + return true; + } + + curByte = source[sourceIndex++]; + + // TZD ['Z'|'+'|'-'] is valid at this point + switch (curByte) + { + case JsonConstants.UtcOffsetToken: + parseData.OffsetToken = JsonConstants.UtcOffsetToken; + return sourceIndex == source.Length; + case JsonConstants.Plus: + case JsonConstants.Hyphen: + parseData.OffsetToken = curByte; + return ParseOffset(ref parseData, source.Slice(sourceIndex)); + default: + return false; + } + + static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) + { + // Parse the hours for the offset + if ( + offsetData.Length < 2 + || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) + ) + { + return false; + } + + // We now have YYYY-MM-DDThh:mm:ss.s+|-hh + + if (offsetData.Length == 2) + { + // Just hours offset specified + return true; + } + + // Ensure we have enough for ":mm" + return offsetData.Length == 5 + && offsetData[2] == JsonConstants.Colon + && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantAssignment + private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) + { + Debug.Assert(source.Length == 2); + + uint digit1 = source[0] - (uint)'0'; + uint digit2 = source[1] - (uint)'0'; + + if (digit1 > 9 || digit2 > 9) + { + value = 0; + return false; + } + + value = (int)(digit1 * 10 + digit2); + return true; + } + + // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs + + /// + /// Overflow-safe DateTime factory. + /// + private static bool TryCreateDateTime( + DateTimeParseData parseData, + DateTimeKind kind, + out DateTime value + ) + { + if (parseData.Year == 0) + { + value = default; + return false; + } + + Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. + + if ((uint)parseData.Month - 1 >= 12) + { + value = default; + return false; + } + + uint dayMinusOne = (uint)parseData.Day - 1; + if ( + dayMinusOne >= 28 + && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) + ) + { + value = default; + return false; + } + + if ((uint)parseData.Hour > 23) + { + value = default; + return false; + } + + if ((uint)parseData.Minute > 59) + { + value = default; + return false; + } + + // This needs to allow leap seconds when appropriate. + // See https://github.com/dotnet/runtime/issues/30135. + if ((uint)parseData.Second > 59) + { + value = default; + return false; + } + + Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. + + ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) + ? DaysToMonth366 + : DaysToMonth365; + int yearMinusOne = parseData.Year - 1; + int totalDays = + yearMinusOne * 365 + + yearMinusOne / 4 + - yearMinusOne / 100 + + yearMinusOne / 400 + + days[parseData.Month - 1] + + parseData.Day + - 1; + long ticks = totalDays * TimeSpan.TicksPerDay; + int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; + ticks += totalSeconds * TimeSpan.TicksPerSecond; + ticks += parseData.Fraction; + value = new DateTime(ticks: ticks, kind: kind); + return true; + } + + private static ReadOnlySpan DaysToMonth365 => + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + private static ReadOnlySpan DaysToMonth366 => + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + } + + internal static class ThrowHelper + { + private const string ExceptionSourceValueToRethrowAsJsonException = + "System.Text.Json.Rethrowable"; + + [DoesNotReturn] + public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) + { + throw GetInvalidOperationException("string", tokenType); + } + + public static void ThrowFormatException(DataType dataType) + { + throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + + private static global::System.Exception GetInvalidOperationException( + string message, + JsonTokenType tokenType + ) + { + return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); + } + + private static InvalidOperationException GetInvalidOperationException(string message) + { + return new InvalidOperationException(message) + { + Source = ExceptionSourceValueToRethrowAsJsonException, + }; + } + } + + internal static class Utf8JsonReaderExtensions + { + internal static int ValueLength(this Utf8JsonReader reader) => + reader.HasValueSequence + ? checked((int)reader.ValueSequence.Length) + : reader.ValueSpan.Length; + } + + internal enum DataType + { + TimeOnly, + DateOnly, + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal static class SR + { + private static readonly bool s_usingResourceKeys = + AppContext.TryGetSwitch( + "System.Resources.UseSystemResourceKeys", + out bool usingResourceKeys + ) && usingResourceKeys; + + public static string UnsupportedFormat => Strings.UnsupportedFormat; + + public static string InvalidCast => Strings.InvalidCast; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1) + : string.Format(resourceFormat, p1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Format(string resourceFormat, object? p1, object? p2) => + s_usingResourceKeys + ? string.Join(", ", resourceFormat, p1, p2) + : string.Format(resourceFormat, p1, p2); + } + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( + "System.Resources.Tools.StronglyTypedResourceBuilder", + "17.0.0.0" + )] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings + { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode" + )] + internal Strings() { } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager( + "System.Text.Json.Resources.Strings", + typeof(Strings).Assembly + ); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( + global::System.ComponentModel.EditorBrowsableState.Advanced + )] + internal static global::System.Globalization.CultureInfo Culture + { + get { return resourceCulture; } + set { resourceCulture = value; } + } + + /// + /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. + /// + internal static string InvalidCast + { + get { return ResourceManager.GetString("InvalidCast", resourceCulture); } + } + + /// + /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. + /// + internal static string UnsupportedFormat + { + get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } + } + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs new file mode 100644 index 000000000000..d7dedc7f165b --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs @@ -0,0 +1,40 @@ +using global::System.Globalization; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +internal class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } + + public override DateTime ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + DateTime value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs new file mode 100644 index 000000000000..93dcc6dd6bca --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs @@ -0,0 +1,15 @@ +namespace SeedApi.Core; + +[global::System.AttributeUsage( + global::System.AttributeTargets.Property | global::System.AttributeTargets.Field +)] +internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute +{ + internal JsonAccessType AccessType { get; init; } = accessType; +} + +internal enum JsonAccessType +{ + ReadOnly, + WriteOnly, +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs new file mode 100644 index 000000000000..2fa8cfb6ad8c --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs @@ -0,0 +1,275 @@ +using global::System.Reflection; +using global::System.Text.Encodings.Web; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using global::System.Text.Json.Serialization; +using global::System.Text.Json.Serialization.Metadata; + +namespace SeedApi.Core; + +internal static partial class JsonOptions +{ + internal static readonly JsonSerializerOptions JsonSerializerOptions; + internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; + + static JsonOptions() + { + var options = new JsonSerializerOptions + { + Converters = + { + new DateTimeSerializer(), +#if USE_PORTABLE_DATE_ONLY + new DateOnlyConverter(), +#endif + new OneOfSerializer(), + new OptionalJsonConverterFactory(), + }, +#if DEBUG + WriteIndented = true, +#endif + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + NullableOptionalModifier, + JsonAccessAndIgnoreModifier, + HandleExtensionDataFields, + }, + }, + }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; + + var relaxedOptions = new JsonSerializerOptions(options) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + JsonSerializerOptionsRelaxedEscaping = relaxedOptions; + } + + private static void NullableOptionalModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var property in typeInfo.Properties) + { + var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; + + if (propertyInfo is null) + continue; + + // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior + var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); + if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) + { + // ReadOnly means "never serialize", which completely overrides Optional/Nullable. + // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier + // will set ShouldSerialize = false anyway. + continue; + } + // Note: WriteOnly doesn't conflict with Optional/Nullable since it only + // affects deserialization (Set), not serialization (ShouldSerialize) + + var isOptionalType = + property.PropertyType.IsGenericType + && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); + + var hasOptionalAttribute = + propertyInfo.GetCustomAttribute() is not null; + var hasNullableAttribute = + propertyInfo.GetCustomAttribute() is not null; + + if (isOptionalType && hasOptionalAttribute) + { + var originalGetter = property.Get; + if (originalGetter is not null) + { + var capturedIsNullable = hasNullableAttribute; + + property.ShouldSerialize = (obj, value) => + { + var optionalValue = originalGetter(obj); + if (optionalValue is not IOptional optional) + return false; + + if (!optional.IsDefined) + return false; + + if (!capturedIsNullable) + { + var innerValue = optional.GetBoxedValue(); + if (innerValue is null) + return false; + } + + return true; + }; + } + } + else if (hasNullableAttribute) + { + // Force serialization of nullable properties even when null + property.ShouldSerialize = (obj, value) => true; + } + } + } + + private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) + { + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + + foreach (var propertyInfo in typeInfo.Properties) + { + var jsonAccessAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonAccessAttribute is not null) + { + propertyInfo.IsRequired = false; + switch (jsonAccessAttribute.AccessType) + { + case JsonAccessType.ReadOnly: + propertyInfo.ShouldSerialize = (_, _) => false; + break; + case JsonAccessType.WriteOnly: + propertyInfo.Set = null; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + var jsonIgnoreAttribute = propertyInfo + .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) + .OfType() + .FirstOrDefault(); + + if (jsonIgnoreAttribute is not null) + { + propertyInfo.IsRequired = false; + } + } + } + + private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) + { + if ( + typeInfo.Kind == JsonTypeInfoKind.Object + && typeInfo.Properties.All(prop => !prop.IsExtensionData) + ) + { + var extensionProp = typeInfo + .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) + .FirstOrDefault(prop => + prop.GetCustomAttribute() is not null + ); + + if (extensionProp is not null) + { + var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( + extensionProp.FieldType, + extensionProp.Name + ); + jsonPropertyInfo.Get = extensionProp.GetValue; + jsonPropertyInfo.Set = extensionProp.SetValue; + jsonPropertyInfo.IsExtensionData = true; + typeInfo.Properties.Add(jsonPropertyInfo); + } + } + } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); +} + +internal static class JsonUtils +{ + internal static string Serialize(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + + internal static string Serialize(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); + + internal static string SerializeRelaxedEscaping(T obj) => + JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => + JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); + + internal static JsonElement SerializeToElement(T obj) => + JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonElement SerializeToElement(object obj, global::System.Type type) => + JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); + + internal static JsonDocument SerializeToDocument(T obj) => + JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); + + internal static JsonNode? SerializeToNode(T obj) => + JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); + + internal static byte[] SerializeToUtf8Bytes(T obj) => + JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); + + internal static string SerializeWithAdditionalProperties( + T obj, + object? additionalProperties = null + ) + { + if (additionalProperties is null) + { + return Serialize(obj); + } + var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); + if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) + { + throw new InvalidOperationException( + "The additional properties must serialize to a JSON object." + ); + } + var jsonNode = SerializeToNode(obj); + if (jsonNode is not JsonObject jsonObject) + { + throw new InvalidOperationException( + "The serialized object must be a JSON object to add properties." + ); + } + MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); + return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); + } + + private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) + { + foreach (var property in overrideObject) + { + if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) + { + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + continue; + } + if ( + existingValue is JsonObject nestedBaseObject + && property.Value is JsonObject nestedOverrideObject + ) + { + // If both values are objects, recursively merge them. + MergeJsonObjects(nestedBaseObject, nestedOverrideObject); + continue; + } + // Otherwise, the overrideObject takes precedence. + baseObject[property.Key] = property.Value is not null + ? JsonNode.Parse(property.Value.ToJsonString()) + : null; + } + } + + internal static T Deserialize(string json) => + JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs new file mode 100644 index 000000000000..a1d30328bf9a --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs @@ -0,0 +1,18 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as nullable in the OpenAPI specification. +/// When applied to Optional properties, this indicates that null values should be +/// written to JSON when the optional is defined with null. +/// +/// +/// For regular (required) properties: +/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) +/// - With [Nullable]: null values are written to JSON +/// +/// For Optional properties (also marked with [Optional]): +/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) +/// - With [Nullable]: Optional.Of(null) → write null to JSON +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs new file mode 100644 index 000000000000..6eeb68fcba46 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs @@ -0,0 +1,145 @@ +using global::System.Reflection; +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using OneOf; + +namespace SeedApi.Core; + +internal class OneOfSerializer : JsonConverter +{ + public override IOneOf? Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (IOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + public override IOneOf ReadAsPropertyName( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = reader.GetString(); + if (stringValue == null) + throw new JsonException("Cannot deserialize null property name into OneOf type"); + + // Try to deserialize the string value into one of the supported types + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + // For primitive types, try direct conversion + if (type == typeof(string)) + { + return (IOneOf)cast.Invoke(null, [stringValue])!; + } + + // For other types, try to deserialize from JSON string + var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); + if (result != null) + { + return (IOneOf)cast.Invoke(null, [result])!; + } + } + catch { } + } + + // If no type-specific deserialization worked, default to string if available + var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); + if (stringType != default) + { + return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; + } + + throw new JsonException( + $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" + ); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + IOneOf value, + JsonSerializerOptions options + ) + { + // Serialize the underlying value to a string suitable for use as a dictionary key + var stringValue = value.Value?.ToString() ?? "null"; + writer.WritePropertyName(stringValue); + } + + private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( + global::System.Type typeToConvert + ) + { + var type = typeToConvert; + if (Nullable.GetUnderlyingType(type) is { } underlyingType) + { + type = underlyingType; + } + + var casts = type.GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + while (type is not null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + var genericArguments = type.GetGenericArguments(); + if (genericArguments.Length == 1) + { + return [(genericArguments[0], casts[0])]; + } + + // if object type is present, make sure it is last + var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); + if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) + { + genericArguments = genericArguments + .OrderBy(t => t == typeof(object) ? 1 : 0) + .ToArray(); + } + + return genericArguments + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(global::System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Optional.cs b/seed/csharp-model/allof/src/SeedApi/Core/Optional.cs new file mode 100644 index 000000000000..d174943cb2cf --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/Optional.cs @@ -0,0 +1,474 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Non-generic interface for Optional types to enable reflection-free checks. +/// +public interface IOptional +{ + /// + /// Returns true if the value is defined (set), even if the value is null. + /// + bool IsDefined { get; } + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + object? GetBoxedValue(); +} + +/// +/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). +/// Use this for HTTP PATCH requests where you need to distinguish between: +/// +/// Undefined: Don't send this field (leave it unchanged on the server) +/// Defined with null: Send null (clear the field on the server) +/// Defined with value: Send the value (update the field on the server) +/// +/// +/// The type of the value. Use nullable types (T?) for fields that can be null. +/// +/// For nullable string fields, use Optional<string?>: +/// +/// public class UpdateUserRequest +/// { +/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; +/// } +/// +/// var request = new UpdateUserRequest +/// { +/// Name = "John" // Will send: { "name": "John" } +/// }; +/// +/// var request2 = new UpdateUserRequest +/// { +/// Name = Optional<string?>.Of(null) // Will send: { "name": null } +/// }; +/// +/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) +/// +/// +public readonly struct Optional : IOptional, IEquatable> +{ + private readonly T _value; + private readonly bool _isDefined; + + private Optional(T value, bool isDefined) + { + _value = value; + _isDefined = isDefined; + } + + /// + /// Creates an undefined value - the field will not be included in the HTTP request. + /// Use this as the default value for optional fields. + /// + /// + /// + /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; + /// + /// + public static Optional Undefined => new(default!, false); + + /// + /// Creates a defined value - the field will be included in the HTTP request. + /// The value can be null if T is a nullable type. + /// + /// The value to set. Can be null if T is nullable (e.g., string?, int?). + /// + /// + /// // Set to a value + /// request.Name = Optional<string?>.Of("John"); + /// + /// // Set to null (clears the field) + /// request.Email = Optional<string?>.Of(null); + /// + /// // Or use implicit conversion + /// request.Name = "John"; // Same as Of("John") + /// request.Email = null; // Same as Of(null) + /// + /// + public static Optional Of(T value) => new(value, true); + + /// + /// Returns true if the field is defined (set), even if the value is null. + /// Use this to determine if the field should be included in the HTTP request. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// requestBody["name"] = request.Name.Value; // Include in request (can be null) + /// } + /// + /// + public bool IsDefined => _isDefined; + + /// + /// Returns true if the field is undefined (not set). + /// Use this to check if the field should be excluded from the HTTP request. + /// + /// + /// + /// if (request.Email.IsUndefined) + /// { + /// // Don't include email in the request - leave it unchanged + /// } + /// + /// + public bool IsUndefined => !_isDefined; + + /// + /// Gets the value. The value may be null if T is a nullable type. + /// + /// Thrown if the value is undefined. + /// + /// Always check before accessing Value, or use instead. + /// + /// + /// + /// if (request.Name.IsDefined) + /// { + /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> + /// } + /// + /// // Or check for null explicitly + /// if (request.Email.IsDefined && request.Email.Value is null) + /// { + /// // Email is explicitly set to null (clear it) + /// } + /// + /// + public T Value + { + get + { + if (!_isDefined) + throw new InvalidOperationException("Optional value is undefined"); + return _value; + } + } + + /// + /// Gets the value if defined, otherwise returns the specified default value. + /// Note: If the value is defined as null, this returns null (not the default). + /// + /// The value to return if undefined. + /// The actual value if defined (can be null), otherwise the default value. + /// + /// + /// string name = request.Name.GetValueOrDefault("Anonymous"); + /// // If Name is undefined: returns "Anonymous" + /// // If Name is Of(null): returns null + /// // If Name is Of("John"): returns "John" + /// + /// + public T GetValueOrDefault(T defaultValue = default!) + { + return _isDefined ? _value : defaultValue; + } + + /// + /// Tries to get the value. Returns true if the value is defined (even if null). + /// + /// + /// When this method returns, contains the value if defined, or default(T) if undefined. + /// The value may be null if T is nullable. + /// + /// True if the value is defined; otherwise, false. + /// + /// + /// if (request.Email.TryGetValue(out var email)) + /// { + /// requestBody["email"] = email; // email can be null + /// } + /// else + /// { + /// // Email is undefined - don't include in request + /// } + /// + /// + public bool TryGetValue(out T value) + { + if (_isDefined) + { + value = _value; + return true; + } + value = default!; + return false; + } + + /// + /// Implicitly converts a value to Optional<T>.Of(value). + /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). + /// + /// The value to convert (can be null if T is nullable). + public static implicit operator Optional(T value) => Of(value); + + /// + /// Returns a string representation of this Optional value. + /// + /// "Undefined" if not set, or "Defined(value)" if set. + public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; + + /// + /// Gets the boxed value. Returns null if undefined or if the value is null. + /// + public object? GetBoxedValue() + { + if (!_isDefined) + return null; + return _value; + } + + /// + public bool Equals(Optional other) => + _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); + + /// + public override bool Equals(object? obj) => obj is Optional other && Equals(other); + + /// + public override int GetHashCode() + { + if (!_isDefined) + return 0; + unchecked + { + int hash = 17; + hash = hash * 31 + 1; // _isDefined = true + hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); + return hash; + } + } + + /// + /// Determines whether two Optional values are equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are equal; otherwise, false. + public static bool operator ==(Optional left, Optional right) => left.Equals(right); + + /// + /// Determines whether two Optional values are not equal. + /// + /// The first Optional to compare. + /// The second Optional to compare. + /// True if the Optional values are not equal; otherwise, false. + public static bool operator !=(Optional left, Optional right) => !left.Equals(right); +} + +/// +/// Extension methods for Optional to simplify common operations. +/// +public static class OptionalExtensions +{ + /// + /// Adds the value to a dictionary if the optional is defined (even if the value is null). + /// This is useful for building JSON request payloads where null values should be included. + /// + /// The type of the optional value. + /// The optional value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined + /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined + /// + /// + public static void AddTo( + this Optional optional, + Dictionary dictionary, + string key + ) + { + if (optional.IsDefined) + { + dictionary[key] = optional.Value; + } + } + + /// + /// Executes an action if the optional is defined. + /// + /// The type of the optional value. + /// The optional value. + /// The action to execute with the value. + /// + /// + /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); + /// + /// + public static void IfDefined(this Optional optional, Action action) + { + if (optional.IsDefined) + { + action(optional.Value); + } + } + + /// + /// Maps the value to a new type if the optional is defined, otherwise returns undefined. + /// + /// The type of the original value. + /// The type to map to. + /// The optional value to map. + /// The mapping function. + /// An optional containing the mapped value if defined, otherwise undefined. + /// + /// + /// Optional<string?> name = Optional<string?>.Of("John"); + /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) + /// + /// + public static Optional Map( + this Optional optional, + Func mapper + ) + { + return optional.IsDefined + ? Optional.Of(mapper(optional.Value)) + : Optional.Undefined; + } + + /// + /// Adds a nullable value to a dictionary only if it is not null. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The type of the value (must be a reference type or Nullable). + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : class + { + if (value is not null) + { + dictionary[key] = value; + } + } + + /// + /// Adds a nullable value type to a dictionary only if it has a value. + /// This is useful for regular nullable properties where null means "omit from request". + /// + /// The underlying value type. + /// The nullable value to add. + /// The dictionary to add to. + /// The key to use in the dictionary. + /// + /// + /// var dict = new Dictionary<string, object?>(); + /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue + /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue + /// + /// + public static void AddIfNotNull( + this T? value, + Dictionary dictionary, + string key + ) + where T : struct + { + if (value.HasValue) + { + dictionary[key] = value.Value; + } + } +} + +/// +/// JSON converter factory for Optional that handles undefined vs null correctly. +/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. +/// +public class OptionalJsonConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(global::System.Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + return false; + + return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); + } + + public override JsonConverter? CreateConverter( + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var valueType = typeToConvert.GetGenericArguments()[0]; + var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); + return (JsonConverter?)global::System.Activator.CreateInstance(converterType); + } +} + +/// +/// JSON converter for Optional that unwraps the value during serialization. +/// The actual property skipping is handled by the OptionalTypeInfoResolver. +/// +public class OptionalJsonConverter : JsonConverter> +{ + public override Optional Read( + ref Utf8JsonReader reader, + global::System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return Optional.Of(default!); + } + + var value = JsonSerializer.Deserialize(ref reader, options); + return Optional.Of(value!); + } + + public override void Write( + Utf8JsonWriter writer, + Optional value, + JsonSerializerOptions options + ) + { + // This will be called by the serializer + // We need to unwrap and serialize the inner value + // The TypeInfoResolver will handle skipping undefined values + + if (value.IsUndefined) + { + // This shouldn't be called for undefined values due to ShouldSerialize + // But if it is, write null and let the resolver filter it + writer.WriteNullValue(); + return; + } + + // Get the inner value + var innerValue = value.Value; + + // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) + if (innerValue is null) + { + writer.WriteNullValue(); + return; + } + + // Serialize the unwrapped value + JsonSerializer.Serialize(writer, innerValue, options); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs new file mode 100644 index 000000000000..4c4c4073a0ae --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs @@ -0,0 +1,17 @@ +namespace SeedApi.Core; + +/// +/// Marks a property as optional in the OpenAPI specification. +/// Optional properties use the Optional type and can be undefined (not present in JSON). +/// +/// +/// Properties marked with [Optional] should use the Optional type: +/// - Undefined: Optional.Undefined → omitted from JSON +/// - Defined: Optional.Of(value) → written to JSON +/// +/// Combine with [Nullable] to allow null values: +/// - [Optional, Nullable] Optional → can be undefined, null, or a value +/// - [Optional] Optional → can be undefined or a value (null is invalid) +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] +public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs new file mode 100644 index 000000000000..8b43322350bd --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs @@ -0,0 +1,353 @@ +using global::System.Collections; +using global::System.Collections.ObjectModel; +using global::System.Text.Json; +using global::System.Text.Json.Nodes; +using SeedApi.Core; + +namespace SeedApi; + +public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties +{ + internal ReadOnlyAdditionalProperties() { } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record ReadOnlyAdditionalProperties : IReadOnlyDictionary +{ + private readonly Dictionary _extensionData = new(); + private readonly Dictionary _convertedCache = new(); + + internal ReadOnlyAdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + internal ReadOnlyAdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + if (kvp.Value is JsonElement element) + { + _extensionData.Add(kvp.Key, element); + } + else + { + _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); + } + + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(JsonElement value) + { + if (typeof(T) == typeof(JsonElement)) + { + return (T)(object)value; + } + + return value.Deserialize(JsonOptions.JsonSerializerOptions)!; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var cached)) + { + return cached; + } + + var value = ConvertToT(_extensionData[key]); + _convertedCache[key] = value; + return value; + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _extensionData.Count; + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var element)) + { + value = ConvertToT(element); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public T this[string key] => GetCached(key); + + public IEnumerable Keys => _extensionData.Keys; + + public IEnumerable Values => Keys.Select(GetCached); +} + +public record AdditionalProperties : AdditionalProperties +{ + public AdditionalProperties() { } + + public AdditionalProperties(IDictionary properties) + : base(properties) { } +} + +public record AdditionalProperties : IDictionary +{ + private readonly Dictionary _extensionData; + private readonly Dictionary _convertedCache; + + public AdditionalProperties() + { + _extensionData = new Dictionary(); + _convertedCache = new Dictionary(); + } + + public AdditionalProperties(IDictionary properties) + { + _extensionData = new Dictionary(properties.Count); + _convertedCache = new Dictionary(properties.Count); + foreach (var kvp in properties) + { + _extensionData[kvp.Key] = kvp.Value; + _convertedCache[kvp.Key] = kvp.Value; + } + } + + private static T ConvertToT(object? extensionDataValue) + { + return extensionDataValue switch + { + T value => value, + JsonElement jsonElement => jsonElement.Deserialize( + JsonOptions.JsonSerializerOptions + )!, + JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, + _ => JsonUtils + .SerializeToElement(extensionDataValue) + .Deserialize(JsonOptions.JsonSerializerOptions)!, + }; + } + + internal void CopyFromExtensionData(IDictionary extensionData) + { + _extensionData.Clear(); + _convertedCache.Clear(); + foreach (var kvp in extensionData) + { + _extensionData[kvp.Key] = kvp.Value; + if (kvp.Value is T value) + { + _convertedCache[kvp.Key] = value; + } + } + } + + internal void CopyToExtensionData(IDictionary extensionData) + { + extensionData.Clear(); + foreach (var kvp in _extensionData) + { + extensionData[kvp.Key] = kvp.Value; + } + } + + public JsonObject ToJsonObject() => + ( + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ) + ).AsObject(); + + public JsonNode ToJsonNode() => + JsonUtils.SerializeToNode(_extensionData) + ?? throw new InvalidOperationException( + "Failed to serialize AdditionalProperties to JSON Node." + ); + + public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); + + public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); + + public IReadOnlyDictionary ToJsonElementDictionary() + { + return new ReadOnlyDictionary( + _extensionData.ToDictionary( + kvp => kvp.Key, + kvp => + { + if (kvp.Value is JsonElement jsonElement) + { + return jsonElement; + } + + return JsonUtils.SerializeToElement(kvp.Value); + } + ) + ); + } + + public ICollection Keys => _extensionData.Keys; + + public ICollection Values + { + get + { + var values = new T[_extensionData.Count]; + var i = 0; + foreach (var key in Keys) + { + values[i++] = GetCached(key); + } + + return values; + } + } + + private T GetCached(string key) + { + if (_convertedCache.TryGetValue(key, out var value)) + { + return value; + } + + value = ConvertToT(_extensionData[key]); + _convertedCache.Add(key, value); + return value; + } + + private void SetCached(string key, T value) + { + _extensionData[key] = value; + _convertedCache[key] = value; + } + + private void AddCached(string key, T value) + { + _extensionData.Add(key, value); + _convertedCache.Add(key, value); + } + + private bool RemoveCached(string key) + { + var isRemoved = _extensionData.Remove(key); + _convertedCache.Remove(key); + return isRemoved; + } + + public int Count => _extensionData.Count; + public bool IsReadOnly => false; + + public T this[string key] + { + get => GetCached(key); + set => SetCached(key, value); + } + + public void Add(string key, T value) => AddCached(key, value); + + public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); + + public bool Remove(string key) => RemoveCached(key); + + public bool Remove(KeyValuePair item) => RemoveCached(item.Key); + + public bool ContainsKey(string key) => _extensionData.ContainsKey(key); + + public bool Contains(KeyValuePair item) + { + return _extensionData.ContainsKey(item.Key) + && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); + } + + public bool TryGetValue(string key, out T value) + { + if (_convertedCache.TryGetValue(key, out value!)) + { + return true; + } + + if (_extensionData.TryGetValue(key, out var extensionDataValue)) + { + value = ConvertToT(extensionDataValue); + _convertedCache[key] = value; + return true; + } + + return false; + } + + public void Clear() + { + _extensionData.Clear(); + _convertedCache.Clear(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (array.Length - arrayIndex < _extensionData.Count) + { + throw new ArgumentException( + "The array does not have enough space to copy the elements." + ); + } + + foreach (var kvp in _extensionData) + { + array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); + } + } + + public IEnumerator> GetEnumerator() + { + return _extensionData + .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs new file mode 100644 index 000000000000..f33d49028884 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs @@ -0,0 +1,63 @@ +namespace SeedApi; + +/// +/// File parameter for uploading files. +/// +public record FileParameter : IDisposable +#if NET6_0_OR_GREATER + , IAsyncDisposable +#endif +{ + private bool _disposed; + + /// + /// The name of the file to be uploaded. + /// + public string? FileName { get; set; } + + /// + /// The content type of the file to be uploaded. + /// + public string? ContentType { get; set; } + + /// + /// The content of the file to be uploaded. + /// + public required Stream Stream { get; set; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + if (disposing) + { + Stream.Dispose(); + } + + _disposed = true; + } + +#if NET6_0_OR_GREATER + /// + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + await Stream.DisposeAsync().ConfigureAwait(false); + _disposed = true; + } + + GC.SuppressFinalize(this); + } +#endif + + public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs b/seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs new file mode 100644 index 000000000000..3d210b7e0b4c --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs @@ -0,0 +1,7 @@ +namespace SeedApi; + +[Serializable] +internal class Version +{ + public const string Current = "0.0.1"; +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs b/seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs new file mode 100644 index 000000000000..9f1f4a1c1181 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +public interface IStringEnum : IEquatable +{ + public string Value { get; } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs new file mode 100644 index 000000000000..704cb6836ab8 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +internal static class StringEnumExtensions +{ + public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; +} diff --git a/seed/csharp-model/allof/src/SeedApi/Describable.cs b/seed/csharp-model/allof/src/SeedApi/Describable.cs new file mode 100644 index 000000000000..f521e9082be7 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Describable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Describable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Display name from Describable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// A short summary. + /// + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs b/seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs new file mode 100644 index 000000000000..7bc064682236 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs @@ -0,0 +1,28 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrg : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("metadata")] + public DetailedOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs b/seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs new file mode 100644 index 000000000000..798903997238 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record DetailedOrgMetadata : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Deployment region from DetailedOrg. + /// + [JsonPropertyName("region")] + public required string Region { get; set; } + + /// + /// Custom domain name. + /// + [JsonPropertyName("domain")] + public string? Domain { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Identifiable.cs b/seed/csharp-model/allof/src/SeedApi/Identifiable.cs new file mode 100644 index 000000000000..05b3808b610b --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Identifiable.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Identifiable : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Unique identifier. + /// + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// + /// Display name from Identifiable. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/Organization.cs b/seed/csharp-model/allof/src/SeedApi/Organization.cs new file mode 100644 index 000000000000..e90644924f99 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/Organization.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record Organization : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("metadata")] + public BaseOrgMetadata? Metadata { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs b/seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs new file mode 100644 index 000000000000..9f4b1b5c0820 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PaginatedResult : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable Results { get; set; } = new List(); + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/PagingCursors.cs b/seed/csharp-model/allof/src/SeedApi/PagingCursors.cs new file mode 100644 index 000000000000..b1f0001a4733 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/PagingCursors.cs @@ -0,0 +1,37 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record PagingCursors : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Cursor for the next page of results. + /// + [JsonPropertyName("next")] + public required string Next { get; set; } + + /// + /// Cursor for the previous page of results. + /// + [JsonPropertyName("previous")] + public string? Previous { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs b/seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs new file mode 100644 index 000000000000..d22a26079f4e --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] +[Serializable] +public readonly record struct RuleExecutionContext : IStringEnum +{ + public static readonly RuleExecutionContext Prod = new(Values.Prod); + + public static readonly RuleExecutionContext Staging = new(Values.Staging); + + public static readonly RuleExecutionContext Dev = new(Values.Dev); + + public RuleExecutionContext(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleExecutionContext FromCustom(string value) + { + return new RuleExecutionContext(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleExecutionContext value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleExecutionContext value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleExecutionContext value) => value.Value; + + public static explicit operator RuleExecutionContext(string value) => new(value); + + internal class RuleExecutionContextSerializer : JsonConverter + { + public override RuleExecutionContext Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleExecutionContext ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleExecutionContext(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleExecutionContext value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Prod = "prod"; + + public const string Staging = "staging"; + + public const string Dev = "dev"; + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleResponse.cs b/seed/csharp-model/allof/src/SeedApi/RuleResponse.cs new file mode 100644 index 000000000000..6459faa88dfd --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/RuleResponse.cs @@ -0,0 +1,65 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("status")] + public required RuleResponseStatus Status { get; set; } + + [JsonPropertyName("executionContext")] + public RuleExecutionContext? ExecutionContext { get; set; } + + /// + /// The user who created this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdBy")] + public string? CreatedBy { get; set; } + + /// + /// When this resource was created. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("createdDateTime")] + public DateTime? CreatedDateTime { get; set; } + + /// + /// The user who last modified this resource. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedBy")] + public string? ModifiedBy { get; set; } + + /// + /// When this resource was last modified. + /// + [JsonAccess(JsonAccessType.ReadOnly)] + [JsonPropertyName("modifiedDateTime")] + public DateTime? ModifiedDateTime { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs b/seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs new file mode 100644 index 000000000000..9b587cdbcba0 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs @@ -0,0 +1,119 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] +[Serializable] +public readonly record struct RuleResponseStatus : IStringEnum +{ + public static readonly RuleResponseStatus Active = new(Values.Active); + + public static readonly RuleResponseStatus Inactive = new(Values.Inactive); + + public static readonly RuleResponseStatus Draft = new(Values.Draft); + + public RuleResponseStatus(string value) + { + Value = value; + } + + /// + /// The string value of the enum. + /// + public string Value { get; } + + /// + /// Create a string enum with the given value. + /// + public static RuleResponseStatus FromCustom(string value) + { + return new RuleResponseStatus(value); + } + + public bool Equals(string? other) + { + return Value.Equals(other); + } + + /// + /// Returns the string value of the enum. + /// + public override string ToString() + { + return Value; + } + + public static bool operator ==(RuleResponseStatus value1, string value2) => + value1.Value.Equals(value2); + + public static bool operator !=(RuleResponseStatus value1, string value2) => + !value1.Value.Equals(value2); + + public static explicit operator string(RuleResponseStatus value) => value.Value; + + public static explicit operator RuleResponseStatus(string value) => new(value); + + internal class RuleResponseStatusSerializer : JsonConverter + { + public override RuleResponseStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON value could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void Write( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WriteStringValue(value.Value); + } + + public override RuleResponseStatus ReadAsPropertyName( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new global::System.Exception( + "The JSON property name could not be read as a string." + ); + return new RuleResponseStatus(stringValue); + } + + public override void WriteAsPropertyName( + Utf8JsonWriter writer, + RuleResponseStatus value, + JsonSerializerOptions options + ) + { + writer.WritePropertyName(value.Value); + } + } + + /// + /// Constant strings for enum values + /// + [Serializable] + public static class Values + { + public const string Active = "active"; + + public const string Inactive = "inactive"; + + public const string Draft = "draft"; + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleType.cs b/seed/csharp-model/allof/src/SeedApi/RuleType.cs new file mode 100644 index 000000000000..578b90315dde --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/RuleType.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleType : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs b/seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs new file mode 100644 index 000000000000..fa14fff1bde0 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record RuleTypeSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props b/seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props new file mode 100644 index 000000000000..17a84cada530 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props @@ -0,0 +1,20 @@ + + + + diff --git a/seed/csharp-model/allof/src/SeedApi/SeedApi.csproj b/seed/csharp-model/allof/src/SeedApi/SeedApi.csproj new file mode 100644 index 000000000000..5393dfc14261 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/SeedApi.csproj @@ -0,0 +1,60 @@ + + + net462;net8.0;net9.0;netstandard2.0 + enable + 12 + enable + 0.0.1 + $(Version) + $(Version) + README.md + https://github.com/allof/fern + true + + + + false + + + $(DefineConstants);USE_PORTABLE_DATE_ONLY + true + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + <_Parameter1>SeedApi.Test + + + + + diff --git a/seed/csharp-model/allof/src/SeedApi/User.cs b/seed/csharp-model/allof/src/SeedApi/User.cs new file mode 100644 index 000000000000..abc389c0a6b6 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/User.cs @@ -0,0 +1,31 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record User : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("email")] + public required string Email { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs b/seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs new file mode 100644 index 000000000000..942b822f3dc4 --- /dev/null +++ b/seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs @@ -0,0 +1,34 @@ +using global::System.Text.Json; +using global::System.Text.Json.Serialization; +using SeedApi.Core; + +namespace SeedApi; + +[Serializable] +public record UserSearchResponse : IJsonOnDeserialized +{ + [JsonExtensionData] + private readonly IDictionary _extensionData = + new Dictionary(); + + /// + /// Current page of results from the requested resource. + /// + [JsonPropertyName("results")] + public IEnumerable? Results { get; set; } + + [JsonPropertyName("paging")] + public required PagingCursors Paging { get; set; } + + [JsonIgnore] + public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); + + void IJsonOnDeserialized.OnDeserialized() => + AdditionalProperties.CopyFromExtensionData(_extensionData); + + /// + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/allof-inline/.fern/metadata.json b/seed/csharp-sdk/allof-inline/.fern/metadata.json index 91d0855bee07..d119d1639273 100644 --- a/seed/csharp-sdk/allof-inline/.fern/metadata.json +++ b/seed/csharp-sdk/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-csharp-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "generatorConfig": {}, "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs index 39ac12fc18b2..8ad25e785bb1 100644 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs @@ -1052,7 +1052,8 @@ private static string GetBoundary(MultipartFormDataContent content) .Headers.ContentType?.Parameters.Single(p => p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) ) - .Value?.Trim('"') ?? throw new global::System.Exception("Boundary not found"); + .Value?.Trim('"') + ?? throw new global::System.Exception("Boundary not found"); } private static SeedApi.Core.MultipartFormRequest CreateMultipartFormRequest() @@ -1084,16 +1085,16 @@ private class SimpleObject public int Count { get; set; } = 42; public char Initial { get; set; } = 'A'; public IEnumerable Values { get; set; } = - [ - "data", - DateOnly.Parse("2023-10-01"), - TimeOnly.Parse("12:00:00"), - TimeSpan.FromHours(1), - Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), - true, - 42, - 'A', - ]; + [ + "data", + DateOnly.Parse("2023-10-01"), + TimeOnly.Parse("12:00:00"), + TimeSpan.FromHours(1), + Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), + true, + 42, + 'A', + ]; } private class ComplexObject diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj index a4dfe85227f6..77e1a9943739 100644 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj +++ b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj @@ -8,6 +8,7 @@ true true + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -26,8 +27,10 @@ + + diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs index edf973008a15..d42791fcc0e0 100644 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs @@ -73,7 +73,8 @@ HttpRequestMessage request .Headers.ContentType?.Parameters.First(p => p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) ) - .Value?.Trim('"') ?? Guid.NewGuid().ToString(); + .Value?.Trim('"') + ?? Guid.NewGuid().ToString(); var newMultipartContent = oldMultipartFormContent switch { MultipartFormDataContent => new MultipartFormDataContent(originalBoundary), diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj index d74cb5af93ca..6ba194fdf143 100644 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj +++ b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj @@ -11,6 +11,7 @@ https://github.com/allof-inline/fern true + false @@ -44,13 +45,16 @@ + + <_Parameter1>SeedApi.Test + diff --git a/seed/go-model/allof-inline/.fern/metadata.json b/seed/go-model/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..d4a30f5a7178 --- /dev/null +++ b/seed/go-model/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-go-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "v0.0.1" +} \ No newline at end of file diff --git a/seed/go-model/allof-inline/doc.go b/seed/go-model/allof-inline/doc.go new file mode 100644 index 000000000000..a57cc3f67402 --- /dev/null +++ b/seed/go-model/allof-inline/doc.go @@ -0,0 +1 @@ +package api \ No newline at end of file diff --git a/seed/go-model/allof-inline/go.mod b/seed/go-model/allof-inline/go.mod new file mode 100644 index 000000000000..f064631d08ad --- /dev/null +++ b/seed/go-model/allof-inline/go.mod @@ -0,0 +1,14 @@ +module github.com/allof-inline/fern + +go 1.21 + +toolchain go1.23.8 + +require github.com/stretchr/testify v1.8.4 + +require gopkg.in/yaml.v3 v3.0.1 // indirect + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/seed/go-model/allof-inline/go.sum b/seed/go-model/allof-inline/go.sum new file mode 100644 index 000000000000..fa4b6e6825c4 --- /dev/null +++ b/seed/go-model/allof-inline/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-model/allof-inline/internal/extra_properties.go b/seed/go-model/allof-inline/internal/extra_properties.go new file mode 100644 index 000000000000..57517691f132 --- /dev/null +++ b/seed/go-model/allof-inline/internal/extra_properties.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler any, key string, value any) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]any{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler any, extraProperties map[string]any) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value any, exclude ...string) (map[string]any, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]any + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value any) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-model/allof-inline/internal/extra_properties_test.go b/seed/go-model/allof-inline/internal/extra_properties_test.go new file mode 100644 index 000000000000..0d46257763fb --- /dev/null +++ b/seed/go-model/allof-inline/internal/extra_properties_test.go @@ -0,0 +1,228 @@ +package internal + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler any + giveExtraProperties map[string]any + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]any{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]any{42: "value"}, + giveExtraProperties: map[string]any{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]any{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]any{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]any{}, + giveExtraProperties: map[string]any{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]any{}, + giveExtraProperties: map[string]any{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{ + "user": map[string]any{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{ + "metadata": map[string]any{ + "ip": "127.0.0.1", + }, + "user": map[string]any{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]any{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]any) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-model/allof-inline/internal/stringer.go b/seed/go-model/allof-inline/internal/stringer.go new file mode 100644 index 000000000000..0be54d1b5359 --- /dev/null +++ b/seed/go-model/allof-inline/internal/stringer.go @@ -0,0 +1,13 @@ +package internal + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value any) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/allof-inline/internal/time.go b/seed/go-model/allof-inline/internal/time.go new file mode 100644 index 000000000000..57f901a35ed8 --- /dev/null +++ b/seed/go-model/allof-inline/internal/time.go @@ -0,0 +1,165 @@ +package internal + +import ( + "encoding/json" + "fmt" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + // If the value is not a string, check if it is a number (unix epoch seconds). + var epoch int64 + if numErr := json.Unmarshal(data, &epoch); numErr == nil { + t := time.Unix(epoch, 0).UTC() + *d = DateTime{t: &t} + return nil + } + return err + } + + // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). + parsedTime, err := time.Parse(time.RFC3339Nano, raw) + if err == nil { + *d = DateTime{t: &parsedTime} + return nil + } + rfc3339NanoErr := err + + // Fall back to ISO 8601 without timezone (assume UTC). + parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + iso8601Err := err + + // Fall back to date-only format. + parsedTime, err = time.Parse("2006-01-02", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + dateOnlyErr := err + + return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) +} diff --git a/seed/go-model/allof-inline/snippet.json b/seed/go-model/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/go-model/allof-inline/types.go b/seed/go-model/allof-inline/types.go new file mode 100644 index 000000000000..51b323994e16 --- /dev/null +++ b/seed/go-model/allof-inline/types.go @@ -0,0 +1,1246 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + json "encoding/json" + fmt "fmt" + time "time" + + internal "github.com/allof-inline/fern/internal" +) + +type PaginatedResult struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []any `json:"results" url:"results"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (p *PaginatedResult) GetPaging() *PagingCursors { + if p == nil { + return nil + } + return p.Paging +} + +func (p *PaginatedResult) GetResults() []any { + if p == nil { + return nil + } + return p.Results +} + +func (p *PaginatedResult) GetExtraProperties() map[string]any { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PaginatedResult) UnmarshalJSON( + data []byte, +) error { + type unmarshaler PaginatedResult + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PaginatedResult(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PaginatedResult) String() string { + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +type PagingCursors struct { + // Cursor for the next page of results. + Next string `json:"next" url:"next"` + // Cursor for the previous page of results. + Previous *string `json:"previous,omitempty" url:"previous,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (p *PagingCursors) GetNext() string { + if p == nil { + return "" + } + return p.Next +} + +func (p *PagingCursors) GetPrevious() *string { + if p == nil { + return nil + } + return p.Previous +} + +func (p *PagingCursors) GetExtraProperties() map[string]any { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PagingCursors) UnmarshalJSON( + data []byte, +) error { + type unmarshaler PagingCursors + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PagingCursors(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PagingCursors) String() string { + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +// Execution environment for a rule. +type RuleExecutionContext string + +const ( + RuleExecutionContextProd = "prod" + RuleExecutionContextStaging = "staging" + RuleExecutionContextDev = "dev" +) + +func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { + switch s { + case "prod": + return RuleExecutionContextProd, nil + case "staging": + return RuleExecutionContextStaging, nil + case "dev": + return RuleExecutionContextDev, nil + } + var t RuleExecutionContext + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleExecutionContext) Ptr() *RuleExecutionContext { + return &r +} + +// Common audit metadata. +type AuditInfo struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (a *AuditInfo) GetCreatedBy() *string { + if a == nil { + return nil + } + return a.CreatedBy +} + +func (a *AuditInfo) GetCreatedDateTime() *time.Time { + if a == nil { + return nil + } + return a.CreatedDateTime +} + +func (a *AuditInfo) GetModifiedBy() *string { + if a == nil { + return nil + } + return a.ModifiedBy +} + +func (a *AuditInfo) GetModifiedDateTime() *time.Time { + if a == nil { + return nil + } + return a.ModifiedDateTime +} + +func (a *AuditInfo) GetExtraProperties() map[string]any { + if a == nil { + return nil + } + return a.extraProperties +} + +func (a *AuditInfo) UnmarshalJSON( + data []byte, +) error { + type embed AuditInfo + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *a = AuditInfo(unmarshaler.embed) + a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *a) + if err != nil { + return err + } + a.extraProperties = extraProperties + a.rawJSON = json.RawMessage(data) + return nil +} + +func (a *AuditInfo) MarshalJSON() ([]byte, error) { + type embed AuditInfo + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*a), + CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), + } + return json.Marshal(marshaler) +} + +func (a *AuditInfo) String() string { + if len(a.rawJSON) > 0 { + if value, err := internal.StringifyJSON(a.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type RuleType struct { + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Description *string `json:"description,omitempty" url:"description,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (r *RuleType) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleType) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleType) GetDescription() *string { + if r == nil { + return nil + } + return r.Description +} + +func (r *RuleType) GetExtraProperties() map[string]any { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleType) UnmarshalJSON( + data []byte, +) error { + type unmarshaler RuleType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleType(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleType) String() string { + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type RuleTypeSearchResponse struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { + if r == nil { + return nil + } + return r.Paging +} + +func (r *RuleTypeSearchResponse) GetResults() []*RuleType { + if r == nil { + return nil + } + return r.Results +} + +func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]any { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleTypeSearchResponse) UnmarshalJSON( + data []byte, +) error { + type unmarshaler RuleTypeSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleTypeSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleTypeSearchResponse) String() string { + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type User struct { + ID string `json:"id" url:"id"` + Email string `json:"email" url:"email"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (u *User) GetID() string { + if u == nil { + return "" + } + return u.ID +} + +func (u *User) GetEmail() string { + if u == nil { + return "" + } + return u.Email +} + +func (u *User) GetExtraProperties() map[string]any { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *User) UnmarshalJSON( + data []byte, +) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *User) String() string { + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} + +type UserSearchResponse struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []*User `json:"results,omitempty" url:"results,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (u *UserSearchResponse) GetPaging() *PagingCursors { + if u == nil { + return nil + } + return u.Paging +} + +func (u *UserSearchResponse) GetResults() []*User { + if u == nil { + return nil + } + return u.Results +} + +func (u *UserSearchResponse) GetExtraProperties() map[string]any { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *UserSearchResponse) UnmarshalJSON( + data []byte, +) error { + type unmarshaler UserSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UserSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *UserSearchResponse) String() string { + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} + +type RuleResponseStatus string + +const ( + RuleResponseStatusActive = "active" + RuleResponseStatusInactive = "inactive" + RuleResponseStatusDraft = "draft" +) + +func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { + switch s { + case "active": + return RuleResponseStatusActive, nil + case "inactive": + return RuleResponseStatusInactive, nil + case "draft": + return RuleResponseStatusDraft, nil + } + var t RuleResponseStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleResponseStatus) Ptr() *RuleResponseStatus { + return &r +} + +type RuleResponse struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Status *RuleResponseStatus `json:"status" url:"status"` + ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (r *RuleResponse) GetCreatedBy() *string { + if r == nil { + return nil + } + return r.CreatedBy +} + +func (r *RuleResponse) GetCreatedDateTime() *time.Time { + if r == nil { + return nil + } + return r.CreatedDateTime +} + +func (r *RuleResponse) GetModifiedBy() *string { + if r == nil { + return nil + } + return r.ModifiedBy +} + +func (r *RuleResponse) GetModifiedDateTime() *time.Time { + if r == nil { + return nil + } + return r.ModifiedDateTime +} + +func (r *RuleResponse) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleResponse) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleResponse) GetStatus() *RuleResponseStatus { + if r == nil { + return nil + } + return r.Status +} + +func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { + if r == nil { + return nil + } + return r.ExecutionContext +} + +func (r *RuleResponse) GetExtraProperties() map[string]any { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleResponse) UnmarshalJSON( + data []byte, +) error { + type embed RuleResponse + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*r), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *r = RuleResponse(unmarshaler.embed) + r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleResponse) MarshalJSON() ([]byte, error) { + type embed RuleResponse + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*r), + CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), + } + return json.Marshal(marshaler) +} + +func (r *RuleResponse) String() string { + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type Identifiable struct { + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Identifiable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (i *Identifiable) GetID() string { + if i == nil { + return "" + } + return i.ID +} + +func (i *Identifiable) GetName() *string { + if i == nil { + return nil + } + return i.Name +} + +func (i *Identifiable) GetExtraProperties() map[string]any { + if i == nil { + return nil + } + return i.extraProperties +} + +func (i *Identifiable) UnmarshalJSON( + data []byte, +) error { + type unmarshaler Identifiable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *i = Identifiable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *i) + if err != nil { + return err + } + i.extraProperties = extraProperties + i.rawJSON = json.RawMessage(data) + return nil +} + +func (i *Identifiable) String() string { + if len(i.rawJSON) > 0 { + if value, err := internal.StringifyJSON(i.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type Describable struct { + // Display name from Describable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (d *Describable) GetName() *string { + if d == nil { + return nil + } + return d.Name +} + +func (d *Describable) GetSummary() *string { + if d == nil { + return nil + } + return d.Summary +} + +func (d *Describable) GetExtraProperties() map[string]any { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *Describable) UnmarshalJSON( + data []byte, +) error { + type unmarshaler Describable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Describable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *Describable) String() string { + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type CombinedEntityStatus string + +const ( + CombinedEntityStatusActive = "active" + CombinedEntityStatusArchived = "archived" +) + +func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { + switch s { + case "active": + return CombinedEntityStatusActive, nil + case "archived": + return CombinedEntityStatusArchived, nil + } + var t CombinedEntityStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { + return &c +} + +type CombinedEntity struct { + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Describable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + Status *CombinedEntityStatus `json:"status" url:"status"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (c *CombinedEntity) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *CombinedEntity) GetName() *string { + if c == nil { + return nil + } + return c.Name +} + +func (c *CombinedEntity) GetSummary() *string { + if c == nil { + return nil + } + return c.Summary +} + +func (c *CombinedEntity) GetStatus() *CombinedEntityStatus { + if c == nil { + return nil + } + return c.Status +} + +func (c *CombinedEntity) GetExtraProperties() map[string]any { + if c == nil { + return nil + } + return c.extraProperties +} + +func (c *CombinedEntity) UnmarshalJSON( + data []byte, +) error { + type unmarshaler CombinedEntity + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CombinedEntity(value) + extraProperties, err := internal.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + c.rawJSON = json.RawMessage(data) + return nil +} + +func (c *CombinedEntity) String() string { + if len(c.rawJSON) > 0 { + if value, err := internal.StringifyJSON(c.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type BaseOrgMetadata struct { + // Deployment region from BaseOrg. + Region string `json:"region" url:"region"` + // Subscription tier. + Tier *string `json:"tier,omitempty" url:"tier,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (b *BaseOrgMetadata) GetRegion() string { + if b == nil { + return "" + } + return b.Region +} + +func (b *BaseOrgMetadata) GetTier() *string { + if b == nil { + return nil + } + return b.Tier +} + +func (b *BaseOrgMetadata) GetExtraProperties() map[string]any { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrgMetadata) UnmarshalJSON( + data []byte, +) error { + type unmarshaler BaseOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrgMetadata) String() string { + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +type BaseOrg struct { + ID string `json:"id" url:"id"` + Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (b *BaseOrg) GetID() string { + if b == nil { + return "" + } + return b.ID +} + +func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { + if b == nil { + return nil + } + return b.Metadata +} + +func (b *BaseOrg) GetExtraProperties() map[string]any { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrg) UnmarshalJSON( + data []byte, +) error { + type unmarshaler BaseOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrg) String() string { + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +type DetailedOrgMetadata struct { + // Deployment region from DetailedOrg. + Region string `json:"region" url:"region"` + // Custom domain name. + Domain *string `json:"domain,omitempty" url:"domain,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (d *DetailedOrgMetadata) GetRegion() string { + if d == nil { + return "" + } + return d.Region +} + +func (d *DetailedOrgMetadata) GetDomain() *string { + if d == nil { + return nil + } + return d.Domain +} + +func (d *DetailedOrgMetadata) GetExtraProperties() map[string]any { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrgMetadata) UnmarshalJSON( + data []byte, +) error { + type unmarshaler DetailedOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrgMetadata) String() string { + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type DetailedOrg struct { + Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { + if d == nil { + return nil + } + return d.Metadata +} + +func (d *DetailedOrg) GetExtraProperties() map[string]any { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrg) UnmarshalJSON( + data []byte, +) error { + type unmarshaler DetailedOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrg) String() string { + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type OrganizationMetadata struct { + // Deployment region from DetailedOrg. + Region string `json:"region" url:"region"` + // Custom domain name. + Domain *string `json:"domain,omitempty" url:"domain,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (o *OrganizationMetadata) GetRegion() string { + if o == nil { + return "" + } + return o.Region +} + +func (o *OrganizationMetadata) GetDomain() *string { + if o == nil { + return nil + } + return o.Domain +} + +func (o *OrganizationMetadata) GetExtraProperties() map[string]any { + if o == nil { + return nil + } + return o.extraProperties +} + +func (o *OrganizationMetadata) UnmarshalJSON( + data []byte, +) error { + type unmarshaler OrganizationMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *o = OrganizationMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *o) + if err != nil { + return err + } + o.extraProperties = extraProperties + o.rawJSON = json.RawMessage(data) + return nil +} + +func (o *OrganizationMetadata) String() string { + if len(o.rawJSON) > 0 { + if value, err := internal.StringifyJSON(o.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type Organization struct { + ID string `json:"id" url:"id"` + Metadata *OrganizationMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + Name string `json:"name" url:"name"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (o *Organization) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *Organization) GetMetadata() *OrganizationMetadata { + if o == nil { + return nil + } + return o.Metadata +} + +func (o *Organization) GetName() string { + if o == nil { + return "" + } + return o.Name +} + +func (o *Organization) GetExtraProperties() map[string]any { + if o == nil { + return nil + } + return o.extraProperties +} + +func (o *Organization) UnmarshalJSON( + data []byte, +) error { + type unmarshaler Organization + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *o = Organization(value) + extraProperties, err := internal.ExtractExtraProperties(data, *o) + if err != nil { + return err + } + o.extraProperties = extraProperties + o.rawJSON = json.RawMessage(data) + return nil +} + +func (o *Organization) String() string { + if len(o.rawJSON) > 0 { + if value, err := internal.StringifyJSON(o.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} diff --git a/seed/go-model/allof/.fern/metadata.json b/seed/go-model/allof/.fern/metadata.json new file mode 100644 index 000000000000..d4a30f5a7178 --- /dev/null +++ b/seed/go-model/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-go-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "v0.0.1" +} \ No newline at end of file diff --git a/seed/go-model/allof/doc.go b/seed/go-model/allof/doc.go new file mode 100644 index 000000000000..a57cc3f67402 --- /dev/null +++ b/seed/go-model/allof/doc.go @@ -0,0 +1 @@ +package api \ No newline at end of file diff --git a/seed/go-model/allof/go.mod b/seed/go-model/allof/go.mod new file mode 100644 index 000000000000..6e24f1795543 --- /dev/null +++ b/seed/go-model/allof/go.mod @@ -0,0 +1,14 @@ +module github.com/allof/fern + +go 1.21 + +toolchain go1.23.8 + +require github.com/stretchr/testify v1.8.4 + +require gopkg.in/yaml.v3 v3.0.1 // indirect + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/seed/go-model/allof/go.sum b/seed/go-model/allof/go.sum new file mode 100644 index 000000000000..fa4b6e6825c4 --- /dev/null +++ b/seed/go-model/allof/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-model/allof/internal/extra_properties.go b/seed/go-model/allof/internal/extra_properties.go new file mode 100644 index 000000000000..57517691f132 --- /dev/null +++ b/seed/go-model/allof/internal/extra_properties.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler any, key string, value any) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]any{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler any, extraProperties map[string]any) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value any, exclude ...string) (map[string]any, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]any + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value any) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-model/allof/internal/extra_properties_test.go b/seed/go-model/allof/internal/extra_properties_test.go new file mode 100644 index 000000000000..0d46257763fb --- /dev/null +++ b/seed/go-model/allof/internal/extra_properties_test.go @@ -0,0 +1,228 @@ +package internal + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler any + giveExtraProperties map[string]any + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]any{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]any{42: "value"}, + giveExtraProperties: map[string]any{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]any{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]any{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]any{}, + giveExtraProperties: map[string]any{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]any{}, + giveExtraProperties: map[string]any{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{ + "user": map[string]any{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]any{"key": "value"}, + giveExtraProperties: map[string]any{ + "metadata": map[string]any{ + "ip": "127.0.0.1", + }, + "user": map[string]any{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]any{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]any) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-model/allof/internal/stringer.go b/seed/go-model/allof/internal/stringer.go new file mode 100644 index 000000000000..0be54d1b5359 --- /dev/null +++ b/seed/go-model/allof/internal/stringer.go @@ -0,0 +1,13 @@ +package internal + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value any) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/allof/internal/time.go b/seed/go-model/allof/internal/time.go new file mode 100644 index 000000000000..57f901a35ed8 --- /dev/null +++ b/seed/go-model/allof/internal/time.go @@ -0,0 +1,165 @@ +package internal + +import ( + "encoding/json" + "fmt" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + // If the value is not a string, check if it is a number (unix epoch seconds). + var epoch int64 + if numErr := json.Unmarshal(data, &epoch); numErr == nil { + t := time.Unix(epoch, 0).UTC() + *d = DateTime{t: &t} + return nil + } + return err + } + + // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). + parsedTime, err := time.Parse(time.RFC3339Nano, raw) + if err == nil { + *d = DateTime{t: &parsedTime} + return nil + } + rfc3339NanoErr := err + + // Fall back to ISO 8601 without timezone (assume UTC). + parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + iso8601Err := err + + // Fall back to date-only format. + parsedTime, err = time.Parse("2006-01-02", raw) + if err == nil { + parsedTime = parsedTime.UTC() + *d = DateTime{t: &parsedTime} + return nil + } + dateOnlyErr := err + + return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) +} diff --git a/seed/go-model/allof/snippet.json b/seed/go-model/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/go-model/allof/types.go b/seed/go-model/allof/types.go new file mode 100644 index 000000000000..dca6f261ecfb --- /dev/null +++ b/seed/go-model/allof/types.go @@ -0,0 +1,1185 @@ +// Code generated by Fern. DO NOT EDIT. + +package api + +import ( + json "encoding/json" + fmt "fmt" + time "time" + + internal "github.com/allof/fern/internal" +) + +type PaginatedResult struct { + Paging *PagingCursors `json:"paging" url:"paging"` + // Current page of results from the requested resource. + Results []any `json:"results" url:"results"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (p *PaginatedResult) GetPaging() *PagingCursors { + if p == nil { + return nil + } + return p.Paging +} + +func (p *PaginatedResult) GetResults() []any { + if p == nil { + return nil + } + return p.Results +} + +func (p *PaginatedResult) GetExtraProperties() map[string]any { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PaginatedResult) UnmarshalJSON( + data []byte, +) error { + type unmarshaler PaginatedResult + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PaginatedResult(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PaginatedResult) String() string { + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +type PagingCursors struct { + // Cursor for the next page of results. + Next string `json:"next" url:"next"` + // Cursor for the previous page of results. + Previous *string `json:"previous,omitempty" url:"previous,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (p *PagingCursors) GetNext() string { + if p == nil { + return "" + } + return p.Next +} + +func (p *PagingCursors) GetPrevious() *string { + if p == nil { + return nil + } + return p.Previous +} + +func (p *PagingCursors) GetExtraProperties() map[string]any { + if p == nil { + return nil + } + return p.extraProperties +} + +func (p *PagingCursors) UnmarshalJSON( + data []byte, +) error { + type unmarshaler PagingCursors + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PagingCursors(value) + extraProperties, err := internal.ExtractExtraProperties(data, *p) + if err != nil { + return err + } + p.extraProperties = extraProperties + p.rawJSON = json.RawMessage(data) + return nil +} + +func (p *PagingCursors) String() string { + if len(p.rawJSON) > 0 { + if value, err := internal.StringifyJSON(p.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} + +// Execution environment for a rule. +type RuleExecutionContext string + +const ( + RuleExecutionContextProd = "prod" + RuleExecutionContextStaging = "staging" + RuleExecutionContextDev = "dev" +) + +func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { + switch s { + case "prod": + return RuleExecutionContextProd, nil + case "staging": + return RuleExecutionContextStaging, nil + case "dev": + return RuleExecutionContextDev, nil + } + var t RuleExecutionContext + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleExecutionContext) Ptr() *RuleExecutionContext { + return &r +} + +// Common audit metadata. +type AuditInfo struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (a *AuditInfo) GetCreatedBy() *string { + if a == nil { + return nil + } + return a.CreatedBy +} + +func (a *AuditInfo) GetCreatedDateTime() *time.Time { + if a == nil { + return nil + } + return a.CreatedDateTime +} + +func (a *AuditInfo) GetModifiedBy() *string { + if a == nil { + return nil + } + return a.ModifiedBy +} + +func (a *AuditInfo) GetModifiedDateTime() *time.Time { + if a == nil { + return nil + } + return a.ModifiedDateTime +} + +func (a *AuditInfo) GetExtraProperties() map[string]any { + if a == nil { + return nil + } + return a.extraProperties +} + +func (a *AuditInfo) UnmarshalJSON( + data []byte, +) error { + type embed AuditInfo + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *a = AuditInfo(unmarshaler.embed) + a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *a) + if err != nil { + return err + } + a.extraProperties = extraProperties + a.rawJSON = json.RawMessage(data) + return nil +} + +func (a *AuditInfo) MarshalJSON() ([]byte, error) { + type embed AuditInfo + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*a), + CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), + } + return json.Marshal(marshaler) +} + +func (a *AuditInfo) String() string { + if len(a.rawJSON) > 0 { + if value, err := internal.StringifyJSON(a.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type RuleType struct { + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Description *string `json:"description,omitempty" url:"description,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (r *RuleType) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleType) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleType) GetDescription() *string { + if r == nil { + return nil + } + return r.Description +} + +func (r *RuleType) GetExtraProperties() map[string]any { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleType) UnmarshalJSON( + data []byte, +) error { + type unmarshaler RuleType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleType(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleType) String() string { + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type RuleTypeSearchResponse struct { + // Current page of results from the requested resource. + Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` + Paging *PagingCursors `json:"paging" url:"paging"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (r *RuleTypeSearchResponse) GetResults() []*RuleType { + if r == nil { + return nil + } + return r.Results +} + +func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { + if r == nil { + return nil + } + return r.Paging +} + +func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]any { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleTypeSearchResponse) UnmarshalJSON( + data []byte, +) error { + type unmarshaler RuleTypeSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RuleTypeSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleTypeSearchResponse) String() string { + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type User struct { + ID string `json:"id" url:"id"` + Email string `json:"email" url:"email"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (u *User) GetID() string { + if u == nil { + return "" + } + return u.ID +} + +func (u *User) GetEmail() string { + if u == nil { + return "" + } + return u.Email +} + +func (u *User) GetExtraProperties() map[string]any { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *User) UnmarshalJSON( + data []byte, +) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *User) String() string { + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} + +type UserSearchResponse struct { + // Current page of results from the requested resource. + Results []*User `json:"results,omitempty" url:"results,omitempty"` + Paging *PagingCursors `json:"paging" url:"paging"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (u *UserSearchResponse) GetResults() []*User { + if u == nil { + return nil + } + return u.Results +} + +func (u *UserSearchResponse) GetPaging() *PagingCursors { + if u == nil { + return nil + } + return u.Paging +} + +func (u *UserSearchResponse) GetExtraProperties() map[string]any { + if u == nil { + return nil + } + return u.extraProperties +} + +func (u *UserSearchResponse) UnmarshalJSON( + data []byte, +) error { + type unmarshaler UserSearchResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UserSearchResponse(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *UserSearchResponse) String() string { + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} + +type RuleResponseStatus string + +const ( + RuleResponseStatusActive = "active" + RuleResponseStatusInactive = "inactive" + RuleResponseStatusDraft = "draft" +) + +func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { + switch s { + case "active": + return RuleResponseStatusActive, nil + case "inactive": + return RuleResponseStatusInactive, nil + case "draft": + return RuleResponseStatusDraft, nil + } + var t RuleResponseStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (r RuleResponseStatus) Ptr() *RuleResponseStatus { + return &r +} + +type RuleResponse struct { + // The user who created this resource. + CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` + // When this resource was created. + CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` + // The user who last modified this resource. + ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` + // When this resource was last modified. + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` + ID string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Status *RuleResponseStatus `json:"status" url:"status"` + ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (r *RuleResponse) GetCreatedBy() *string { + if r == nil { + return nil + } + return r.CreatedBy +} + +func (r *RuleResponse) GetCreatedDateTime() *time.Time { + if r == nil { + return nil + } + return r.CreatedDateTime +} + +func (r *RuleResponse) GetModifiedBy() *string { + if r == nil { + return nil + } + return r.ModifiedBy +} + +func (r *RuleResponse) GetModifiedDateTime() *time.Time { + if r == nil { + return nil + } + return r.ModifiedDateTime +} + +func (r *RuleResponse) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RuleResponse) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *RuleResponse) GetStatus() *RuleResponseStatus { + if r == nil { + return nil + } + return r.Status +} + +func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { + if r == nil { + return nil + } + return r.ExecutionContext +} + +func (r *RuleResponse) GetExtraProperties() map[string]any { + if r == nil { + return nil + } + return r.extraProperties +} + +func (r *RuleResponse) UnmarshalJSON( + data []byte, +) error { + type embed RuleResponse + var unmarshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*r), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *r = RuleResponse(unmarshaler.embed) + r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() + r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() + extraProperties, err := internal.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + r.rawJSON = json.RawMessage(data) + return nil +} + +func (r *RuleResponse) MarshalJSON() ([]byte, error) { + type embed RuleResponse + var marshaler = struct { + embed + CreatedDateTime *internal.DateTime `json:"createdDateTime"` + ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` + }{ + embed: embed(*r), + CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), + ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), + } + return json.Marshal(marshaler) +} + +func (r *RuleResponse) String() string { + if len(r.rawJSON) > 0 { + if value, err := internal.StringifyJSON(r.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type Identifiable struct { + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Identifiable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (i *Identifiable) GetID() string { + if i == nil { + return "" + } + return i.ID +} + +func (i *Identifiable) GetName() *string { + if i == nil { + return nil + } + return i.Name +} + +func (i *Identifiable) GetExtraProperties() map[string]any { + if i == nil { + return nil + } + return i.extraProperties +} + +func (i *Identifiable) UnmarshalJSON( + data []byte, +) error { + type unmarshaler Identifiable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *i = Identifiable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *i) + if err != nil { + return err + } + i.extraProperties = extraProperties + i.rawJSON = json.RawMessage(data) + return nil +} + +func (i *Identifiable) String() string { + if len(i.rawJSON) > 0 { + if value, err := internal.StringifyJSON(i.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type Describable struct { + // Display name from Describable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (d *Describable) GetName() *string { + if d == nil { + return nil + } + return d.Name +} + +func (d *Describable) GetSummary() *string { + if d == nil { + return nil + } + return d.Summary +} + +func (d *Describable) GetExtraProperties() map[string]any { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *Describable) UnmarshalJSON( + data []byte, +) error { + type unmarshaler Describable + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Describable(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *Describable) String() string { + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type CombinedEntityStatus string + +const ( + CombinedEntityStatusActive = "active" + CombinedEntityStatusArchived = "archived" +) + +func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { + switch s { + case "active": + return CombinedEntityStatusActive, nil + case "archived": + return CombinedEntityStatusArchived, nil + } + var t CombinedEntityStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { + return &c +} + +type CombinedEntity struct { + Status *CombinedEntityStatus `json:"status" url:"status"` + // Unique identifier. + ID string `json:"id" url:"id"` + // Display name from Identifiable. + Name *string `json:"name,omitempty" url:"name,omitempty"` + // A short summary. + Summary *string `json:"summary,omitempty" url:"summary,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (c *CombinedEntity) GetStatus() *CombinedEntityStatus { + if c == nil { + return nil + } + return c.Status +} + +func (c *CombinedEntity) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *CombinedEntity) GetName() *string { + if c == nil { + return nil + } + return c.Name +} + +func (c *CombinedEntity) GetSummary() *string { + if c == nil { + return nil + } + return c.Summary +} + +func (c *CombinedEntity) GetExtraProperties() map[string]any { + if c == nil { + return nil + } + return c.extraProperties +} + +func (c *CombinedEntity) UnmarshalJSON( + data []byte, +) error { + type unmarshaler CombinedEntity + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CombinedEntity(value) + extraProperties, err := internal.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + c.rawJSON = json.RawMessage(data) + return nil +} + +func (c *CombinedEntity) String() string { + if len(c.rawJSON) > 0 { + if value, err := internal.StringifyJSON(c.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type BaseOrgMetadata struct { + // Deployment region from BaseOrg. + Region string `json:"region" url:"region"` + // Subscription tier. + Tier *string `json:"tier,omitempty" url:"tier,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (b *BaseOrgMetadata) GetRegion() string { + if b == nil { + return "" + } + return b.Region +} + +func (b *BaseOrgMetadata) GetTier() *string { + if b == nil { + return nil + } + return b.Tier +} + +func (b *BaseOrgMetadata) GetExtraProperties() map[string]any { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrgMetadata) UnmarshalJSON( + data []byte, +) error { + type unmarshaler BaseOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrgMetadata) String() string { + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +type BaseOrg struct { + ID string `json:"id" url:"id"` + Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (b *BaseOrg) GetID() string { + if b == nil { + return "" + } + return b.ID +} + +func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { + if b == nil { + return nil + } + return b.Metadata +} + +func (b *BaseOrg) GetExtraProperties() map[string]any { + if b == nil { + return nil + } + return b.extraProperties +} + +func (b *BaseOrg) UnmarshalJSON( + data []byte, +) error { + type unmarshaler BaseOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *b = BaseOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *b) + if err != nil { + return err + } + b.extraProperties = extraProperties + b.rawJSON = json.RawMessage(data) + return nil +} + +func (b *BaseOrg) String() string { + if len(b.rawJSON) > 0 { + if value, err := internal.StringifyJSON(b.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} + +type DetailedOrgMetadata struct { + // Deployment region from DetailedOrg. + Region string `json:"region" url:"region"` + // Custom domain name. + Domain *string `json:"domain,omitempty" url:"domain,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (d *DetailedOrgMetadata) GetRegion() string { + if d == nil { + return "" + } + return d.Region +} + +func (d *DetailedOrgMetadata) GetDomain() *string { + if d == nil { + return nil + } + return d.Domain +} + +func (d *DetailedOrgMetadata) GetExtraProperties() map[string]any { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrgMetadata) UnmarshalJSON( + data []byte, +) error { + type unmarshaler DetailedOrgMetadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrgMetadata(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrgMetadata) String() string { + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type DetailedOrg struct { + Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { + if d == nil { + return nil + } + return d.Metadata +} + +func (d *DetailedOrg) GetExtraProperties() map[string]any { + if d == nil { + return nil + } + return d.extraProperties +} + +func (d *DetailedOrg) UnmarshalJSON( + data []byte, +) error { + type unmarshaler DetailedOrg + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = DetailedOrg(value) + extraProperties, err := internal.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + d.rawJSON = json.RawMessage(data) + return nil +} + +func (d *DetailedOrg) String() string { + if len(d.rawJSON) > 0 { + if value, err := internal.StringifyJSON(d.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type Organization struct { + Name string `json:"name" url:"name"` + ID string `json:"id" url:"id"` + Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]any + rawJSON json.RawMessage +} + +func (o *Organization) GetName() string { + if o == nil { + return "" + } + return o.Name +} + +func (o *Organization) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *Organization) GetMetadata() *BaseOrgMetadata { + if o == nil { + return nil + } + return o.Metadata +} + +func (o *Organization) GetExtraProperties() map[string]any { + if o == nil { + return nil + } + return o.extraProperties +} + +func (o *Organization) UnmarshalJSON( + data []byte, +) error { + type unmarshaler Organization + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *o = Organization(value) + extraProperties, err := internal.ExtractExtraProperties(data, *o) + if err != nil { + return err + } + o.extraProperties = extraProperties + o.rawJSON = json.RawMessage(data) + return nil +} + +func (o *Organization) String() string { + if len(o.rawJSON) > 0 { + if value, err := internal.StringifyJSON(o.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} diff --git a/seed/go-sdk/allof-inline/.fern/metadata.json b/seed/go-sdk/allof-inline/.fern/metadata.json index 1e0f7d8e54e4..711224a2aaf7 100644 --- a/seed/go-sdk/allof-inline/.fern/metadata.json +++ b/seed/go-sdk/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-go-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "generatorConfig": { "enableWireTests": false }, diff --git a/seed/go-sdk/allof/.fern/metadata.json b/seed/go-sdk/allof/.fern/metadata.json index 1e0f7d8e54e4..711224a2aaf7 100644 --- a/seed/go-sdk/allof/.fern/metadata.json +++ b/seed/go-sdk/allof/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-go-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "generatorConfig": { "enableWireTests": false }, diff --git a/seed/java-model/allof-inline/.github/workflows/ci.yml b/seed/java-model/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..09c8c666ad73 --- /dev/null +++ b/seed/java-model/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-model/allof-inline/.gitignore b/seed/java-model/allof-inline/.gitignore new file mode 100644 index 000000000000..d4199abc2cd4 --- /dev/null +++ b/seed/java-model/allof-inline/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-model/allof-inline/build.gradle b/seed/java-model/allof-inline/build.gradle new file mode 100644 index 000000000000..d56d2277685f --- /dev/null +++ b/seed/java-model/allof-inline/build.gradle @@ -0,0 +1,98 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(Javadoc) { + failOnError false + options.addStringOption('Xdoclint:none', '-quiet') +} + +spotless { + java { + palantirJavaFormat() + } +} + + +java { + withSourcesJar() + withJavadocJar() +} + + +group = 'com.fern' + +version = '0.0.1' + +jar { + dependsOn(":generatePomFileForMavenPublication") + archiveBaseName = "allof-inline" +} + +sourcesJar { + archiveBaseName = "allof-inline" +} + +javadocJar { + archiveBaseName = "allof-inline" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'allof-inline' + version = '0.0.1' + from components.java + pom { + licenses { + license { + name = 'The MIT License (MIT)' + url = 'https://mit-license.org/' + } + } + scm { + connection = 'scm:git:git://github.com/allof-inline/fern.git' + developerConnection = 'scm:git:git://github.com/allof-inline/fern.git' + url = 'https://github.com/allof-inline/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-model/allof-inline/settings.gradle b/seed/java-model/allof-inline/settings.gradle new file mode 100644 index 000000000000..be2d3eef00b7 --- /dev/null +++ b/seed/java-model/allof-inline/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'allof-inline' + diff --git a/seed/java-model/allof-inline/snippet.json b/seed/java-model/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 000000000000..b67a9041b1f5 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} \ No newline at end of file diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java new file mode 100644 index 000000000000..0d2114931cb1 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; + +/** + * Custom serializer that writes integer-valued doubles without a decimal point. + * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. + * Non-integer values like {@code 3.14} are serialized normally. + */ +class DoubleSerializer extends JsonSerializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule() + .addSerializer(Double.class, new DoubleSerializer()) + .addSerializer(double.class, new DoubleSerializer()); + } + + /** + * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { + gen.writeNumber(value.longValue()); + } else { + gen.writeNumber(value); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 000000000000..f294ae327e6d --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,52 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .addModule(DoubleSerializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() { + } + + public static String stringify(Object o) { + try { + return JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } + catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + + public static Object parseErrorBody(String responseBodyString) { + try { + return JSON_MAPPER.readValue(responseBodyString, Object.class); + } + catch (JsonProcessingException ignored) { + return responseBodyString; + } + } + } diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java new file mode 100644 index 000000000000..a75a230a0fe6 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. + * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. + */ +public class Rfc2822DateTimeDeserializer extends JsonDeserializer { + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + String raw = parser.getValueAsString(); + return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java new file mode 100644 index 000000000000..134efb3b95ee --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java @@ -0,0 +1,192 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.time.OffsetDateTime; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = AuditInfo.Builder.class +) +public final class AuditInfo { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private AuditInfo(Optional createdBy, Optional createdDateTime, + Optional modifiedBy, Optional modifiedDateTime) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AuditInfo && equalTo((AuditInfo) other); + } + + private boolean equalTo(AuditInfo other) { + return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional createdBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + private Builder() { + } + + public Builder from(AuditInfo other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + return this; + } + + /** + *

The user who created this resource.

+ */ + @JsonSetter( + value = "createdBy", + nulls = Nulls.SKIP + ) + public Builder createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + public Builder createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

When this resource was created.

+ */ + @JsonSetter( + value = "createdDateTime", + nulls = Nulls.SKIP + ) + public Builder createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + public Builder createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @JsonSetter( + value = "modifiedBy", + nulls = Nulls.SKIP + ) + public Builder modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + public Builder modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @JsonSetter( + value = "modifiedDateTime", + nulls = Nulls.SKIP + ) + public Builder modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + public AuditInfo build() { + return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java new file mode 100644 index 000000000000..00535bf9e0c9 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java @@ -0,0 +1,127 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = BaseOrg.Builder.class +) +public final class BaseOrg { + private final String id; + + private final Optional metadata; + + private BaseOrg(String id, Optional metadata) { + this.id = id; + this.metadata = metadata; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrg && equalTo((BaseOrg) other); + } + + private boolean equalTo(BaseOrg other) { + return id.equals(other.id) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(String id); + + Builder from(BaseOrg other); + } + + public interface _FinalStage { + BaseOrg build(); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(BaseOrgMetadata metadata); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional metadata = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(BaseOrg other) { + id(other.getId()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(BaseOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public BaseOrg build() { + return new BaseOrg(id, metadata); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java new file mode 100644 index 000000000000..6ccd08740868 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = BaseOrgMetadata.Builder.class +) +public final class BaseOrgMetadata { + private final String region; + + private final Optional tier; + + private BaseOrgMetadata(String region, Optional tier) { + this.region = region; + this.tier = tier; + } + + /** + * @return Deployment region from BaseOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Subscription tier. + */ + @JsonProperty("tier") + public Optional getTier() { + return tier; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); + } + + private boolean equalTo(BaseOrgMetadata other) { + return region.equals(other.region) && tier.equals(other.tier); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.tier); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from BaseOrg.

+ */ + _FinalStage region(String region); + + Builder from(BaseOrgMetadata other); + } + + public interface _FinalStage { + BaseOrgMetadata build(); + + /** + *

Subscription tier.

+ */ + _FinalStage tier(Optional tier); + + _FinalStage tier(String tier); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional tier = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(BaseOrgMetadata other) { + region(other.getRegion()); + tier(other.getTier()); + return this; + } + + /** + *

Deployment region from BaseOrg.

+ *

Deployment region from BaseOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Subscription tier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage tier(String tier) { + this.tier = Optional.ofNullable(tier); + return this; + } + + /** + *

Subscription tier.

+ */ + @java.lang.Override + @JsonSetter( + value = "tier", + nulls = Nulls.SKIP + ) + public _FinalStage tier(Optional tier) { + this.tier = tier; + return this; + } + + @java.lang.Override + public BaseOrgMetadata build() { + return new BaseOrgMetadata(region, tier); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java new file mode 100644 index 000000000000..95f15ae22959 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java @@ -0,0 +1,218 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = CombinedEntity.Builder.class +) +public final class CombinedEntity { + private final String id; + + private final Optional name; + + private final Optional summary; + + private final CombinedEntityStatus status; + + private CombinedEntity(String id, Optional name, Optional summary, + CombinedEntityStatus status) { + this.id = id; + this.name = name; + this.summary = summary; + this.status = status; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Describable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @JsonProperty("status") + public CombinedEntityStatus getStatus() { + return status; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CombinedEntity && equalTo((CombinedEntity) other); + } + + private boolean equalTo(CombinedEntity other) { + return id.equals(other.id) && name.equals(other.name) && summary.equals(other.summary) && status.equals(other.status); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.summary, this.status); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + StatusStage id(String id); + + Builder from(CombinedEntity other); + } + + public interface StatusStage { + _FinalStage status(CombinedEntityStatus status); + } + + public interface _FinalStage { + CombinedEntity build(); + + /** + *

Display name from Describable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + + /** + *

A short summary.

+ */ + _FinalStage summary(Optional summary); + + _FinalStage summary(String summary); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, StatusStage, _FinalStage { + private String id; + + private CombinedEntityStatus status; + + private Optional summary = Optional.empty(); + + private Optional name = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(CombinedEntity other) { + id(other.getId()); + name(other.getName()); + summary(other.getSummary()); + status(other.getStatus()); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public StatusStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public _FinalStage status(CombinedEntityStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + /** + *

A short summary.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + /** + *

A short summary.

+ */ + @java.lang.Override + @JsonSetter( + value = "summary", + nulls = Nulls.SKIP + ) + public _FinalStage summary(Optional summary) { + this.summary = summary; + return this; + } + + /** + *

Display name from Describable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Describable.

+ */ + @java.lang.Override + @JsonSetter( + value = "name", + nulls = Nulls.SKIP + ) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public CombinedEntity build() { + return new CombinedEntity(id, name, summary, status); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java new file mode 100644 index 000000000000..12b6e20c62fa --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.String; + +public enum CombinedEntityStatus { + ACTIVE("active"), + + ARCHIVED("archived"); + + private final String value; + + CombinedEntityStatus(String value) { + this.value = value; + } + + @JsonValue + @java.lang.Override + public String toString() { + return this.value; + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java new file mode 100644 index 000000000000..47749f51c511 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java @@ -0,0 +1,128 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = Describable.Builder.class +) +public final class Describable { + private final Optional name; + + private final Optional summary; + + private Describable(Optional name, Optional summary) { + this.name = name; + this.summary = summary; + } + + /** + * @return Display name from Describable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Describable && equalTo((Describable) other); + } + + private boolean equalTo(Describable other) { + return name.equals(other.name) && summary.equals(other.summary); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.summary); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional name = Optional.empty(); + + private Optional summary = Optional.empty(); + + private Builder() { + } + + public Builder from(Describable other) { + name(other.getName()); + summary(other.getSummary()); + return this; + } + + /** + *

Display name from Describable.

+ */ + @JsonSetter( + value = "name", + nulls = Nulls.SKIP + ) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

A short summary.

+ */ + @JsonSetter( + value = "summary", + nulls = Nulls.SKIP + ) + public Builder summary(Optional summary) { + this.summary = summary; + return this; + } + + public Builder summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + public Describable build() { + return new Describable(name, summary); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java new file mode 100644 index 000000000000..2673e956d206 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java @@ -0,0 +1,91 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = DetailedOrg.Builder.class +) +public final class DetailedOrg { + private final Optional metadata; + + private DetailedOrg(Optional metadata) { + this.metadata = metadata; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrg && equalTo((DetailedOrg) other); + } + + private boolean equalTo(DetailedOrg other) { + return metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional metadata = Optional.empty(); + + private Builder() { + } + + public Builder from(DetailedOrg other) { + metadata(other.getMetadata()); + return this; + } + + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public Builder metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(DetailedOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public DetailedOrg build() { + return new DetailedOrg(metadata); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java new file mode 100644 index 000000000000..db3256745178 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = DetailedOrgMetadata.Builder.class +) +public final class DetailedOrgMetadata { + private final String region; + + private final Optional domain; + + private DetailedOrgMetadata(String region, Optional domain) { + this.region = region; + this.domain = domain; + } + + /** + * @return Deployment region from DetailedOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Custom domain name. + */ + @JsonProperty("domain") + public Optional getDomain() { + return domain; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); + } + + private boolean equalTo(DetailedOrgMetadata other) { + return region.equals(other.region) && domain.equals(other.domain); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.domain); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from DetailedOrg.

+ */ + _FinalStage region(String region); + + Builder from(DetailedOrgMetadata other); + } + + public interface _FinalStage { + DetailedOrgMetadata build(); + + /** + *

Custom domain name.

+ */ + _FinalStage domain(Optional domain); + + _FinalStage domain(String domain); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional domain = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(DetailedOrgMetadata other) { + region(other.getRegion()); + domain(other.getDomain()); + return this; + } + + /** + *

Deployment region from DetailedOrg.

+ *

Deployment region from DetailedOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Custom domain name.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage domain(String domain) { + this.domain = Optional.ofNullable(domain); + return this; + } + + /** + *

Custom domain name.

+ */ + @java.lang.Override + @JsonSetter( + value = "domain", + nulls = Nulls.SKIP + ) + public _FinalStage domain(Optional domain) { + this.domain = domain; + return this; + } + + @java.lang.Override + public DetailedOrgMetadata build() { + return new DetailedOrgMetadata(region, domain); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java new file mode 100644 index 000000000000..c9dfcb3179ad --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = Identifiable.Builder.class +) +public final class Identifiable { + private final String id; + + private final Optional name; + + private Identifiable(String id, Optional name) { + this.id = id; + this.name = name; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Identifiable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Identifiable && equalTo((Identifiable) other); + } + + private boolean equalTo(Identifiable other) { + return id.equals(other.id) && name.equals(other.name); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + _FinalStage id(String id); + + Builder from(Identifiable other); + } + + public interface _FinalStage { + Identifiable build(); + + /** + *

Display name from Identifiable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional name = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(Identifiable other) { + id(other.getId()); + name(other.getName()); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + /** + *

Display name from Identifiable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Identifiable.

+ */ + @java.lang.Override + @JsonSetter( + value = "name", + nulls = Nulls.SKIP + ) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public Identifiable build() { + return new Identifiable(id, name); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java new file mode 100644 index 000000000000..a619c1e30d41 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java @@ -0,0 +1,149 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = Organization.Builder.class +) +public final class Organization { + private final String id; + + private final Optional metadata; + + private final String name; + + private Organization(String id, Optional metadata, String name) { + this.id = id; + this.metadata = metadata; + this.name = name; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Organization && equalTo((Organization) other); + } + + private boolean equalTo(Organization other) { + return id.equals(other.id) && metadata.equals(other.metadata) && name.equals(other.name); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.metadata, this.name); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(String id); + + Builder from(Organization other); + } + + public interface NameStage { + _FinalStage name(String name); + } + + public interface _FinalStage { + Organization build(); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(OrganizationMetadata metadata); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, NameStage, _FinalStage { + private String id; + + private String name; + + private Optional metadata = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(Organization other) { + id(other.getId()); + metadata(other.getMetadata()); + name(other.getName()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(OrganizationMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public Organization build() { + return new Organization(id, metadata, name); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java new file mode 100644 index 000000000000..3dd26d99224f --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = OrganizationMetadata.Builder.class +) +public final class OrganizationMetadata { + private final String region; + + private final Optional domain; + + private OrganizationMetadata(String region, Optional domain) { + this.region = region; + this.domain = domain; + } + + /** + * @return Deployment region from DetailedOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Custom domain name. + */ + @JsonProperty("domain") + public Optional getDomain() { + return domain; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof OrganizationMetadata && equalTo((OrganizationMetadata) other); + } + + private boolean equalTo(OrganizationMetadata other) { + return region.equals(other.region) && domain.equals(other.domain); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.domain); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from DetailedOrg.

+ */ + _FinalStage region(String region); + + Builder from(OrganizationMetadata other); + } + + public interface _FinalStage { + OrganizationMetadata build(); + + /** + *

Custom domain name.

+ */ + _FinalStage domain(Optional domain); + + _FinalStage domain(String domain); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional domain = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(OrganizationMetadata other) { + region(other.getRegion()); + domain(other.getDomain()); + return this; + } + + /** + *

Deployment region from DetailedOrg.

+ *

Deployment region from DetailedOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Custom domain name.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage domain(String domain) { + this.domain = Optional.ofNullable(domain); + return this; + } + + /** + *

Custom domain name.

+ */ + @java.lang.Override + @JsonSetter( + value = "domain", + nulls = Nulls.SKIP + ) + public _FinalStage domain(Optional domain) { + this.domain = domain; + return this; + } + + @java.lang.Override + public OrganizationMetadata build() { + return new OrganizationMetadata(region, domain); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java new file mode 100644 index 000000000000..f01c8cb01a89 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java @@ -0,0 +1,158 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = PaginatedResult.Builder.class +) +public final class PaginatedResult { + private final PagingCursors paging; + + private final List results; + + private PaginatedResult(PagingCursors paging, List results) { + this.paging = paging; + this.results = results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public List getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PaginatedResult && equalTo((PaginatedResult) other); + } + + private boolean equalTo(PaginatedResult other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(PagingCursors paging); + + Builder from(PaginatedResult other); + } + + public interface _FinalStage { + PaginatedResult build(); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(List results); + + _FinalStage addResults(Object results); + + _FinalStage addAllResults(List results); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private List results = new ArrayList<>(); + + private Builder() { + } + + @java.lang.Override + public Builder from(PaginatedResult other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addAllResults(List results) { + if (results != null) { + this.results.addAll(results); + } + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addResults(Object results) { + this.results.add(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "results", + nulls = Nulls.SKIP + ) + public _FinalStage results(List results) { + this.results.clear(); + if (results != null) { + this.results.addAll(results); + } + return this; + } + + @java.lang.Override + public PaginatedResult build() { + return new PaginatedResult(paging, results); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java new file mode 100644 index 000000000000..0d89da26698b --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = PagingCursors.Builder.class +) +public final class PagingCursors { + private final String next; + + private final Optional previous; + + private PagingCursors(String next, Optional previous) { + this.next = next; + this.previous = previous; + } + + /** + * @return Cursor for the next page of results. + */ + @JsonProperty("next") + public String getNext() { + return next; + } + + /** + * @return Cursor for the previous page of results. + */ + @JsonProperty("previous") + public Optional getPrevious() { + return previous; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PagingCursors && equalTo((PagingCursors) other); + } + + private boolean equalTo(PagingCursors other) { + return next.equals(other.next) && previous.equals(other.previous); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.next, this.previous); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NextStage builder() { + return new Builder(); + } + + public interface NextStage { + /** + *

Cursor for the next page of results.

+ */ + _FinalStage next(String next); + + Builder from(PagingCursors other); + } + + public interface _FinalStage { + PagingCursors build(); + + /** + *

Cursor for the previous page of results.

+ */ + _FinalStage previous(Optional previous); + + _FinalStage previous(String previous); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements NextStage, _FinalStage { + private String next; + + private Optional previous = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(PagingCursors other) { + next(other.getNext()); + previous(other.getPrevious()); + return this; + } + + /** + *

Cursor for the next page of results.

+ *

Cursor for the next page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("next") + public _FinalStage next(String next) { + this.next = Objects.requireNonNull(next, "next must not be null"); + return this; + } + + /** + *

Cursor for the previous page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage previous(String previous) { + this.previous = Optional.ofNullable(previous); + return this; + } + + /** + *

Cursor for the previous page of results.

+ */ + @java.lang.Override + @JsonSetter( + value = "previous", + nulls = Nulls.SKIP + ) + public _FinalStage previous(Optional previous) { + this.previous = previous; + return this; + } + + @java.lang.Override + public PagingCursors build() { + return new PagingCursors(next, previous); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java new file mode 100644 index 000000000000..93327278e6e3 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.String; + +public enum RuleExecutionContext { + PROD("prod"), + + STAGING("staging"), + + DEV("dev"); + + private final String value; + + RuleExecutionContext(String value) { + this.value = value; + } + + @JsonValue + @java.lang.Override + public String toString() { + return this.value; + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java new file mode 100644 index 000000000000..a8f62579a85c --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java @@ -0,0 +1,350 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.time.OffsetDateTime; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = RuleResponse.Builder.class +) +public final class RuleResponse { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private final String id; + + private final String name; + + private final RuleResponseStatus status; + + private final Optional executionContext; + + private RuleResponse(Optional createdBy, Optional createdDateTime, + Optional modifiedBy, Optional modifiedDateTime, String id, + String name, RuleResponseStatus status, Optional executionContext) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + this.id = id; + this.name = name; + this.status = status; + this.executionContext = executionContext; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("status") + public RuleResponseStatus getStatus() { + return status; + } + + @JsonProperty("executionContext") + public Optional getExecutionContext() { + return executionContext; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleResponse && equalTo((RuleResponse) other); + } + + private boolean equalTo(RuleResponse other) { + return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime) && id.equals(other.id) && name.equals(other.name) && status.equals(other.status) && executionContext.equals(other.executionContext); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime, this.id, this.name, this.status, this.executionContext); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(String id); + + Builder from(RuleResponse other); + } + + public interface NameStage { + StatusStage name(String name); + } + + public interface StatusStage { + _FinalStage status(RuleResponseStatus status); + } + + public interface _FinalStage { + RuleResponse build(); + + /** + *

The user who created this resource.

+ */ + _FinalStage createdBy(Optional createdBy); + + _FinalStage createdBy(String createdBy); + + /** + *

When this resource was created.

+ */ + _FinalStage createdDateTime(Optional createdDateTime); + + _FinalStage createdDateTime(OffsetDateTime createdDateTime); + + /** + *

The user who last modified this resource.

+ */ + _FinalStage modifiedBy(Optional modifiedBy); + + _FinalStage modifiedBy(String modifiedBy); + + /** + *

When this resource was last modified.

+ */ + _FinalStage modifiedDateTime(Optional modifiedDateTime); + + _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); + + _FinalStage executionContext(Optional executionContext); + + _FinalStage executionContext(RuleExecutionContext executionContext); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { + private String id; + + private String name; + + private RuleResponseStatus status; + + private Optional executionContext = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional createdBy = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(RuleResponse other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + id(other.getId()); + name(other.getName()); + status(other.getStatus()); + executionContext(other.getExecutionContext()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public StatusStage name(String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public _FinalStage status(RuleResponseStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage executionContext(RuleExecutionContext executionContext) { + this.executionContext = Optional.ofNullable(executionContext); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "executionContext", + nulls = Nulls.SKIP + ) + public _FinalStage executionContext(Optional executionContext) { + this.executionContext = executionContext; + return this; + } + + /** + *

When this resource was last modified.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @java.lang.Override + @JsonSetter( + value = "modifiedDateTime", + nulls = Nulls.SKIP + ) + public _FinalStage modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + /** + *

The user who last modified this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "modifiedBy", + nulls = Nulls.SKIP + ) + public _FinalStage modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + /** + *

When this resource was created.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

When this resource was created.

+ */ + @java.lang.Override + @JsonSetter( + value = "createdDateTime", + nulls = Nulls.SKIP + ) + public _FinalStage createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + /** + *

The user who created this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

The user who created this resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "createdBy", + nulls = Nulls.SKIP + ) + public _FinalStage createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + @java.lang.Override + public RuleResponse build() { + return new RuleResponse(createdBy, createdDateTime, modifiedBy, modifiedDateTime, id, name, status, executionContext); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java new file mode 100644 index 000000000000..302cf6780280 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.String; + +public enum RuleResponseStatus { + ACTIVE("active"), + + INACTIVE("inactive"), + + DRAFT("draft"); + + private final String value; + + RuleResponseStatus(String value) { + this.value = value; + } + + @JsonValue + @java.lang.Override + public String toString() { + return this.value; + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java new file mode 100644 index 000000000000..daf82b139790 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java @@ -0,0 +1,149 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = RuleType.Builder.class +) +public final class RuleType { + private final String id; + + private final String name; + + private final Optional description; + + private RuleType(String id, String name, Optional description) { + this.id = id; + this.name = name; + this.description = description; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("description") + public Optional getDescription() { + return description; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleType && equalTo((RuleType) other); + } + + private boolean equalTo(RuleType other) { + return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.description); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(String id); + + Builder from(RuleType other); + } + + public interface NameStage { + _FinalStage name(String name); + } + + public interface _FinalStage { + RuleType build(); + + _FinalStage description(Optional description); + + _FinalStage description(String description); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, NameStage, _FinalStage { + private String id; + + private String name; + + private Optional description = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(RuleType other) { + id(other.getId()); + name(other.getName()); + description(other.getDescription()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage description(String description) { + this.description = Optional.ofNullable(description); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "description", + nulls = Nulls.SKIP + ) + public _FinalStage description(Optional description) { + this.description = description; + return this; + } + + @java.lang.Override + public RuleType build() { + return new RuleType(id, name, description); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java new file mode 100644 index 000000000000..3aa67d711e00 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java @@ -0,0 +1,141 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = RuleTypeSearchResponse.Builder.class +) +public final class RuleTypeSearchResponse { + private final PagingCursors paging; + + private final Optional> results; + + private RuleTypeSearchResponse(PagingCursors paging, Optional> results) { + this.paging = paging; + this.results = results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); + } + + private boolean equalTo(RuleTypeSearchResponse other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(PagingCursors paging); + + Builder from(RuleTypeSearchResponse other); + } + + public interface _FinalStage { + RuleTypeSearchResponse build(); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(RuleTypeSearchResponse other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "results", + nulls = Nulls.SKIP + ) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public RuleTypeSearchResponse build() { + return new RuleTypeSearchResponse(paging, results); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java new file mode 100644 index 000000000000..12f5b0483346 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java @@ -0,0 +1,116 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = User.Builder.class +) +public final class User { + private final String id; + + private final String email; + + private User(String id, String email) { + this.id = id; + this.email = email; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("email") + public String getEmail() { + return email; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + private boolean equalTo(User other) { + return id.equals(other.id) && email.equals(other.email); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.email); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + EmailStage id(String id); + + Builder from(User other); + } + + public interface EmailStage { + _FinalStage email(String email); + } + + public interface _FinalStage { + User build(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, EmailStage, _FinalStage { + private String id; + + private String email; + + private Builder() { + } + + @java.lang.Override + public Builder from(User other) { + id(other.getId()); + email(other.getEmail()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public EmailStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("email") + public _FinalStage email(String email) { + this.email = Objects.requireNonNull(email, "email must not be null"); + return this; + } + + @java.lang.Override + public User build() { + return new User(id, email); + } + } +} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java new file mode 100644 index 000000000000..b78b2fb2b2e2 --- /dev/null +++ b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java @@ -0,0 +1,141 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = UserSearchResponse.Builder.class +) +public final class UserSearchResponse { + private final PagingCursors paging; + + private final Optional> results; + + private UserSearchResponse(PagingCursors paging, Optional> results) { + this.paging = paging; + this.results = results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); + } + + private boolean equalTo(UserSearchResponse other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(PagingCursors paging); + + Builder from(UserSearchResponse other); + } + + public interface _FinalStage { + UserSearchResponse build(); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(UserSearchResponse other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "results", + nulls = Nulls.SKIP + ) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public UserSearchResponse build() { + return new UserSearchResponse(paging, results); + } + } +} diff --git a/seed/java-model/allof/.github/workflows/ci.yml b/seed/java-model/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..09c8c666ad73 --- /dev/null +++ b/seed/java-model/allof/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-model/allof/.gitignore b/seed/java-model/allof/.gitignore new file mode 100644 index 000000000000..d4199abc2cd4 --- /dev/null +++ b/seed/java-model/allof/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-model/allof/build.gradle b/seed/java-model/allof/build.gradle new file mode 100644 index 000000000000..79416f4ae6b9 --- /dev/null +++ b/seed/java-model/allof/build.gradle @@ -0,0 +1,98 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(Javadoc) { + failOnError false + options.addStringOption('Xdoclint:none', '-quiet') +} + +spotless { + java { + palantirJavaFormat() + } +} + + +java { + withSourcesJar() + withJavadocJar() +} + + +group = 'com.fern' + +version = '0.0.1' + +jar { + dependsOn(":generatePomFileForMavenPublication") + archiveBaseName = "allof" +} + +sourcesJar { + archiveBaseName = "allof" +} + +javadocJar { + archiveBaseName = "allof" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'allof' + version = '0.0.1' + from components.java + pom { + licenses { + license { + name = 'The MIT License (MIT)' + url = 'https://mit-license.org/' + } + } + scm { + connection = 'scm:git:git://github.com/allof/fern.git' + developerConnection = 'scm:git:git://github.com/allof/fern.git' + url = 'https://github.com/allof/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-model/allof/settings.gradle b/seed/java-model/allof/settings.gradle new file mode 100644 index 000000000000..418cc33300ad --- /dev/null +++ b/seed/java-model/allof/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'allof' + diff --git a/seed/java-model/allof/snippet.json b/seed/java-model/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 000000000000..b67a9041b1f5 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} \ No newline at end of file diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java new file mode 100644 index 000000000000..0d2114931cb1 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; + +/** + * Custom serializer that writes integer-valued doubles without a decimal point. + * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. + * Non-integer values like {@code 3.14} are serialized normally. + */ +class DoubleSerializer extends JsonSerializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule() + .addSerializer(Double.class, new DoubleSerializer()) + .addSerializer(double.class, new DoubleSerializer()); + } + + /** + * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { + gen.writeNumber(value.longValue()); + } else { + gen.writeNumber(value); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 000000000000..f294ae327e6d --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,52 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .addModule(DoubleSerializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() { + } + + public static String stringify(Object o) { + try { + return JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } + catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + + public static Object parseErrorBody(String responseBodyString) { + try { + return JSON_MAPPER.readValue(responseBodyString, Object.class); + } + catch (JsonProcessingException ignored) { + return responseBodyString; + } + } + } diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java new file mode 100644 index 000000000000..a75a230a0fe6 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. + * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. + */ +public class Rfc2822DateTimeDeserializer extends JsonDeserializer { + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + String raw = parser.getValueAsString(); + return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java b/seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java new file mode 100644 index 000000000000..ff26f71f1d98 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java @@ -0,0 +1,196 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.time.OffsetDateTime; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = AuditInfo.Builder.class +) +public final class AuditInfo implements IAuditInfo { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private AuditInfo(Optional createdBy, Optional createdDateTime, + Optional modifiedBy, Optional modifiedDateTime) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + @java.lang.Override + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + @java.lang.Override + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + @java.lang.Override + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + @java.lang.Override + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AuditInfo && equalTo((AuditInfo) other); + } + + private boolean equalTo(AuditInfo other) { + return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional createdBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + private Builder() { + } + + public Builder from(AuditInfo other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + return this; + } + + /** + *

The user who created this resource.

+ */ + @JsonSetter( + value = "createdBy", + nulls = Nulls.SKIP + ) + public Builder createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + public Builder createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

When this resource was created.

+ */ + @JsonSetter( + value = "createdDateTime", + nulls = Nulls.SKIP + ) + public Builder createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + public Builder createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @JsonSetter( + value = "modifiedBy", + nulls = Nulls.SKIP + ) + public Builder modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + public Builder modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @JsonSetter( + value = "modifiedDateTime", + nulls = Nulls.SKIP + ) + public Builder modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + public AuditInfo build() { + return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java b/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java new file mode 100644 index 000000000000..00535bf9e0c9 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java @@ -0,0 +1,127 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = BaseOrg.Builder.class +) +public final class BaseOrg { + private final String id; + + private final Optional metadata; + + private BaseOrg(String id, Optional metadata) { + this.id = id; + this.metadata = metadata; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrg && equalTo((BaseOrg) other); + } + + private boolean equalTo(BaseOrg other) { + return id.equals(other.id) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(String id); + + Builder from(BaseOrg other); + } + + public interface _FinalStage { + BaseOrg build(); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(BaseOrgMetadata metadata); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional metadata = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(BaseOrg other) { + id(other.getId()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(BaseOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public BaseOrg build() { + return new BaseOrg(id, metadata); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java b/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java new file mode 100644 index 000000000000..6ccd08740868 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = BaseOrgMetadata.Builder.class +) +public final class BaseOrgMetadata { + private final String region; + + private final Optional tier; + + private BaseOrgMetadata(String region, Optional tier) { + this.region = region; + this.tier = tier; + } + + /** + * @return Deployment region from BaseOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Subscription tier. + */ + @JsonProperty("tier") + public Optional getTier() { + return tier; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); + } + + private boolean equalTo(BaseOrgMetadata other) { + return region.equals(other.region) && tier.equals(other.tier); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.tier); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from BaseOrg.

+ */ + _FinalStage region(String region); + + Builder from(BaseOrgMetadata other); + } + + public interface _FinalStage { + BaseOrgMetadata build(); + + /** + *

Subscription tier.

+ */ + _FinalStage tier(Optional tier); + + _FinalStage tier(String tier); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional tier = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(BaseOrgMetadata other) { + region(other.getRegion()); + tier(other.getTier()); + return this; + } + + /** + *

Deployment region from BaseOrg.

+ *

Deployment region from BaseOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Subscription tier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage tier(String tier) { + this.tier = Optional.ofNullable(tier); + return this; + } + + /** + *

Subscription tier.

+ */ + @java.lang.Override + @JsonSetter( + value = "tier", + nulls = Nulls.SKIP + ) + public _FinalStage tier(Optional tier) { + this.tier = tier; + return this; + } + + @java.lang.Override + public BaseOrgMetadata build() { + return new BaseOrgMetadata(region, tier); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java b/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java new file mode 100644 index 000000000000..20ce10be75f4 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java @@ -0,0 +1,218 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = CombinedEntity.Builder.class +) +public final class CombinedEntity { + private final CombinedEntityStatus status; + + private final String id; + + private final Optional name; + + private final Optional summary; + + private CombinedEntity(CombinedEntityStatus status, String id, Optional name, + Optional summary) { + this.status = status; + this.id = id; + this.name = name; + this.summary = summary; + } + + @JsonProperty("status") + public CombinedEntityStatus getStatus() { + return status; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Identifiable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CombinedEntity && equalTo((CombinedEntity) other); + } + + private boolean equalTo(CombinedEntity other) { + return status.equals(other.status) && id.equals(other.id) && name.equals(other.name) && summary.equals(other.summary); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.status, this.id, this.name, this.summary); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static StatusStage builder() { + return new Builder(); + } + + public interface StatusStage { + IdStage status(CombinedEntityStatus status); + + Builder from(CombinedEntity other); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + _FinalStage id(String id); + } + + public interface _FinalStage { + CombinedEntity build(); + + /** + *

Display name from Identifiable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + + /** + *

A short summary.

+ */ + _FinalStage summary(Optional summary); + + _FinalStage summary(String summary); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements StatusStage, IdStage, _FinalStage { + private CombinedEntityStatus status; + + private String id; + + private Optional summary = Optional.empty(); + + private Optional name = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(CombinedEntity other) { + status(other.getStatus()); + id(other.getId()); + name(other.getName()); + summary(other.getSummary()); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public IdStage status(CombinedEntityStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + /** + *

A short summary.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + /** + *

A short summary.

+ */ + @java.lang.Override + @JsonSetter( + value = "summary", + nulls = Nulls.SKIP + ) + public _FinalStage summary(Optional summary) { + this.summary = summary; + return this; + } + + /** + *

Display name from Identifiable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Identifiable.

+ */ + @java.lang.Override + @JsonSetter( + value = "name", + nulls = Nulls.SKIP + ) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public CombinedEntity build() { + return new CombinedEntity(status, id, name, summary); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java b/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java new file mode 100644 index 000000000000..12b6e20c62fa --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.String; + +public enum CombinedEntityStatus { + ACTIVE("active"), + + ARCHIVED("archived"); + + private final String value; + + CombinedEntityStatus(String value) { + this.value = value; + } + + @JsonValue + @java.lang.Override + public String toString() { + return this.value; + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java b/seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java new file mode 100644 index 000000000000..47749f51c511 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java @@ -0,0 +1,128 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = Describable.Builder.class +) +public final class Describable { + private final Optional name; + + private final Optional summary; + + private Describable(Optional name, Optional summary) { + this.name = name; + this.summary = summary; + } + + /** + * @return Display name from Describable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return A short summary. + */ + @JsonProperty("summary") + public Optional getSummary() { + return summary; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Describable && equalTo((Describable) other); + } + + private boolean equalTo(Describable other) { + return name.equals(other.name) && summary.equals(other.summary); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.summary); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional name = Optional.empty(); + + private Optional summary = Optional.empty(); + + private Builder() { + } + + public Builder from(Describable other) { + name(other.getName()); + summary(other.getSummary()); + return this; + } + + /** + *

Display name from Describable.

+ */ + @JsonSetter( + value = "name", + nulls = Nulls.SKIP + ) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

A short summary.

+ */ + @JsonSetter( + value = "summary", + nulls = Nulls.SKIP + ) + public Builder summary(Optional summary) { + this.summary = summary; + return this; + } + + public Builder summary(String summary) { + this.summary = Optional.ofNullable(summary); + return this; + } + + public Describable build() { + return new Describable(name, summary); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java b/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java new file mode 100644 index 000000000000..2673e956d206 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java @@ -0,0 +1,91 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = DetailedOrg.Builder.class +) +public final class DetailedOrg { + private final Optional metadata; + + private DetailedOrg(Optional metadata) { + this.metadata = metadata; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrg && equalTo((DetailedOrg) other); + } + + private boolean equalTo(DetailedOrg other) { + return metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional metadata = Optional.empty(); + + private Builder() { + } + + public Builder from(DetailedOrg other) { + metadata(other.getMetadata()); + return this; + } + + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public Builder metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(DetailedOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public DetailedOrg build() { + return new DetailedOrg(metadata); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java b/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java new file mode 100644 index 000000000000..db3256745178 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = DetailedOrgMetadata.Builder.class +) +public final class DetailedOrgMetadata { + private final String region; + + private final Optional domain; + + private DetailedOrgMetadata(String region, Optional domain) { + this.region = region; + this.domain = domain; + } + + /** + * @return Deployment region from DetailedOrg. + */ + @JsonProperty("region") + public String getRegion() { + return region; + } + + /** + * @return Custom domain name. + */ + @JsonProperty("domain") + public Optional getDomain() { + return domain; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); + } + + private boolean equalTo(DetailedOrgMetadata other) { + return region.equals(other.region) && domain.equals(other.domain); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.region, this.domain); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RegionStage builder() { + return new Builder(); + } + + public interface RegionStage { + /** + *

Deployment region from DetailedOrg.

+ */ + _FinalStage region(String region); + + Builder from(DetailedOrgMetadata other); + } + + public interface _FinalStage { + DetailedOrgMetadata build(); + + /** + *

Custom domain name.

+ */ + _FinalStage domain(Optional domain); + + _FinalStage domain(String domain); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements RegionStage, _FinalStage { + private String region; + + private Optional domain = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(DetailedOrgMetadata other) { + region(other.getRegion()); + domain(other.getDomain()); + return this; + } + + /** + *

Deployment region from DetailedOrg.

+ *

Deployment region from DetailedOrg.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("region") + public _FinalStage region(String region) { + this.region = Objects.requireNonNull(region, "region must not be null"); + return this; + } + + /** + *

Custom domain name.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage domain(String domain) { + this.domain = Optional.ofNullable(domain); + return this; + } + + /** + *

Custom domain name.

+ */ + @java.lang.Override + @JsonSetter( + value = "domain", + nulls = Nulls.SKIP + ) + public _FinalStage domain(Optional domain) { + this.domain = domain; + return this; + } + + @java.lang.Override + public DetailedOrgMetadata build() { + return new DetailedOrgMetadata(region, domain); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java b/seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java new file mode 100644 index 000000000000..381064953ef7 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import java.lang.String; +import java.time.OffsetDateTime; +import java.util.Optional; + +public interface IAuditInfo { + Optional getCreatedBy(); + + Optional getCreatedDateTime(); + + Optional getModifiedBy(); + + Optional getModifiedDateTime(); +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java b/seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java new file mode 100644 index 000000000000..c9dfcb3179ad --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = Identifiable.Builder.class +) +public final class Identifiable { + private final String id; + + private final Optional name; + + private Identifiable(String id, Optional name) { + this.id = id; + this.name = name; + } + + /** + * @return Unique identifier. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * @return Display name from Identifiable. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Identifiable && equalTo((Identifiable) other); + } + + private boolean equalTo(Identifiable other) { + return id.equals(other.id) && name.equals(other.name); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + /** + *

Unique identifier.

+ */ + _FinalStage id(String id); + + Builder from(Identifiable other); + } + + public interface _FinalStage { + Identifiable build(); + + /** + *

Display name from Identifiable.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional name = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(Identifiable other) { + id(other.getId()); + name(other.getName()); + return this; + } + + /** + *

Unique identifier.

+ *

Unique identifier.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + /** + *

Display name from Identifiable.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Display name from Identifiable.

+ */ + @java.lang.Override + @JsonSetter( + value = "name", + nulls = Nulls.SKIP + ) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public Identifiable build() { + return new Identifiable(id, name); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java b/seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java new file mode 100644 index 000000000000..afcaed6668f1 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java @@ -0,0 +1,149 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = Organization.Builder.class +) +public final class Organization { + private final String name; + + private final String id; + + private final Optional metadata; + + private Organization(String name, String id, Optional metadata) { + this.name = name; + this.id = id; + this.metadata = metadata; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Organization && equalTo((Organization) other); + } + + private boolean equalTo(Organization other) { + return name.equals(other.name) && id.equals(other.id) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.id, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NameStage builder() { + return new Builder(); + } + + public interface NameStage { + IdStage name(String name); + + Builder from(Organization other); + } + + public interface IdStage { + _FinalStage id(String id); + } + + public interface _FinalStage { + Organization build(); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(BaseOrgMetadata metadata); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements NameStage, IdStage, _FinalStage { + private String name; + + private String id; + + private Optional metadata = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(Organization other) { + name(other.getName()); + id(other.getId()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public IdStage name(String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage metadata(BaseOrgMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public Organization build() { + return new Organization(name, id, metadata); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java b/seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java new file mode 100644 index 000000000000..f01c8cb01a89 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java @@ -0,0 +1,158 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = PaginatedResult.Builder.class +) +public final class PaginatedResult { + private final PagingCursors paging; + + private final List results; + + private PaginatedResult(PagingCursors paging, List results) { + this.paging = paging; + this.results = results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public List getResults() { + return results; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PaginatedResult && equalTo((PaginatedResult) other); + } + + private boolean equalTo(PaginatedResult other) { + return paging.equals(other.paging) && results.equals(other.results); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.paging, this.results); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(PagingCursors paging); + + Builder from(PaginatedResult other); + } + + public interface _FinalStage { + PaginatedResult build(); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(List results); + + _FinalStage addResults(Object results); + + _FinalStage addAllResults(List results); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private List results = new ArrayList<>(); + + private Builder() { + } + + @java.lang.Override + public Builder from(PaginatedResult other) { + paging(other.getPaging()); + results(other.getResults()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addAllResults(List results) { + if (results != null) { + this.results.addAll(results); + } + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage addResults(Object results) { + this.results.add(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "results", + nulls = Nulls.SKIP + ) + public _FinalStage results(List results) { + this.results.clear(); + if (results != null) { + this.results.addAll(results); + } + return this; + } + + @java.lang.Override + public PaginatedResult build() { + return new PaginatedResult(paging, results); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java b/seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java new file mode 100644 index 000000000000..0d89da26698b --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java @@ -0,0 +1,151 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = PagingCursors.Builder.class +) +public final class PagingCursors { + private final String next; + + private final Optional previous; + + private PagingCursors(String next, Optional previous) { + this.next = next; + this.previous = previous; + } + + /** + * @return Cursor for the next page of results. + */ + @JsonProperty("next") + public String getNext() { + return next; + } + + /** + * @return Cursor for the previous page of results. + */ + @JsonProperty("previous") + public Optional getPrevious() { + return previous; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PagingCursors && equalTo((PagingCursors) other); + } + + private boolean equalTo(PagingCursors other) { + return next.equals(other.next) && previous.equals(other.previous); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.next, this.previous); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NextStage builder() { + return new Builder(); + } + + public interface NextStage { + /** + *

Cursor for the next page of results.

+ */ + _FinalStage next(String next); + + Builder from(PagingCursors other); + } + + public interface _FinalStage { + PagingCursors build(); + + /** + *

Cursor for the previous page of results.

+ */ + _FinalStage previous(Optional previous); + + _FinalStage previous(String previous); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements NextStage, _FinalStage { + private String next; + + private Optional previous = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(PagingCursors other) { + next(other.getNext()); + previous(other.getPrevious()); + return this; + } + + /** + *

Cursor for the next page of results.

+ *

Cursor for the next page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("next") + public _FinalStage next(String next) { + this.next = Objects.requireNonNull(next, "next must not be null"); + return this; + } + + /** + *

Cursor for the previous page of results.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage previous(String previous) { + this.previous = Optional.ofNullable(previous); + return this; + } + + /** + *

Cursor for the previous page of results.

+ */ + @java.lang.Override + @JsonSetter( + value = "previous", + nulls = Nulls.SKIP + ) + public _FinalStage previous(Optional previous) { + this.previous = previous; + return this; + } + + @java.lang.Override + public PagingCursors build() { + return new PagingCursors(next, previous); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java new file mode 100644 index 000000000000..93327278e6e3 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.String; + +public enum RuleExecutionContext { + PROD("prod"), + + STAGING("staging"), + + DEV("dev"); + + private final String value; + + RuleExecutionContext(String value) { + this.value = value; + } + + @JsonValue + @java.lang.Override + public String toString() { + return this.value; + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java new file mode 100644 index 000000000000..20b4bae74875 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java @@ -0,0 +1,354 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.time.OffsetDateTime; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = RuleResponse.Builder.class +) +public final class RuleResponse implements IAuditInfo { + private final Optional createdBy; + + private final Optional createdDateTime; + + private final Optional modifiedBy; + + private final Optional modifiedDateTime; + + private final String id; + + private final String name; + + private final RuleResponseStatus status; + + private final Optional executionContext; + + private RuleResponse(Optional createdBy, Optional createdDateTime, + Optional modifiedBy, Optional modifiedDateTime, String id, + String name, RuleResponseStatus status, Optional executionContext) { + this.createdBy = createdBy; + this.createdDateTime = createdDateTime; + this.modifiedBy = modifiedBy; + this.modifiedDateTime = modifiedDateTime; + this.id = id; + this.name = name; + this.status = status; + this.executionContext = executionContext; + } + + /** + * @return The user who created this resource. + */ + @JsonProperty("createdBy") + @java.lang.Override + public Optional getCreatedBy() { + return createdBy; + } + + /** + * @return When this resource was created. + */ + @JsonProperty("createdDateTime") + @java.lang.Override + public Optional getCreatedDateTime() { + return createdDateTime; + } + + /** + * @return The user who last modified this resource. + */ + @JsonProperty("modifiedBy") + @java.lang.Override + public Optional getModifiedBy() { + return modifiedBy; + } + + /** + * @return When this resource was last modified. + */ + @JsonProperty("modifiedDateTime") + @java.lang.Override + public Optional getModifiedDateTime() { + return modifiedDateTime; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("status") + public RuleResponseStatus getStatus() { + return status; + } + + @JsonProperty("executionContext") + public Optional getExecutionContext() { + return executionContext; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleResponse && equalTo((RuleResponse) other); + } + + private boolean equalTo(RuleResponse other) { + return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime) && id.equals(other.id) && name.equals(other.name) && status.equals(other.status) && executionContext.equals(other.executionContext); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime, this.id, this.name, this.status, this.executionContext); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(String id); + + Builder from(RuleResponse other); + } + + public interface NameStage { + StatusStage name(String name); + } + + public interface StatusStage { + _FinalStage status(RuleResponseStatus status); + } + + public interface _FinalStage { + RuleResponse build(); + + /** + *

The user who created this resource.

+ */ + _FinalStage createdBy(Optional createdBy); + + _FinalStage createdBy(String createdBy); + + /** + *

When this resource was created.

+ */ + _FinalStage createdDateTime(Optional createdDateTime); + + _FinalStage createdDateTime(OffsetDateTime createdDateTime); + + /** + *

The user who last modified this resource.

+ */ + _FinalStage modifiedBy(Optional modifiedBy); + + _FinalStage modifiedBy(String modifiedBy); + + /** + *

When this resource was last modified.

+ */ + _FinalStage modifiedDateTime(Optional modifiedDateTime); + + _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); + + _FinalStage executionContext(Optional executionContext); + + _FinalStage executionContext(RuleExecutionContext executionContext); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { + private String id; + + private String name; + + private RuleResponseStatus status; + + private Optional executionContext = Optional.empty(); + + private Optional modifiedDateTime = Optional.empty(); + + private Optional modifiedBy = Optional.empty(); + + private Optional createdDateTime = Optional.empty(); + + private Optional createdBy = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(RuleResponse other) { + createdBy(other.getCreatedBy()); + createdDateTime(other.getCreatedDateTime()); + modifiedBy(other.getModifiedBy()); + modifiedDateTime(other.getModifiedDateTime()); + id(other.getId()); + name(other.getName()); + status(other.getStatus()); + executionContext(other.getExecutionContext()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public StatusStage name(String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("status") + public _FinalStage status(RuleResponseStatus status) { + this.status = Objects.requireNonNull(status, "status must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage executionContext(RuleExecutionContext executionContext) { + this.executionContext = Optional.ofNullable(executionContext); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "executionContext", + nulls = Nulls.SKIP + ) + public _FinalStage executionContext(Optional executionContext) { + this.executionContext = executionContext; + return this; + } + + /** + *

When this resource was last modified.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { + this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); + return this; + } + + /** + *

When this resource was last modified.

+ */ + @java.lang.Override + @JsonSetter( + value = "modifiedDateTime", + nulls = Nulls.SKIP + ) + public _FinalStage modifiedDateTime(Optional modifiedDateTime) { + this.modifiedDateTime = modifiedDateTime; + return this; + } + + /** + *

The user who last modified this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage modifiedBy(String modifiedBy) { + this.modifiedBy = Optional.ofNullable(modifiedBy); + return this; + } + + /** + *

The user who last modified this resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "modifiedBy", + nulls = Nulls.SKIP + ) + public _FinalStage modifiedBy(Optional modifiedBy) { + this.modifiedBy = modifiedBy; + return this; + } + + /** + *

When this resource was created.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { + this.createdDateTime = Optional.ofNullable(createdDateTime); + return this; + } + + /** + *

When this resource was created.

+ */ + @java.lang.Override + @JsonSetter( + value = "createdDateTime", + nulls = Nulls.SKIP + ) + public _FinalStage createdDateTime(Optional createdDateTime) { + this.createdDateTime = createdDateTime; + return this; + } + + /** + *

The user who created this resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage createdBy(String createdBy) { + this.createdBy = Optional.ofNullable(createdBy); + return this; + } + + /** + *

The user who created this resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "createdBy", + nulls = Nulls.SKIP + ) + public _FinalStage createdBy(Optional createdBy) { + this.createdBy = createdBy; + return this; + } + + @java.lang.Override + public RuleResponse build() { + return new RuleResponse(createdBy, createdDateTime, modifiedBy, modifiedDateTime, id, name, status, executionContext); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java new file mode 100644 index 000000000000..302cf6780280 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.String; + +public enum RuleResponseStatus { + ACTIVE("active"), + + INACTIVE("inactive"), + + DRAFT("draft"); + + private final String value; + + RuleResponseStatus(String value) { + this.value = value; + } + + @JsonValue + @java.lang.Override + public String toString() { + return this.value; + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java new file mode 100644 index 000000000000..daf82b139790 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java @@ -0,0 +1,149 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = RuleType.Builder.class +) +public final class RuleType { + private final String id; + + private final String name; + + private final Optional description; + + private RuleType(String id, String name, Optional description) { + this.id = id; + this.name = name; + this.description = description; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("description") + public Optional getDescription() { + return description; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleType && equalTo((RuleType) other); + } + + private boolean equalTo(RuleType other) { + return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.description); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + NameStage id(String id); + + Builder from(RuleType other); + } + + public interface NameStage { + _FinalStage name(String name); + } + + public interface _FinalStage { + RuleType build(); + + _FinalStage description(Optional description); + + _FinalStage description(String description); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, NameStage, _FinalStage { + private String id; + + private String name; + + private Optional description = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(RuleType other) { + id(other.getId()); + name(other.getName()); + description(other.getDescription()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public NameStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage description(String description) { + this.description = Optional.ofNullable(description); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "description", + nulls = Nulls.SKIP + ) + public _FinalStage description(Optional description) { + this.description = description; + return this; + } + + @java.lang.Override + public RuleType build() { + return new RuleType(id, name, description); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java new file mode 100644 index 000000000000..dc70bd9a6364 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java @@ -0,0 +1,141 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = RuleTypeSearchResponse.Builder.class +) +public final class RuleTypeSearchResponse { + private final Optional> results; + + private final PagingCursors paging; + + private RuleTypeSearchResponse(Optional> results, PagingCursors paging) { + this.results = results; + this.paging = paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); + } + + private boolean equalTo(RuleTypeSearchResponse other) { + return results.equals(other.results) && paging.equals(other.paging); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.results, this.paging); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(PagingCursors paging); + + Builder from(RuleTypeSearchResponse other); + } + + public interface _FinalStage { + RuleTypeSearchResponse build(); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(RuleTypeSearchResponse other) { + results(other.getResults()); + paging(other.getPaging()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "results", + nulls = Nulls.SKIP + ) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public RuleTypeSearchResponse build() { + return new RuleTypeSearchResponse(results, paging); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/User.java b/seed/java-model/allof/src/main/java/com/seed/api/model/User.java new file mode 100644 index 000000000000..12f5b0483346 --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/User.java @@ -0,0 +1,116 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = User.Builder.class +) +public final class User { + private final String id; + + private final String email; + + private User(String id, String email) { + this.id = id; + this.email = email; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("email") + public String getEmail() { + return email; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + private boolean equalTo(User other) { + return id.equals(other.id) && email.equals(other.email); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.email); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + EmailStage id(String id); + + Builder from(User other); + } + + public interface EmailStage { + _FinalStage email(String email); + } + + public interface _FinalStage { + User build(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, EmailStage, _FinalStage { + private String id; + + private String email; + + private Builder() { + } + + @java.lang.Override + public Builder from(User other) { + id(other.getId()); + email(other.getEmail()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public EmailStage id(String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("email") + public _FinalStage email(String email) { + this.email = Objects.requireNonNull(email, "email must not be null"); + return this; + } + + @java.lang.Override + public User build() { + return new User(id, email); + } + } +} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java b/seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java new file mode 100644 index 000000000000..0bd6c146a72f --- /dev/null +++ b/seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java @@ -0,0 +1,141 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = UserSearchResponse.Builder.class +) +public final class UserSearchResponse { + private final Optional> results; + + private final PagingCursors paging; + + private UserSearchResponse(Optional> results, PagingCursors paging) { + this.results = results; + this.paging = paging; + } + + /** + * @return Current page of results from the requested resource. + */ + @JsonProperty("results") + public Optional> getResults() { + return results; + } + + @JsonProperty("paging") + public PagingCursors getPaging() { + return paging; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); + } + + private boolean equalTo(UserSearchResponse other) { + return results.equals(other.results) && paging.equals(other.paging); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.results, this.paging); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static PagingStage builder() { + return new Builder(); + } + + public interface PagingStage { + _FinalStage paging(PagingCursors paging); + + Builder from(UserSearchResponse other); + } + + public interface _FinalStage { + UserSearchResponse build(); + + /** + *

Current page of results from the requested resource.

+ */ + _FinalStage results(Optional> results); + + _FinalStage results(List results); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements PagingStage, _FinalStage { + private PagingCursors paging; + + private Optional> results = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(UserSearchResponse other) { + results(other.getResults()); + paging(other.getPaging()); + return this; + } + + @java.lang.Override + @JsonSetter("paging") + public _FinalStage paging(PagingCursors paging) { + this.paging = Objects.requireNonNull(paging, "paging must not be null"); + return this; + } + + /** + *

Current page of results from the requested resource.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage results(List results) { + this.results = Optional.ofNullable(results); + return this; + } + + /** + *

Current page of results from the requested resource.

+ */ + @java.lang.Override + @JsonSetter( + value = "results", + nulls = Nulls.SKIP + ) + public _FinalStage results(Optional> results) { + this.results = results; + return this; + } + + @java.lang.Override + public UserSearchResponse build() { + return new UserSearchResponse(results, paging); + } + } +} diff --git a/seed/java-sdk/allof-inline/.fern/metadata.json b/seed/java-sdk/allof-inline/.fern/metadata.json index 6077ef8b5f57..762fcfc814db 100644 --- a/seed/java-sdk/allof-inline/.fern/metadata.json +++ b/seed/java-sdk/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/java-sdk/allof/.fern/metadata.json b/seed/java-sdk/allof/.fern/metadata.json index 6077ef8b5f57..762fcfc814db 100644 --- a/seed/java-sdk/allof/.fern/metadata.json +++ b/seed/java-sdk/allof/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/openapi/allof-inline/openapi.yml b/seed/openapi/allof-inline/openapi.yml new file mode 100644 index 000000000000..3f991de537f7 --- /dev/null +++ b/seed/openapi/allof-inline/openapi.yml @@ -0,0 +1,432 @@ +openapi: 3.0.1 +info: + title: allOf Composition + version: '' +paths: + /rule-types: + get: + operationId: searchRuleTypes + tags: + - '' + parameters: + - name: query + in: query + required: false + schema: + type: string + nullable: true + responses: + '200': + description: Paginated list of rule types + content: + application/json: + schema: + $ref: '#/components/schemas/RuleTypeSearchResponse' + examples: + Example1: + value: + paging: + next: next + previous: previous + results: + - id: id + name: name + description: description + summary: Search rule types with paginated results + /rules: + post: + operationId: createRule + tags: + - '' + parameters: [] + responses: + '200': + description: Created rule + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + examples: + Example1: + value: + createdBy: createdBy + createdDateTime: '2024-01-15T09:30:00Z' + modifiedBy: modifiedBy + modifiedDateTime: '2024-01-15T09:30:00Z' + id: id + name: name + status: active + executionContext: prod + summary: Create a rule with constrained execution context + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + example: name + executionContext: + $ref: '#/components/schemas/RuleExecutionContext' + required: + - name + - executionContext + examples: + Example1: + value: + name: name + executionContext: prod + /users: + get: + operationId: listUsers + tags: + - '' + parameters: [] + responses: + '200': + description: Paginated list of users + content: + application/json: + schema: + $ref: '#/components/schemas/UserSearchResponse' + examples: + Example1: + value: + paging: + next: next + previous: previous + results: + - id: id + email: email + summary: List users with paginated results + /entities: + get: + operationId: getEntity + tags: + - '' + parameters: [] + responses: + '200': + description: An entity with properties from multiple parents + content: + application/json: + schema: + $ref: '#/components/schemas/CombinedEntity' + examples: + Example1: + value: + id: id + name: name + summary: summary + status: active + summary: Get an entity that combines multiple parents + /organizations: + get: + operationId: getOrganization + tags: + - '' + parameters: [] + responses: + '200': + description: An organization whose metadata is merged from two parents + content: + application/json: + schema: + $ref: '#/components/schemas/Organization' + examples: + Example1: + value: + id: id + metadata: + region: region + domain: domain + name: name + summary: Get an organization with merged object-typed properties +components: + schemas: + PaginatedResult: + title: PaginatedResult + type: object + properties: + paging: + $ref: '#/components/schemas/PagingCursors' + results: + type: array + items: {} + description: Current page of results from the requested resource. + required: + - paging + - results + PagingCursors: + title: PagingCursors + type: object + properties: + next: + type: string + description: Cursor for the next page of results. + previous: + type: string + nullable: true + description: Cursor for the previous page of results. + required: + - next + RuleExecutionContext: + title: RuleExecutionContext + type: string + enum: + - prod + - staging + - dev + description: Execution environment for a rule. + AuditInfo: + title: AuditInfo + type: object + description: Common audit metadata. + properties: + createdBy: + type: string + nullable: true + description: The user who created this resource. + createdDateTime: + type: string + format: date-time + nullable: true + description: When this resource was created. + modifiedBy: + type: string + nullable: true + description: The user who last modified this resource. + modifiedDateTime: + type: string + format: date-time + nullable: true + description: When this resource was last modified. + RuleType: + title: RuleType + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + nullable: true + required: + - id + - name + RuleTypeSearchResponse: + title: RuleTypeSearchResponse + type: object + properties: + paging: + $ref: '#/components/schemas/PagingCursors' + results: + type: array + items: + $ref: '#/components/schemas/RuleType' + nullable: true + description: Current page of results from the requested resource. + required: + - paging + User: + title: User + type: object + properties: + id: + type: string + email: + type: string + format: email + required: + - id + - email + UserSearchResponse: + title: UserSearchResponse + type: object + properties: + paging: + $ref: '#/components/schemas/PagingCursors' + results: + type: array + items: + $ref: '#/components/schemas/User' + nullable: true + description: Current page of results from the requested resource. + required: + - paging + RuleResponseStatus: + title: RuleResponseStatus + type: string + enum: + - active + - inactive + - draft + RuleResponse: + title: RuleResponse + type: object + properties: + createdBy: + type: string + nullable: true + description: The user who created this resource. + createdDateTime: + type: string + format: date-time + nullable: true + description: When this resource was created. + modifiedBy: + type: string + nullable: true + description: The user who last modified this resource. + modifiedDateTime: + type: string + format: date-time + nullable: true + description: When this resource was last modified. + id: + type: string + example: id + name: + type: string + example: name + status: + $ref: '#/components/schemas/RuleResponseStatus' + executionContext: + $ref: '#/components/schemas/RuleExecutionContext' + nullable: true + required: + - id + - name + - status + Identifiable: + title: Identifiable + type: object + properties: + id: + type: string + description: Unique identifier. + name: + type: string + nullable: true + description: Display name from Identifiable. + required: + - id + Describable: + title: Describable + type: object + properties: + name: + type: string + nullable: true + description: Display name from Describable. + summary: + type: string + nullable: true + description: A short summary. + CombinedEntityStatus: + title: CombinedEntityStatus + type: string + enum: + - active + - archived + CombinedEntity: + title: CombinedEntity + type: object + properties: + id: + type: string + description: Unique identifier. + example: id + name: + type: string + nullable: true + description: Display name from Describable. + summary: + type: string + nullable: true + description: A short summary. + status: + $ref: '#/components/schemas/CombinedEntityStatus' + required: + - id + - status + BaseOrgMetadata: + title: BaseOrgMetadata + type: object + properties: + region: + type: string + description: Deployment region from BaseOrg. + tier: + type: string + nullable: true + description: Subscription tier. + required: + - region + BaseOrg: + title: BaseOrg + type: object + properties: + id: + type: string + metadata: + $ref: '#/components/schemas/BaseOrgMetadata' + nullable: true + required: + - id + DetailedOrgMetadata: + title: DetailedOrgMetadata + type: object + properties: + region: + type: string + description: Deployment region from DetailedOrg. + domain: + type: string + nullable: true + description: Custom domain name. + required: + - region + DetailedOrg: + title: DetailedOrg + type: object + properties: + metadata: + $ref: '#/components/schemas/DetailedOrgMetadata' + nullable: true + OrganizationMetadata: + title: OrganizationMetadata + type: object + properties: + region: + type: string + description: Deployment region from DetailedOrg. + domain: + type: string + nullable: true + description: Custom domain name. + required: + - region + Organization: + title: Organization + type: object + properties: + id: + type: string + example: id + metadata: + $ref: '#/components/schemas/OrganizationMetadata' + nullable: true + name: + type: string + example: name + required: + - id + - name + securitySchemes: {} +servers: + - url: https://api.example.com + description: Default diff --git a/seed/openapi/allof-inline/snippet.json b/seed/openapi/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/openapi/allof/openapi.yml b/seed/openapi/allof/openapi.yml new file mode 100644 index 000000000000..a8927f6d41d3 --- /dev/null +++ b/seed/openapi/allof/openapi.yml @@ -0,0 +1,403 @@ +openapi: 3.0.1 +info: + title: allOf Composition + version: '' +paths: + /rule-types: + get: + operationId: searchRuleTypes + tags: + - '' + parameters: + - name: query + in: query + required: false + schema: + type: string + nullable: true + responses: + '200': + description: Paginated list of rule types + content: + application/json: + schema: + $ref: '#/components/schemas/RuleTypeSearchResponse' + examples: + Example1: + value: + paging: + next: next + previous: previous + results: + - id: id + name: name + description: description + summary: Search rule types with paginated results + /rules: + post: + operationId: createRule + tags: + - '' + parameters: [] + responses: + '200': + description: Created rule + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + examples: + Example1: + value: + createdBy: createdBy + createdDateTime: '2024-01-15T09:30:00Z' + modifiedBy: modifiedBy + modifiedDateTime: '2024-01-15T09:30:00Z' + id: id + name: name + status: active + executionContext: prod + summary: Create a rule with constrained execution context + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + example: name + executionContext: + $ref: '#/components/schemas/RuleExecutionContext' + required: + - name + - executionContext + examples: + Example1: + value: + name: name + executionContext: prod + /users: + get: + operationId: listUsers + tags: + - '' + parameters: [] + responses: + '200': + description: Paginated list of users + content: + application/json: + schema: + $ref: '#/components/schemas/UserSearchResponse' + examples: + Example1: + value: + paging: + next: next + previous: previous + results: + - id: id + email: email + summary: List users with paginated results + /entities: + get: + operationId: getEntity + tags: + - '' + parameters: [] + responses: + '200': + description: An entity with properties from multiple parents + content: + application/json: + schema: + $ref: '#/components/schemas/CombinedEntity' + examples: + Example1: + value: + name: name + summary: summary + id: id + status: active + summary: Get an entity that combines multiple parents + /organizations: + get: + operationId: getOrganization + tags: + - '' + parameters: [] + responses: + '200': + description: An organization whose metadata is merged from two parents + content: + application/json: + schema: + $ref: '#/components/schemas/Organization' + examples: + Example1: + value: + metadata: + region: region + tier: tier + id: id + name: name + summary: Get an organization with merged object-typed properties +components: + schemas: + PaginatedResult: + title: PaginatedResult + type: object + properties: + paging: + $ref: '#/components/schemas/PagingCursors' + results: + type: array + items: {} + description: Current page of results from the requested resource. + required: + - paging + - results + PagingCursors: + title: PagingCursors + type: object + properties: + next: + type: string + description: Cursor for the next page of results. + previous: + type: string + nullable: true + description: Cursor for the previous page of results. + required: + - next + RuleExecutionContext: + title: RuleExecutionContext + type: string + enum: + - prod + - staging + - dev + description: Execution environment for a rule. + AuditInfo: + title: AuditInfo + type: object + description: Common audit metadata. + properties: + createdBy: + type: string + nullable: true + description: The user who created this resource. + createdDateTime: + type: string + format: date-time + nullable: true + description: When this resource was created. + modifiedBy: + type: string + nullable: true + description: The user who last modified this resource. + modifiedDateTime: + type: string + format: date-time + nullable: true + description: When this resource was last modified. + RuleType: + title: RuleType + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + nullable: true + required: + - id + - name + RuleTypeSearchResponse: + title: RuleTypeSearchResponse + type: object + properties: + results: + type: array + items: + $ref: '#/components/schemas/RuleType' + nullable: true + description: Current page of results from the requested resource. + paging: + $ref: '#/components/schemas/PagingCursors' + required: + - paging + User: + title: User + type: object + properties: + id: + type: string + email: + type: string + format: email + required: + - id + - email + UserSearchResponse: + title: UserSearchResponse + type: object + properties: + results: + type: array + items: + $ref: '#/components/schemas/User' + nullable: true + description: Current page of results from the requested resource. + paging: + $ref: '#/components/schemas/PagingCursors' + required: + - paging + RuleResponseStatus: + title: RuleResponseStatus + type: string + enum: + - active + - inactive + - draft + RuleResponse: + title: RuleResponse + type: object + properties: + id: + type: string + example: id + name: + type: string + example: name + status: + $ref: '#/components/schemas/RuleResponseStatus' + executionContext: + $ref: '#/components/schemas/RuleExecutionContext' + nullable: true + required: + - id + - name + - status + allOf: + - $ref: '#/components/schemas/AuditInfo' + Identifiable: + title: Identifiable + type: object + properties: + id: + type: string + description: Unique identifier. + name: + type: string + nullable: true + description: Display name from Identifiable. + required: + - id + Describable: + title: Describable + type: object + properties: + name: + type: string + nullable: true + description: Display name from Describable. + summary: + type: string + nullable: true + description: A short summary. + CombinedEntityStatus: + title: CombinedEntityStatus + type: string + enum: + - active + - archived + CombinedEntity: + title: CombinedEntity + type: object + properties: + status: + $ref: '#/components/schemas/CombinedEntityStatus' + id: + type: string + description: Unique identifier. + example: id + name: + type: string + nullable: true + description: Display name from Identifiable. + summary: + type: string + nullable: true + description: A short summary. + required: + - status + - id + BaseOrgMetadata: + title: BaseOrgMetadata + type: object + properties: + region: + type: string + description: Deployment region from BaseOrg. + tier: + type: string + nullable: true + description: Subscription tier. + required: + - region + BaseOrg: + title: BaseOrg + type: object + properties: + id: + type: string + metadata: + $ref: '#/components/schemas/BaseOrgMetadata' + nullable: true + required: + - id + DetailedOrgMetadata: + title: DetailedOrgMetadata + type: object + properties: + region: + type: string + description: Deployment region from DetailedOrg. + domain: + type: string + nullable: true + description: Custom domain name. + required: + - region + DetailedOrg: + title: DetailedOrg + type: object + properties: + metadata: + $ref: '#/components/schemas/DetailedOrgMetadata' + nullable: true + Organization: + title: Organization + type: object + properties: + name: + type: string + example: name + id: + type: string + example: id + metadata: + $ref: '#/components/schemas/BaseOrgMetadata' + nullable: true + required: + - name + - id + securitySchemes: {} +servers: + - url: https://api.example.com + description: Default diff --git a/seed/openapi/allof/snippet.json b/seed/openapi/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/php-model/allof-inline/.fern/metadata.json b/seed/php-model/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..20bd10351f7f --- /dev/null +++ b/seed/php-model/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-php-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/php-model/allof-inline/.github/workflows/ci.yml b/seed/php-model/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..678eb6c9e141 --- /dev/null +++ b/seed/php-model/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test diff --git a/seed/php-model/allof-inline/.gitignore b/seed/php-model/allof-inline/.gitignore new file mode 100644 index 000000000000..31a1aeb14f35 --- /dev/null +++ b/seed/php-model/allof-inline/.gitignore @@ -0,0 +1,5 @@ +.idea +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-model/allof-inline/composer.json b/seed/php-model/allof-inline/composer.json new file mode 100644 index 000000000000..ad30960a8764 --- /dev/null +++ b/seed/php-model/allof-inline/composer.json @@ -0,0 +1,46 @@ +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "php-http/discovery": "^1.0", + "php-http/multipart-stream-builder": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12", + "guzzlehttp/guzzle": "^7.4" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src tests --memory-limit=1G" + } +} \ No newline at end of file diff --git a/seed/php-model/allof-inline/phpstan.neon b/seed/php-model/allof-inline/phpstan.neon new file mode 100644 index 000000000000..780706b8f8a2 --- /dev/null +++ b/seed/php-model/allof-inline/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: max + reportUnmatchedIgnoredErrors: false + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-model/allof-inline/phpunit.xml b/seed/php-model/allof-inline/phpunit.xml new file mode 100644 index 000000000000..54630a51163c --- /dev/null +++ b/seed/php-model/allof-inline/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-model/allof-inline/snippet.json b/seed/php-model/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/php-model/allof-inline/src/AuditInfo.php b/seed/php-model/allof-inline/src/AuditInfo.php new file mode 100644 index 000000000000..36f65f9b9e51 --- /dev/null +++ b/seed/php-model/allof-inline/src/AuditInfo.php @@ -0,0 +1,63 @@ +createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/BaseOrg.php b/seed/php-model/allof-inline/src/BaseOrg.php new file mode 100644 index 000000000000..058cc81a7740 --- /dev/null +++ b/seed/php-model/allof-inline/src/BaseOrg.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/BaseOrgMetadata.php b/seed/php-model/allof-inline/src/BaseOrgMetadata.php new file mode 100644 index 000000000000..d19465ec45a8 --- /dev/null +++ b/seed/php-model/allof-inline/src/BaseOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->tier = $values['tier'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/CombinedEntity.php b/seed/php-model/allof-inline/src/CombinedEntity.php new file mode 100644 index 000000000000..c7bca9c380d5 --- /dev/null +++ b/seed/php-model/allof-inline/src/CombinedEntity.php @@ -0,0 +1,58 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @param array{ + * id: string, + * status: value-of, + * name?: ?string, + * summary?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->id = $values['id']; + $this->name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + $this->status = $values['status']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/CombinedEntityStatus.php b/seed/php-model/allof-inline/src/CombinedEntityStatus.php new file mode 100644 index 000000000000..0f880e407b51 --- /dev/null +++ b/seed/php-model/allof-inline/src/CombinedEntityStatus.php @@ -0,0 +1,9 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: $json"); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php b/seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php new file mode 100644 index 000000000000..1a250c614e45 --- /dev/null +++ b/seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,218 @@ + $data The array to be deserialized. + * @param array $type The type definition from the annotation. + * @return array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (\Throwable) { + // Catching Throwable instead of Exception to handle TypeError + // that occurs when assigning null to non-nullable typed properties + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: $type" + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + /** @var array $data */ + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement JsonSerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, JsonSerializableType::class)) { + throw new JsonException("$type is not a subclass of JsonSerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php b/seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php new file mode 100644 index 000000000000..0dbf3fcc9948 --- /dev/null +++ b/seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ + Extra properties from JSON that don't map to class properties */ + private array $__additionalProperties = []; + + /** @var array Properties that have been explicitly set via setter methods */ + private array $__explicitlySetProperties = []; + + /** + * Serializes the object to a JSON string. + * + * @return string JSON-encoded string representation of the object. + * @throws Exception If encoding fails. + */ + public function toJson(): string + { + $serializedObject = $this->jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey === null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === Date::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + // Include the value if it's not null, OR if it was explicitly set (even to null) + if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { + $result[$jsonKey] = $value; + } + } + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + /** @var array $decodedJson */ + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + $properties = []; + $additionalProperties = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + $properties[$jsonKey] = $property; + } + + foreach ($data as $jsonKey => $value) { + if (!isset($properties[$jsonKey])) { + // This JSON key doesn't map to any class property - add it to additionalProperties + $additionalProperties[$jsonKey] = $value; + continue; + } + + $property = $properties[$jsonKey]; + + // Handle Date annotation + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === Date::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle Array annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + /** @var array $arrayValue */ + $arrayValue = $value; + $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); + } + + $args[$property->getName()] = $value; + } + + // Fill in any missing properties with defaults + foreach ($properties as $property) { + if (!isset($args[$property->getName()])) { + $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; + } + } + + // @phpstan-ignore-next-line + $result = new static($args); + $result->__additionalProperties = $additionalProperties; + return $result; + } + + /** + * Get properties from JSON that weren't mapped to class fields + * @return array + */ + public function getAdditionalProperties(): array + { + return $this->__additionalProperties; + } + + /** + * Mark a property as explicitly set. + * This ensures the property will be included in JSON serialization even if null. + * + * @param string $propertyName The name of the property to mark as explicitly set. + */ + protected function _setField(string $propertyName): void + { + $this->__explicitlySetProperties[$propertyName] = true; + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php b/seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php new file mode 100644 index 000000000000..f7d80ed5e8f3 --- /dev/null +++ b/seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php @@ -0,0 +1,205 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + $formatted = $date->format(Constant::DateTimeFormat); + if (str_ends_with($formatted, '+00:00')) { + return substr($formatted, 0, -6) . 'Z'; + } + return $formatted; + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param array $data The array to be serialized. + * @param array $type The type definition from the annotation. + * @return array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: $unionType" + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/allof-inline/src/Core/Json/Utils.php b/seed/php-model/allof-inline/src/Core/Json/Utils.php new file mode 100644 index 000000000000..4099b8253005 --- /dev/null +++ b/seed/php-model/allof-inline/src/Core/Json/Utils.php @@ -0,0 +1,62 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return int|string The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): int|string + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + // PHP arrays don't support float keys; truncate to int + 'float' => (int)$key, + 'string' => (string)$key, + default => is_int($key) ? $key : (string)$key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/allof-inline/src/Core/Types/ArrayType.php b/seed/php-model/allof-inline/src/Core/Types/ArrayType.php new file mode 100644 index 000000000000..a26d29008ec3 --- /dev/null +++ b/seed/php-model/allof-inline/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/allof-inline/src/Core/Types/Constant.php b/seed/php-model/allof-inline/src/Core/Types/Constant.php new file mode 100644 index 000000000000..5ac4518cc6d6 --- /dev/null +++ b/seed/php-model/allof-inline/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/allof-inline/src/Describable.php b/seed/php-model/allof-inline/src/Describable.php new file mode 100644 index 000000000000..54244b8b6d52 --- /dev/null +++ b/seed/php-model/allof-inline/src/Describable.php @@ -0,0 +1,42 @@ +name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/DetailedOrg.php b/seed/php-model/allof-inline/src/DetailedOrg.php new file mode 100644 index 000000000000..398ab2d6da4d --- /dev/null +++ b/seed/php-model/allof-inline/src/DetailedOrg.php @@ -0,0 +1,34 @@ +metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/DetailedOrgMetadata.php b/seed/php-model/allof-inline/src/DetailedOrgMetadata.php new file mode 100644 index 000000000000..cd14f445e6bb --- /dev/null +++ b/seed/php-model/allof-inline/src/DetailedOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->domain = $values['domain'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/Identifiable.php b/seed/php-model/allof-inline/src/Identifiable.php new file mode 100644 index 000000000000..beffe19d83ea --- /dev/null +++ b/seed/php-model/allof-inline/src/Identifiable.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->name = $values['name'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/Organization.php b/seed/php-model/allof-inline/src/Organization.php new file mode 100644 index 000000000000..a19d1d695019 --- /dev/null +++ b/seed/php-model/allof-inline/src/Organization.php @@ -0,0 +1,50 @@ +id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + $this->name = $values['name']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/OrganizationMetadata.php b/seed/php-model/allof-inline/src/OrganizationMetadata.php new file mode 100644 index 000000000000..1e0038954f23 --- /dev/null +++ b/seed/php-model/allof-inline/src/OrganizationMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->domain = $values['domain'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/PaginatedResult.php b/seed/php-model/allof-inline/src/PaginatedResult.php new file mode 100644 index 000000000000..708f16632a8a --- /dev/null +++ b/seed/php-model/allof-inline/src/PaginatedResult.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType(['mixed'])] + public array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/PagingCursors.php b/seed/php-model/allof-inline/src/PagingCursors.php new file mode 100644 index 000000000000..d5fd868b3f76 --- /dev/null +++ b/seed/php-model/allof-inline/src/PagingCursors.php @@ -0,0 +1,42 @@ +next = $values['next']; + $this->previous = $values['previous'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/RuleExecutionContext.php b/seed/php-model/allof-inline/src/RuleExecutionContext.php new file mode 100644 index 000000000000..f74be26cce99 --- /dev/null +++ b/seed/php-model/allof-inline/src/RuleExecutionContext.php @@ -0,0 +1,10 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @var ?value-of $executionContext + */ + #[JsonProperty('executionContext')] + public ?string $executionContext; + + /** + * @param array{ + * id: string, + * name: string, + * status: value-of, + * createdBy?: ?string, + * createdDateTime?: ?DateTime, + * modifiedBy?: ?string, + * modifiedDateTime?: ?DateTime, + * executionContext?: ?value-of, + * } $values + */ + public function __construct( + array $values, + ) { + $this->createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + $this->id = $values['id']; + $this->name = $values['name']; + $this->status = $values['status']; + $this->executionContext = $values['executionContext'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/RuleResponseStatus.php b/seed/php-model/allof-inline/src/RuleResponseStatus.php new file mode 100644 index 000000000000..c14b15f636cf --- /dev/null +++ b/seed/php-model/allof-inline/src/RuleResponseStatus.php @@ -0,0 +1,10 @@ +id = $values['id']; + $this->name = $values['name']; + $this->description = $values['description'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/RuleTypeSearchResponse.php b/seed/php-model/allof-inline/src/RuleTypeSearchResponse.php new file mode 100644 index 000000000000..f80bd406c292 --- /dev/null +++ b/seed/php-model/allof-inline/src/RuleTypeSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([RuleType::class])] + public ?array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/User.php b/seed/php-model/allof-inline/src/User.php new file mode 100644 index 000000000000..42f9a1b02b7f --- /dev/null +++ b/seed/php-model/allof-inline/src/User.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->email = $values['email']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/src/UserSearchResponse.php b/seed/php-model/allof-inline/src/UserSearchResponse.php new file mode 100644 index 000000000000..e959fabc832f --- /dev/null +++ b/seed/php-model/allof-inline/src/UserSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([User::class])] + public ?array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php new file mode 100644 index 000000000000..2c32002340e7 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php @@ -0,0 +1,76 @@ +name; + } + + /** + * @return string|null + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * @param array{ + * name: string, + * email?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->email = $values['email'] ?? null; + } +} + +class AdditionalPropertiesTest extends TestCase +{ + public function testExtraProperties(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'name' => 'john.doe', + 'email' => 'john.doe@example.com', + 'age' => 42 + ], + ); + + $person = Person::fromJson($expectedJson); + $this->assertEquals('john.doe', $person->getName()); + $this->assertEquals('john.doe@example.com', $person->getEmail()); + $this->assertEquals( + [ + 'age' => 42 + ], + $person->getAdditionalProperties(), + ); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php new file mode 100644 index 000000000000..e7794d652432 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php @@ -0,0 +1,54 @@ +dates = $values['dates']; + } +} + +class DateArrayTest extends TestCase +{ + public function testDateTimeInArrays(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ], + ); + + $object = DateArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php new file mode 100644 index 000000000000..b5f217e01f76 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php @@ -0,0 +1,71 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArrayTest extends TestCase +{ + public function testEmptyArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ], + ); + + $object = EmptyArray::fromJson($expectedJson); + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/EnumTest.php b/seed/php-model/allof-inline/tests/Core/Json/EnumTest.php new file mode 100644 index 000000000000..72dc6f2cfa00 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/EnumTest.php @@ -0,0 +1,77 @@ +value; + } +} + +class ShapeType extends JsonSerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = JsonEncoder::encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ]); + + $actualJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $actualJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php b/seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php new file mode 100644 index 000000000000..4c288378b48b --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php @@ -0,0 +1,197 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class Type extends JsonSerializableType +{ + /** + * @var Nested nestedType + */ + #[JsonProperty('nested_type')] + public Nested $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[Date(Date::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[Date(Date::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(Nested::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: Nested, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class ExhaustiveTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in Type. + */ + public function testExhaustive(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // Omit 'nullable_property' to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56Z', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> + ], + ); + + $object = Type::fromJson($expectedJson); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php b/seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php new file mode 100644 index 000000000000..9d845ea113b8 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php @@ -0,0 +1,42 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTest extends TestCase +{ + public function testInvalidJsonThrowsException(): void + { + $this->expectException(\TypeError::class); + $json = JsonEncoder::encode( + [ + 'integer_property' => 'not_an_integer' + ], + ); + Invalid::fromJson($json); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php new file mode 100644 index 000000000000..8fbbeb939f02 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php @@ -0,0 +1,89 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArray extends JsonSerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTest extends TestCase +{ + public function testNestedUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ], + ); + + $object = NestedUnionArray::fromJson($expectedJson); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php b/seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php new file mode 100644 index 000000000000..ce20a2442825 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php @@ -0,0 +1,53 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullProperty( + [ + "nonNullProperty" => "Test String", + "nullProperty" => null + ] + ); + + $serialized = $object->jsonSerialize(); + $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); + $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php new file mode 100644 index 000000000000..d1749c434a4c --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php @@ -0,0 +1,49 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTest extends TestCase +{ + public function testNullableArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nullable_string_array' => ['one', null, 'three'] + ], + ); + + $object = NullableArray::fromJson($expectedJson); + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php b/seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php new file mode 100644 index 000000000000..ad4db0251bb5 --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php @@ -0,0 +1,116 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats + ], + ); + + $object = Scalar::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/TraitTest.php b/seed/php-model/allof-inline/tests/Core/Json/TraitTest.php new file mode 100644 index 000000000000..e18f06d4191b --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/TraitTest.php @@ -0,0 +1,60 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ], + ); + + $object = TypeWithTrait::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php new file mode 100644 index 000000000000..de20cf9fde1b --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php @@ -0,0 +1,57 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class UnionArrayTest extends TestCase +{ + public function testUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00Z', + 2 => null, + 3 => 'Some String' + ] + ], + ); + + $object = UnionArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php b/seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php new file mode 100644 index 000000000000..f733062cfabc --- /dev/null +++ b/seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php @@ -0,0 +1,111 @@ + 'integer'], UnionProperty::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionProperty + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => [1 => 100, 2 => 200] + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => new UnionProperty( + [ + 'complexUnion' => 'Nested String' + ] + ) + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $expectedJson = JsonEncoder::encode( + [], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 42 + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 'Some String' + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/allof/.fern/metadata.json b/seed/php-model/allof/.fern/metadata.json new file mode 100644 index 000000000000..20bd10351f7f --- /dev/null +++ b/seed/php-model/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-php-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/php-model/allof/.github/workflows/ci.yml b/seed/php-model/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..678eb6c9e141 --- /dev/null +++ b/seed/php-model/allof/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test diff --git a/seed/php-model/allof/.gitignore b/seed/php-model/allof/.gitignore new file mode 100644 index 000000000000..31a1aeb14f35 --- /dev/null +++ b/seed/php-model/allof/.gitignore @@ -0,0 +1,5 @@ +.idea +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-model/allof/composer.json b/seed/php-model/allof/composer.json new file mode 100644 index 000000000000..ad30960a8764 --- /dev/null +++ b/seed/php-model/allof/composer.json @@ -0,0 +1,46 @@ +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "php-http/discovery": "^1.0", + "php-http/multipart-stream-builder": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12", + "guzzlehttp/guzzle": "^7.4" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src tests --memory-limit=1G" + } +} \ No newline at end of file diff --git a/seed/php-model/allof/phpstan.neon b/seed/php-model/allof/phpstan.neon new file mode 100644 index 000000000000..780706b8f8a2 --- /dev/null +++ b/seed/php-model/allof/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: max + reportUnmatchedIgnoredErrors: false + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-model/allof/phpunit.xml b/seed/php-model/allof/phpunit.xml new file mode 100644 index 000000000000..54630a51163c --- /dev/null +++ b/seed/php-model/allof/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-model/allof/snippet.json b/seed/php-model/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/php-model/allof/src/AuditInfo.php b/seed/php-model/allof/src/AuditInfo.php new file mode 100644 index 000000000000..36f65f9b9e51 --- /dev/null +++ b/seed/php-model/allof/src/AuditInfo.php @@ -0,0 +1,63 @@ +createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/BaseOrg.php b/seed/php-model/allof/src/BaseOrg.php new file mode 100644 index 000000000000..058cc81a7740 --- /dev/null +++ b/seed/php-model/allof/src/BaseOrg.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/BaseOrgMetadata.php b/seed/php-model/allof/src/BaseOrgMetadata.php new file mode 100644 index 000000000000..d19465ec45a8 --- /dev/null +++ b/seed/php-model/allof/src/BaseOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->tier = $values['tier'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/CombinedEntity.php b/seed/php-model/allof/src/CombinedEntity.php new file mode 100644 index 000000000000..df398f7242f3 --- /dev/null +++ b/seed/php-model/allof/src/CombinedEntity.php @@ -0,0 +1,58 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @var string $id Unique identifier. + */ + #[JsonProperty('id')] + public string $id; + + /** + * @var ?string $name Display name from Identifiable. + */ + #[JsonProperty('name')] + public ?string $name; + + /** + * @var ?string $summary A short summary. + */ + #[JsonProperty('summary')] + public ?string $summary; + + /** + * @param array{ + * status: value-of, + * id: string, + * name?: ?string, + * summary?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->status = $values['status']; + $this->id = $values['id']; + $this->name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/CombinedEntityStatus.php b/seed/php-model/allof/src/CombinedEntityStatus.php new file mode 100644 index 000000000000..0f880e407b51 --- /dev/null +++ b/seed/php-model/allof/src/CombinedEntityStatus.php @@ -0,0 +1,9 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: $json"); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/allof/src/Core/Json/JsonDeserializer.php b/seed/php-model/allof/src/Core/Json/JsonDeserializer.php new file mode 100644 index 000000000000..1a250c614e45 --- /dev/null +++ b/seed/php-model/allof/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,218 @@ + $data The array to be deserialized. + * @param array $type The type definition from the annotation. + * @return array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (\Throwable) { + // Catching Throwable instead of Exception to handle TypeError + // that occurs when assigning null to non-nullable typed properties + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: $type" + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + /** @var array $data */ + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement JsonSerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, JsonSerializableType::class)) { + throw new JsonException("$type is not a subclass of JsonSerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/allof/src/Core/Json/JsonEncoder.php b/seed/php-model/allof/src/Core/Json/JsonEncoder.php new file mode 100644 index 000000000000..0dbf3fcc9948 --- /dev/null +++ b/seed/php-model/allof/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ + Extra properties from JSON that don't map to class properties */ + private array $__additionalProperties = []; + + /** @var array Properties that have been explicitly set via setter methods */ + private array $__explicitlySetProperties = []; + + /** + * Serializes the object to a JSON string. + * + * @return string JSON-encoded string representation of the object. + * @throws Exception If encoding fails. + */ + public function toJson(): string + { + $serializedObject = $this->jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey === null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === Date::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + // Include the value if it's not null, OR if it was explicitly set (even to null) + if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { + $result[$jsonKey] = $value; + } + } + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + /** @var array $decodedJson */ + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + $properties = []; + $additionalProperties = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + $properties[$jsonKey] = $property; + } + + foreach ($data as $jsonKey => $value) { + if (!isset($properties[$jsonKey])) { + // This JSON key doesn't map to any class property - add it to additionalProperties + $additionalProperties[$jsonKey] = $value; + continue; + } + + $property = $properties[$jsonKey]; + + // Handle Date annotation + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === Date::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle Array annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + /** @var array $arrayValue */ + $arrayValue = $value; + $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); + } + + $args[$property->getName()] = $value; + } + + // Fill in any missing properties with defaults + foreach ($properties as $property) { + if (!isset($args[$property->getName()])) { + $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; + } + } + + // @phpstan-ignore-next-line + $result = new static($args); + $result->__additionalProperties = $additionalProperties; + return $result; + } + + /** + * Get properties from JSON that weren't mapped to class fields + * @return array + */ + public function getAdditionalProperties(): array + { + return $this->__additionalProperties; + } + + /** + * Mark a property as explicitly set. + * This ensures the property will be included in JSON serialization even if null. + * + * @param string $propertyName The name of the property to mark as explicitly set. + */ + protected function _setField(string $propertyName): void + { + $this->__explicitlySetProperties[$propertyName] = true; + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/allof/src/Core/Json/JsonSerializer.php b/seed/php-model/allof/src/Core/Json/JsonSerializer.php new file mode 100644 index 000000000000..f7d80ed5e8f3 --- /dev/null +++ b/seed/php-model/allof/src/Core/Json/JsonSerializer.php @@ -0,0 +1,205 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + $formatted = $date->format(Constant::DateTimeFormat); + if (str_ends_with($formatted, '+00:00')) { + return substr($formatted, 0, -6) . 'Z'; + } + return $formatted; + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param array $data The array to be serialized. + * @param array $type The type definition from the annotation. + * @return array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) !== "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: $unionType" + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. + if ($type === 'bool' && is_bool($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $keyType = (string) $keyType; + $valueType = $type[$keyType]; + /** @var array $result */ + $result = []; + + foreach ($data as $key => $item) { + $key = (string) Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + /** @var array */ + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/allof/src/Core/Json/Utils.php b/seed/php-model/allof/src/Core/Json/Utils.php new file mode 100644 index 000000000000..4099b8253005 --- /dev/null +++ b/seed/php-model/allof/src/Core/Json/Utils.php @@ -0,0 +1,62 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return int|string The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): int|string + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + // PHP arrays don't support float keys; truncate to int + 'float' => (int)$key, + 'string' => (string)$key, + default => is_int($key) ? $key : (string)$key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/allof/src/Core/Types/ArrayType.php b/seed/php-model/allof/src/Core/Types/ArrayType.php new file mode 100644 index 000000000000..a26d29008ec3 --- /dev/null +++ b/seed/php-model/allof/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/allof/src/Core/Types/Constant.php b/seed/php-model/allof/src/Core/Types/Constant.php new file mode 100644 index 000000000000..5ac4518cc6d6 --- /dev/null +++ b/seed/php-model/allof/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/allof/src/Describable.php b/seed/php-model/allof/src/Describable.php new file mode 100644 index 000000000000..54244b8b6d52 --- /dev/null +++ b/seed/php-model/allof/src/Describable.php @@ -0,0 +1,42 @@ +name = $values['name'] ?? null; + $this->summary = $values['summary'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/DetailedOrg.php b/seed/php-model/allof/src/DetailedOrg.php new file mode 100644 index 000000000000..398ab2d6da4d --- /dev/null +++ b/seed/php-model/allof/src/DetailedOrg.php @@ -0,0 +1,34 @@ +metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/DetailedOrgMetadata.php b/seed/php-model/allof/src/DetailedOrgMetadata.php new file mode 100644 index 000000000000..cd14f445e6bb --- /dev/null +++ b/seed/php-model/allof/src/DetailedOrgMetadata.php @@ -0,0 +1,42 @@ +region = $values['region']; + $this->domain = $values['domain'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/Identifiable.php b/seed/php-model/allof/src/Identifiable.php new file mode 100644 index 000000000000..beffe19d83ea --- /dev/null +++ b/seed/php-model/allof/src/Identifiable.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->name = $values['name'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/Organization.php b/seed/php-model/allof/src/Organization.php new file mode 100644 index 000000000000..51776ae2fe4b --- /dev/null +++ b/seed/php-model/allof/src/Organization.php @@ -0,0 +1,50 @@ +name = $values['name']; + $this->id = $values['id']; + $this->metadata = $values['metadata'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/PaginatedResult.php b/seed/php-model/allof/src/PaginatedResult.php new file mode 100644 index 000000000000..708f16632a8a --- /dev/null +++ b/seed/php-model/allof/src/PaginatedResult.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType(['mixed'])] + public array $results; + + /** + * @param array{ + * paging: PagingCursors, + * results: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->paging = $values['paging']; + $this->results = $values['results']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/PagingCursors.php b/seed/php-model/allof/src/PagingCursors.php new file mode 100644 index 000000000000..d5fd868b3f76 --- /dev/null +++ b/seed/php-model/allof/src/PagingCursors.php @@ -0,0 +1,42 @@ +next = $values['next']; + $this->previous = $values['previous'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/RuleExecutionContext.php b/seed/php-model/allof/src/RuleExecutionContext.php new file mode 100644 index 000000000000..f74be26cce99 --- /dev/null +++ b/seed/php-model/allof/src/RuleExecutionContext.php @@ -0,0 +1,10 @@ + $status + */ + #[JsonProperty('status')] + public string $status; + + /** + * @var ?value-of $executionContext + */ + #[JsonProperty('executionContext')] + public ?string $executionContext; + + /** + * @param array{ + * id: string, + * name: string, + * status: value-of, + * createdBy?: ?string, + * createdDateTime?: ?DateTime, + * modifiedBy?: ?string, + * modifiedDateTime?: ?DateTime, + * executionContext?: ?value-of, + * } $values + */ + public function __construct( + array $values, + ) { + $this->createdBy = $values['createdBy'] ?? null; + $this->createdDateTime = $values['createdDateTime'] ?? null; + $this->modifiedBy = $values['modifiedBy'] ?? null; + $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; + $this->id = $values['id']; + $this->name = $values['name']; + $this->status = $values['status']; + $this->executionContext = $values['executionContext'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/RuleResponseStatus.php b/seed/php-model/allof/src/RuleResponseStatus.php new file mode 100644 index 000000000000..c14b15f636cf --- /dev/null +++ b/seed/php-model/allof/src/RuleResponseStatus.php @@ -0,0 +1,10 @@ +id = $values['id']; + $this->name = $values['name']; + $this->description = $values['description'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/RuleTypeSearchResponse.php b/seed/php-model/allof/src/RuleTypeSearchResponse.php new file mode 100644 index 000000000000..45632f5487a1 --- /dev/null +++ b/seed/php-model/allof/src/RuleTypeSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([RuleType::class])] + public ?array $results; + + /** + * @var PagingCursors $paging + */ + #[JsonProperty('paging')] + public PagingCursors $paging; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->results = $values['results'] ?? null; + $this->paging = $values['paging']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/Traits/AuditInfo.php b/seed/php-model/allof/src/Traits/AuditInfo.php new file mode 100644 index 000000000000..2c2ce1e5f033 --- /dev/null +++ b/seed/php-model/allof/src/Traits/AuditInfo.php @@ -0,0 +1,42 @@ +id = $values['id']; + $this->email = $values['email']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/src/UserSearchResponse.php b/seed/php-model/allof/src/UserSearchResponse.php new file mode 100644 index 000000000000..d43e218c9d99 --- /dev/null +++ b/seed/php-model/allof/src/UserSearchResponse.php @@ -0,0 +1,43 @@ + $results Current page of results from the requested resource. + */ + #[JsonProperty('results'), ArrayType([User::class])] + public ?array $results; + + /** + * @var PagingCursors $paging + */ + #[JsonProperty('paging')] + public PagingCursors $paging; + + /** + * @param array{ + * paging: PagingCursors, + * results?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->results = $values['results'] ?? null; + $this->paging = $values['paging']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php new file mode 100644 index 000000000000..2c32002340e7 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php @@ -0,0 +1,76 @@ +name; + } + + /** + * @return string|null + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * @param array{ + * name: string, + * email?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->email = $values['email'] ?? null; + } +} + +class AdditionalPropertiesTest extends TestCase +{ + public function testExtraProperties(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'name' => 'john.doe', + 'email' => 'john.doe@example.com', + 'age' => 42 + ], + ); + + $person = Person::fromJson($expectedJson); + $this->assertEquals('john.doe', $person->getName()); + $this->assertEquals('john.doe@example.com', $person->getEmail()); + $this->assertEquals( + [ + 'age' => 42 + ], + $person->getAdditionalProperties(), + ); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/DateArrayTest.php b/seed/php-model/allof/tests/Core/Json/DateArrayTest.php new file mode 100644 index 000000000000..e7794d652432 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/DateArrayTest.php @@ -0,0 +1,54 @@ +dates = $values['dates']; + } +} + +class DateArrayTest extends TestCase +{ + public function testDateTimeInArrays(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ], + ); + + $object = DateArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php b/seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php new file mode 100644 index 000000000000..b5f217e01f76 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php @@ -0,0 +1,71 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArrayTest extends TestCase +{ + public function testEmptyArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ], + ); + + $object = EmptyArray::fromJson($expectedJson); + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/EnumTest.php b/seed/php-model/allof/tests/Core/Json/EnumTest.php new file mode 100644 index 000000000000..72dc6f2cfa00 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/EnumTest.php @@ -0,0 +1,77 @@ +value; + } +} + +class ShapeType extends JsonSerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = JsonEncoder::encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ]); + + $actualJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $actualJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php b/seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php new file mode 100644 index 000000000000..4c288378b48b --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php @@ -0,0 +1,197 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class Type extends JsonSerializableType +{ + /** + * @var Nested nestedType + */ + #[JsonProperty('nested_type')] + public Nested $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[Date(Date::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[Date(Date::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(Nested::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: Nested, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class ExhaustiveTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in Type. + */ + public function testExhaustive(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // Omit 'nullable_property' to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56Z', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> + ], + ); + + $object = Type::fromJson($expectedJson); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/InvalidTest.php b/seed/php-model/allof/tests/Core/Json/InvalidTest.php new file mode 100644 index 000000000000..9d845ea113b8 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/InvalidTest.php @@ -0,0 +1,42 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTest extends TestCase +{ + public function testInvalidJsonThrowsException(): void + { + $this->expectException(\TypeError::class); + $json = JsonEncoder::encode( + [ + 'integer_property' => 'not_an_integer' + ], + ); + Invalid::fromJson($json); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php new file mode 100644 index 000000000000..8fbbeb939f02 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php @@ -0,0 +1,89 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArray extends JsonSerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTest extends TestCase +{ + public function testNestedUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ], + ); + + $object = NestedUnionArray::fromJson($expectedJson); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/NullPropertyTest.php b/seed/php-model/allof/tests/Core/Json/NullPropertyTest.php new file mode 100644 index 000000000000..ce20a2442825 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/NullPropertyTest.php @@ -0,0 +1,53 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullProperty( + [ + "nonNullProperty" => "Test String", + "nullProperty" => null + ] + ); + + $serialized = $object->jsonSerialize(); + $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); + $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/NullableArrayTest.php b/seed/php-model/allof/tests/Core/Json/NullableArrayTest.php new file mode 100644 index 000000000000..d1749c434a4c --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/NullableArrayTest.php @@ -0,0 +1,49 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTest extends TestCase +{ + public function testNullableArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'nullable_string_array' => ['one', null, 'three'] + ], + ); + + $object = NullableArray::fromJson($expectedJson); + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/ScalarTest.php b/seed/php-model/allof/tests/Core/Json/ScalarTest.php new file mode 100644 index 000000000000..ad4db0251bb5 --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/ScalarTest.php @@ -0,0 +1,116 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats + ], + ); + + $object = Scalar::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/TraitTest.php b/seed/php-model/allof/tests/Core/Json/TraitTest.php new file mode 100644 index 000000000000..e18f06d4191b --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/TraitTest.php @@ -0,0 +1,60 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ], + ); + + $object = TypeWithTrait::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/UnionArrayTest.php b/seed/php-model/allof/tests/Core/Json/UnionArrayTest.php new file mode 100644 index 000000000000..de20cf9fde1b --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/UnionArrayTest.php @@ -0,0 +1,57 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class UnionArrayTest extends TestCase +{ + public function testUnionArray(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00Z', + 2 => null, + 3 => 'Some String' + ] + ], + ); + + $object = UnionArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php b/seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php new file mode 100644 index 000000000000..f733062cfabc --- /dev/null +++ b/seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php @@ -0,0 +1,111 @@ + 'integer'], UnionProperty::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionProperty + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => [1 => 100, 2 => 200] + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => new UnionProperty( + [ + 'complexUnion' => 'Nested String' + ] + ) + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $expectedJson = JsonEncoder::encode( + [], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 42 + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $expectedJson = JsonEncoder::encode( + [ + 'complexUnion' => 'Some String' + ], + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/allof-inline/.fern/metadata.json b/seed/php-sdk/allof-inline/.fern/metadata.json index 37f759a1679d..143d7b896167 100644 --- a/seed/php-sdk/allof-inline/.fern/metadata.json +++ b/seed/php-sdk/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-php-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/php-sdk/allof/.fern/metadata.json b/seed/php-sdk/allof/.fern/metadata.json index 37f759a1679d..143d7b896167 100644 --- a/seed/php-sdk/allof/.fern/metadata.json +++ b/seed/php-sdk/allof/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-php-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/pydantic/allof-inline/.github/workflows/ci.yml b/seed/pydantic/allof-inline/.github/workflows/ci.yml new file mode 100644 index 000000000000..fd1df043d08d --- /dev/null +++ b/seed/pydantic/allof-inline/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: ci +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest -rP -n auto . + + - name: Install aiohttp extra + run: poetry install --extras aiohttp + + - name: Test (aiohttp) + run: poetry run pytest -rP -n auto -m aiohttp . + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" + env: + PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/pydantic/allof-inline/.gitignore b/seed/pydantic/allof-inline/.gitignore new file mode 100644 index 000000000000..d2e4ca808d21 --- /dev/null +++ b/seed/pydantic/allof-inline/.gitignore @@ -0,0 +1,5 @@ +.mypy_cache/ +.ruff_cache/ +__pycache__/ +dist/ +poetry.toml diff --git a/seed/pydantic/allof-inline/README.md b/seed/pydantic/allof-inline/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/pydantic/allof-inline/poetry.lock b/seed/pydantic/allof-inline/poetry.lock new file mode 100644 index 000000000000..e1efb51734e4 --- /dev/null +++ b/seed/pydantic/allof-inline/poetry.lock @@ -0,0 +1,546 @@ +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.1.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pydantic" +version = "2.12.5" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, +] + +[package.dependencies] +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "ruff" +version = "0.11.5" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, + {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, + {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, + {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, + {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, + {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, + {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tomli" +version = "2.4.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20260408" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.10" +files = [ + {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, + {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "283a69ea9a1eb309027a93a4e056bedb5cb3c6018ca2eec381af57784ad1eb58" diff --git a/seed/pydantic/allof-inline/pyproject.toml b/seed/pydantic/allof-inline/pyproject.toml new file mode 100644 index 000000000000..2ce1377736c7 --- /dev/null +++ b/seed/pydantic/allof-inline/pyproject.toml @@ -0,0 +1,93 @@ +[project] +name = "fern_allof-inline" +dynamic = ["version"] + +[tool.poetry] +name = "fern_allof-inline" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [ + "fern", + "test" +] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed/api", from = "src"} +] + +[tool.poetry.urls] +Documentation = 'https://buildwithfern.com/learn' +Homepage = 'https://buildwithfern.com/' +Repository = 'https://github.com/allof-inline/fern' + +[tool.poetry.dependencies] +python = "^3.10" +pydantic = ">= 1.9.2" +pydantic-core = ">=2.18.2,<2.44.0" + +[tool.poetry.group.dev.dependencies] +mypy = "==1.13.0" +pytest = "^8.2.0" +pytest-asyncio = "^1.0.0" +pytest-xdist = "^3.6.1" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" +ruff = "==0.11.5" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" +markers = [ + "aiohttp: tests that require httpx_aiohttp to be installed", +] + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort +] +ignore = [ + "E402", # Module level import not at top of file + "E501", # Line too long + "E711", # Comparison to `None` should be `cond is not None` + "E712", # Avoid equality comparisons to `True`; use `if ...:` checks + "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks + "E722", # Do not use bare `except` + "E731", # Do not assign a `lambda` expression, use a `def` + "F821", # Undefined name + "F841" # Local variable ... is assigned to but never used +] + +[tool.ruff.lint.isort] +section-order = ["future", "standard-library", "third-party", "first-party"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/pydantic/allof-inline/requirements.txt b/seed/pydantic/allof-inline/requirements.txt new file mode 100644 index 000000000000..e090fd9ba093 --- /dev/null +++ b/seed/pydantic/allof-inline/requirements.txt @@ -0,0 +1,2 @@ +pydantic>= 1.9.2 +pydantic-core>=2.18.2,<2.44.0 diff --git a/seed/pydantic/allof-inline/snippet.json b/seed/pydantic/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/pydantic/allof-inline/src/seed/api/__init__.py b/seed/pydantic/allof-inline/src/seed/api/__init__.py new file mode 100644 index 000000000000..881ca901e048 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/__init__.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +from .audit_info import AuditInfo +from .base_org import BaseOrg +from .base_org_metadata import BaseOrgMetadata +from .combined_entity import CombinedEntity +from .combined_entity_status import CombinedEntityStatus +from .describable import Describable +from .detailed_org import DetailedOrg +from .detailed_org_metadata import DetailedOrgMetadata +from .identifiable import Identifiable +from .organization import Organization +from .organization_metadata import OrganizationMetadata +from .paginated_result import PaginatedResult +from .paging_cursors import PagingCursors +from .rule_execution_context import RuleExecutionContext +from .rule_response import RuleResponse +from .rule_response_status import RuleResponseStatus +from .rule_type import RuleType +from .rule_type_search_response import RuleTypeSearchResponse +from .user import User +from .user_search_response import UserSearchResponse + +__all__ = [ + "AuditInfo", + "BaseOrg", + "BaseOrgMetadata", + "CombinedEntity", + "CombinedEntityStatus", + "Describable", + "DetailedOrg", + "DetailedOrgMetadata", + "Identifiable", + "Organization", + "OrganizationMetadata", + "PaginatedResult", + "PagingCursors", + "RuleExecutionContext", + "RuleResponse", + "RuleResponseStatus", + "RuleType", + "RuleTypeSearchResponse", + "User", + "UserSearchResponse", +] diff --git a/seed/pydantic/allof-inline/src/seed/api/audit_info.py b/seed/pydantic/allof-inline/src/seed/api/audit_info.py new file mode 100644 index 000000000000..5b00ec223d32 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/audit_info.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .core.serialization import FieldMetadata + + +class AuditInfo(UniversalBaseModel): + """ + Common audit metadata. + """ + + created_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="createdBy"), + pydantic.Field(alias="createdBy", description="The user who created this resource."), + ] = None + created_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="createdDateTime"), + pydantic.Field(alias="createdDateTime", description="When this resource was created."), + ] = None + modified_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="modifiedBy"), + pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), + ] = None + modified_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="modifiedDateTime"), + pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/base_org.py b/seed/pydantic/allof-inline/src/seed/api/base_org.py new file mode 100644 index 000000000000..512f3ed40d4e --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/base_org.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_org_metadata import BaseOrgMetadata +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class BaseOrg(UniversalBaseModel): + id: str + metadata: typing.Optional[BaseOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py b/seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py new file mode 100644 index 000000000000..8c58e204c200 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class BaseOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from BaseOrg. + """ + + tier: typing.Optional[str] = pydantic.Field(default=None) + """ + Subscription tier. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/combined_entity.py b/seed/pydantic/allof-inline/src/seed/api/combined_entity.py new file mode 100644 index 000000000000..a8e7f211dc26 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/combined_entity.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .combined_entity_status import CombinedEntityStatus +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CombinedEntity(UniversalBaseModel): + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Describable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + status: CombinedEntityStatus + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py b/seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py new file mode 100644 index 000000000000..42ac60430cd7 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/pydantic/allof-inline/src/seed/api/core/__init__.py b/seed/pydantic/allof-inline/src/seed/api/core/__init__.py new file mode 100644 index 000000000000..f9bdc1729e86 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/core/__init__.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .serialization import FieldMetadata + +__all__ = [ + "FieldMetadata", + "IS_PYDANTIC_V2", + "Rfc2822DateTime", + "UniversalBaseModel", + "UniversalRootModel", + "parse_obj_as", + "parse_rfc2822_datetime", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py b/seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py new file mode 100644 index 000000000000..a12b2ad03c53 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +from email.utils import parsedate_to_datetime +from typing import Any + +import pydantic + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + + +def parse_rfc2822_datetime(v: Any) -> dt.datetime: + """ + Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") + into a datetime object. If the value is already a datetime, return it as-is. + Falls back to ISO 8601 parsing if RFC 2822 parsing fails. + """ + if isinstance(v, dt.datetime): + return v + if isinstance(v, str): + try: + return parsedate_to_datetime(v) + except Exception: + pass + # Fallback to ISO 8601 parsing + return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) + raise ValueError(f"Expected str or datetime, got {type(v)}") + + +class Rfc2822DateTime(dt.datetime): + """A datetime subclass that parses RFC 2822 date strings. + + On Pydantic V1, uses __get_validators__ for pre-validation. + On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. + """ + + @classmethod + def __get_validators__(cls): # type: ignore[no-untyped-def] + yield parse_rfc2822_datetime + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] + from pydantic_core import core_schema + + return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/pydantic/allof-inline/src/seed/api/core/enum.py b/seed/pydantic/allof-inline/src/seed/api/core/enum.py new file mode 100644 index 000000000000..a3d17a67b128 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/core/enum.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +Provides a StrEnum base class that works across Python versions. + +For Python >= 3.11, this re-exports the standard library enum.StrEnum. +For older Python versions, this defines a compatible StrEnum using the +(str, Enum) mixin pattern so that generated SDKs can use a single base +class in all supported Python versions. +""" + +import enum +import sys + +if sys.version_info >= (3, 11): + from enum import StrEnum +else: + + class StrEnum(str, enum.Enum): + pass diff --git a/seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py b/seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py new file mode 100644 index 000000000000..ed95da8bf8ce --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py @@ -0,0 +1,515 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import inspect +import json +import logging +from collections import defaultdict +from dataclasses import asdict +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +import pydantic +import typing_extensions +from pydantic.fields import FieldInfo as _FieldInfo + +_logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from .http_sse._models import ServerSentEvent + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] + _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] + + def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value + return _datetime_adapter.validate_python(value) + + def parse_date(value: Any) -> dt.date: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value.date() + if isinstance(value, dt.date): + return value + return _date_adapter.validate_python(value) + + # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. + from typing import get_args as get_args # type: ignore[assignment] + from typing import get_origin as get_origin # type: ignore[assignment] + + def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return typing_extensions.get_origin(tp) is typing_extensions.Literal + + def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] + + # Inline encoders_by_type to avoid importing from pydantic.v1.json + import re as _re + from collections import deque as _deque + from decimal import Decimal as _Decimal + from enum import Enum as _Enum + from ipaddress import ( + IPv4Address as _IPv4Address, + ) + from ipaddress import ( + IPv4Interface as _IPv4Interface, + ) + from ipaddress import ( + IPv4Network as _IPv4Network, + ) + from ipaddress import ( + IPv6Address as _IPv6Address, + ) + from ipaddress import ( + IPv6Interface as _IPv6Interface, + ) + from ipaddress import ( + IPv6Network as _IPv6Network, + ) + from pathlib import Path as _Path + from types import GeneratorType as _GeneratorType + from uuid import UUID as _UUID + + from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] + + def _decimal_encoder(dec_value: Any) -> Any: + if dec_value.as_tuple().exponent >= 0: + return int(dec_value) + return float(dec_value) + + encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] + bytes: lambda o: o.decode(), + dt.date: lambda o: o.isoformat(), + dt.datetime: lambda o: o.isoformat(), + dt.time: lambda o: o.isoformat(), + dt.timedelta: lambda td: td.total_seconds(), + _Decimal: _decimal_encoder, + _Enum: lambda o: o.value, + frozenset: list, + _deque: list, + _GeneratorType: list, + _IPv4Address: str, + _IPv4Interface: str, + _IPv4Network: str, + _IPv6Address: str, + _IPv6Interface: str, + _IPv6Network: str, + _Path: str, + _re.Pattern: lambda o: o.pattern, + set: list, + _UUID: str, + } +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] + from pydantic.typing import get_args as get_args # type: ignore[no-redef] + from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] + from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] + from pydantic.typing import is_union as is_union # type: ignore[no-redef] + +from .datetime_utils import serialize_datetime +from typing_extensions import TypeAlias + +T = TypeVar("T") +Model = TypeVar("Model", bound=pydantic.BaseModel) + + +def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: + """ + Extract the discriminator field name and union variants from a discriminated union type. + Supports Annotated[Union[...], Field(discriminator=...)] patterns. + Returns (discriminator, variants) or (None, None) if not a discriminated union. + """ + origin = typing_extensions.get_origin(type_) + + if origin is typing_extensions.Annotated: + args = typing_extensions.get_args(type_) + if len(args) >= 2: + inner_type = args[0] + # Check annotations for discriminator + discriminator = None + for annotation in args[1:]: + if hasattr(annotation, "discriminator"): + discriminator = getattr(annotation, "discriminator", None) + break + + if discriminator: + inner_origin = typing_extensions.get_origin(inner_type) + if inner_origin is Union: + variants = list(typing_extensions.get_args(inner_type)) + return discriminator, variants + return None, None + + +def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: + """Get the type annotation of a field from a Pydantic model.""" + if IS_PYDANTIC_V2: + fields = getattr(model, "model_fields", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.annotation) + else: + fields = getattr(model, "__fields__", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.outer_type_) + return None + + +def _find_variant_by_discriminator( + variants: List[Type[Any]], + discriminator: str, + discriminator_value: Any, +) -> Optional[Type[Any]]: + """Find the union variant that matches the discriminator value.""" + for variant in variants: + if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): + continue + + disc_annotation = _get_field_annotation(variant, discriminator) + if disc_annotation and is_literal_type(disc_annotation): + literal_args = get_args(disc_annotation) + if literal_args and literal_args[0] == discriminator_value: + return variant + return None + + +def _is_string_type(type_: Type[Any]) -> bool: + """Check if a type is str or Optional[str].""" + if type_ is str: + return True + + origin = typing_extensions.get_origin(type_) + if origin is Union: + args = typing_extensions.get_args(type_) + # Optional[str] = Union[str, None] + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1 and non_none_args[0] is str: + return True + + return False + + +def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: + """ + Parse a ServerSentEvent into the appropriate type. + + Handles two scenarios based on where the discriminator field is located: + + 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. + The union describes the data content, not the SSE envelope. + -> Returns: json.loads(data) parsed into the type + + 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. + The union describes the full SSE event structure. + -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string + + Args: + sse: The ServerSentEvent object to parse + type_: The target discriminated union type + + Returns: + The parsed object of type T + + Note: + This function is only available in SDK contexts where http_sse module exists. + """ + sse_event = asdict(sse) + discriminator, variants = _get_discriminator_and_variants(type_) + + if discriminator is None or variants is None: + # Not a discriminated union - parse the data field as JSON + data_value = sse_event.get("data") + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + data_value = sse_event.get("data") + + # Check if discriminator is at the top level (event-level discrimination) + if discriminator in sse_event: + # Case 2: Event-level discrimination + # Find the matching variant to check if 'data' field needs JSON parsing + disc_value = sse_event.get(discriminator) + matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) + + if matching_variant is not None: + # Check what type the variant expects for 'data' + data_type = _get_field_annotation(matching_variant, "data") + if data_type is not None and not _is_string_type(data_type): + # Variant expects non-string data - parse JSON + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + new_object = dict(sse_event) + new_object["data"] = parsed_data + return parse_obj_as(type_, new_object) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + # Either no matching variant, data is string type, or JSON parse failed + return parse_obj_as(type_, sse_event) + + else: + # Case 1: Data-level discrimination + # The discriminator is inside the data payload - extract and parse data only + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + +def parse_obj_as(type_: Type[T], object_: Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] + return adapter.validate_python(object_) + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] + validate_by_name=True, + validate_by_alias=True, + # Allow fields beginning with `model_` to be used in the model + protected_namespaces=(), + ) + + @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] + def serialize_model(self) -> Any: # type: ignore[name-defined] + def _serialize_recursive(obj: Any) -> Any: + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + elif isinstance(obj, dict): + return {k: _serialize_recursive(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [_serialize_recursive(item) for item in obj] + return obj + + serialized = self.model_dump() # type: ignore[attr-defined] + return _serialize_recursive(serialized) + + else: + + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + # Allow fields beginning with `model_` to be used in the model + protected_namespaces = () + + def json(self, **kwargs: Any) -> str: + kwargs_with_defaults = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: Any) -> Dict[str, Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] + ) + + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + return super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + +def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: + converted_list: List[Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[name-defined, type-arg] + pass + + UniversalRootModel: TypeAlias = V2RootModel +else: + UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] + + +def encode_by_type(o: Any) -> Any: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: Type["Model"], **localns: Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = Callable[..., Any] + + +def universal_root_validator( + pre: bool = False, +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) + + return decorator + + +PydanticField = Union[ModelField, _FieldInfo] + + +def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] + return cast(Mapping[str, PydanticField], model.__fields__) + + +def _get_field_default(field: PydanticField) -> Any: + try: + value = field.get_default() # type: ignore[union-attr] + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/seed/pydantic/allof-inline/src/seed/api/core/serialization.py b/seed/pydantic/allof-inline/src/seed/api/core/serialization.py new file mode 100644 index 000000000000..c36e865cc729 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/core/serialization.py @@ -0,0 +1,276 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import pydantic +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + try: + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + except NameError: + # The TypedDict contains a circular reference, so + # we use the __annotations__ attribute directly. + annotations = getattr(expected_type, "__annotations__", {}) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/seed/pydantic/allof-inline/src/seed/api/describable.py b/seed/pydantic/allof-inline/src/seed/api/describable.py new file mode 100644 index 000000000000..b22612d75808 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/describable.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Describable(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Describable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/detailed_org.py b/seed/pydantic/allof-inline/src/seed/api/detailed_org.py new file mode 100644 index 000000000000..0779d792bb37 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/detailed_org.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .detailed_org_metadata import DetailedOrgMetadata + + +class DetailedOrg(UniversalBaseModel): + metadata: typing.Optional[DetailedOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py b/seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py new file mode 100644 index 000000000000..fe3f63b969a5 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DetailedOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from DetailedOrg. + """ + + domain: typing.Optional[str] = pydantic.Field(default=None) + """ + Custom domain name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/identifiable.py b/seed/pydantic/allof-inline/src/seed/api/identifiable.py new file mode 100644 index 000000000000..825da5f0d495 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/identifiable.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Identifiable(UniversalBaseModel): + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Identifiable. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/organization.py b/seed/pydantic/allof-inline/src/seed/api/organization.py new file mode 100644 index 000000000000..a446ab4bb313 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/organization.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .organization_metadata import OrganizationMetadata + + +class Organization(UniversalBaseModel): + id: str + metadata: typing.Optional[OrganizationMetadata] = None + name: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/organization_metadata.py b/seed/pydantic/allof-inline/src/seed/api/organization_metadata.py new file mode 100644 index 000000000000..77f35c177d8b --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/organization_metadata.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class OrganizationMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from DetailedOrg. + """ + + domain: typing.Optional[str] = pydantic.Field(default=None) + """ + Custom domain name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/paginated_result.py b/seed/pydantic/allof-inline/src/seed/api/paginated_result.py new file mode 100644 index 000000000000..089091066549 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/paginated_result.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors + + +class PaginatedResult(UniversalBaseModel): + paging: PagingCursors + results: typing.List[typing.Any] = pydantic.Field() + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/paging_cursors.py b/seed/pydantic/allof-inline/src/seed/api/paging_cursors.py new file mode 100644 index 000000000000..9132a3ed2eaf --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/paging_cursors.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PagingCursors(UniversalBaseModel): + next: str = pydantic.Field() + """ + Cursor for the next page of results. + """ + + previous: typing.Optional[str] = pydantic.Field(default=None) + """ + Cursor for the previous page of results. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/py.typed b/seed/pydantic/allof-inline/src/seed/api/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py b/seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py new file mode 100644 index 000000000000..1d22ed9cabf8 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_response.py b/seed/pydantic/allof-inline/src/seed/api/rule_response.py new file mode 100644 index 000000000000..f408b0ed40eb --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/rule_response.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .core.serialization import FieldMetadata +from .rule_execution_context import RuleExecutionContext +from .rule_response_status import RuleResponseStatus + + +class RuleResponse(UniversalBaseModel): + created_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="createdBy"), + pydantic.Field(alias="createdBy", description="The user who created this resource."), + ] = None + created_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="createdDateTime"), + pydantic.Field(alias="createdDateTime", description="When this resource was created."), + ] = None + modified_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="modifiedBy"), + pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), + ] = None + modified_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="modifiedDateTime"), + pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), + ] = None + id: str + name: str + status: RuleResponseStatus + execution_context: typing_extensions.Annotated[ + typing.Optional[RuleExecutionContext], + FieldMetadata(alias="executionContext"), + pydantic.Field(alias="executionContext"), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_response_status.py b/seed/pydantic/allof-inline/src/seed/api/rule_response_status.py new file mode 100644 index 000000000000..4cbd106638cb --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/rule_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_type.py b/seed/pydantic/allof-inline/src/seed/api/rule_type.py new file mode 100644 index 000000000000..7f5646c5bc8a --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/rule_type.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RuleType(UniversalBaseModel): + id: str + name: str + description: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py b/seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py new file mode 100644 index 000000000000..3d4641d39dff --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .rule_type import RuleType + + +class RuleTypeSearchResponse(UniversalBaseModel): + paging: PagingCursors + results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/user.py b/seed/pydantic/allof-inline/src/seed/api/user.py new file mode 100644 index 000000000000..b737a327a2ce --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/user.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class User(UniversalBaseModel): + id: str + email: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/user_search_response.py b/seed/pydantic/allof-inline/src/seed/api/user_search_response.py new file mode 100644 index 000000000000..d71aa4274c66 --- /dev/null +++ b/seed/pydantic/allof-inline/src/seed/api/user_search_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .user import User + + +class UserSearchResponse(UniversalBaseModel): + paging: PagingCursors + results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/tests/custom/test_client.py b/seed/pydantic/allof-inline/tests/custom/test_client.py new file mode 100644 index 000000000000..ab04ce6393ef --- /dev/null +++ b/seed/pydantic/allof-inline/tests/custom/test_client.py @@ -0,0 +1,7 @@ +import pytest + + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True diff --git a/seed/pydantic/allof/.github/workflows/ci.yml b/seed/pydantic/allof/.github/workflows/ci.yml new file mode 100644 index 000000000000..fd1df043d08d --- /dev/null +++ b/seed/pydantic/allof/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: ci +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest -rP -n auto . + + - name: Install aiohttp extra + run: poetry install --extras aiohttp + + - name: Test (aiohttp) + run: poetry run pytest -rP -n auto -m aiohttp . + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" + env: + PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/pydantic/allof/.gitignore b/seed/pydantic/allof/.gitignore new file mode 100644 index 000000000000..d2e4ca808d21 --- /dev/null +++ b/seed/pydantic/allof/.gitignore @@ -0,0 +1,5 @@ +.mypy_cache/ +.ruff_cache/ +__pycache__/ +dist/ +poetry.toml diff --git a/seed/pydantic/allof/README.md b/seed/pydantic/allof/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/pydantic/allof/poetry.lock b/seed/pydantic/allof/poetry.lock new file mode 100644 index 000000000000..e1efb51734e4 --- /dev/null +++ b/seed/pydantic/allof/poetry.lock @@ -0,0 +1,546 @@ +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.1.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pydantic" +version = "2.12.5" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, +] + +[package.dependencies] +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "ruff" +version = "0.11.5" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, + {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, + {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, + {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, + {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, + {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, + {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tomli" +version = "2.4.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20260408" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.10" +files = [ + {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, + {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "283a69ea9a1eb309027a93a4e056bedb5cb3c6018ca2eec381af57784ad1eb58" diff --git a/seed/pydantic/allof/pyproject.toml b/seed/pydantic/allof/pyproject.toml new file mode 100644 index 000000000000..fe46224af192 --- /dev/null +++ b/seed/pydantic/allof/pyproject.toml @@ -0,0 +1,93 @@ +[project] +name = "fern_allof" +dynamic = ["version"] + +[tool.poetry] +name = "fern_allof" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [ + "fern", + "test" +] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed/api", from = "src"} +] + +[tool.poetry.urls] +Documentation = 'https://buildwithfern.com/learn' +Homepage = 'https://buildwithfern.com/' +Repository = 'https://github.com/allof/fern' + +[tool.poetry.dependencies] +python = "^3.10" +pydantic = ">= 1.9.2" +pydantic-core = ">=2.18.2,<2.44.0" + +[tool.poetry.group.dev.dependencies] +mypy = "==1.13.0" +pytest = "^8.2.0" +pytest-asyncio = "^1.0.0" +pytest-xdist = "^3.6.1" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" +ruff = "==0.11.5" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" +markers = [ + "aiohttp: tests that require httpx_aiohttp to be installed", +] + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort +] +ignore = [ + "E402", # Module level import not at top of file + "E501", # Line too long + "E711", # Comparison to `None` should be `cond is not None` + "E712", # Avoid equality comparisons to `True`; use `if ...:` checks + "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks + "E722", # Do not use bare `except` + "E731", # Do not assign a `lambda` expression, use a `def` + "F821", # Undefined name + "F841" # Local variable ... is assigned to but never used +] + +[tool.ruff.lint.isort] +section-order = ["future", "standard-library", "third-party", "first-party"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/pydantic/allof/requirements.txt b/seed/pydantic/allof/requirements.txt new file mode 100644 index 000000000000..e090fd9ba093 --- /dev/null +++ b/seed/pydantic/allof/requirements.txt @@ -0,0 +1,2 @@ +pydantic>= 1.9.2 +pydantic-core>=2.18.2,<2.44.0 diff --git a/seed/pydantic/allof/snippet.json b/seed/pydantic/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/pydantic/allof/src/seed/api/__init__.py b/seed/pydantic/allof/src/seed/api/__init__.py new file mode 100644 index 000000000000..f6ca40254bd4 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/__init__.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +from .audit_info import AuditInfo +from .base_org import BaseOrg +from .base_org_metadata import BaseOrgMetadata +from .combined_entity import CombinedEntity +from .combined_entity_status import CombinedEntityStatus +from .describable import Describable +from .detailed_org import DetailedOrg +from .detailed_org_metadata import DetailedOrgMetadata +from .identifiable import Identifiable +from .organization import Organization +from .paginated_result import PaginatedResult +from .paging_cursors import PagingCursors +from .rule_execution_context import RuleExecutionContext +from .rule_response import RuleResponse +from .rule_response_status import RuleResponseStatus +from .rule_type import RuleType +from .rule_type_search_response import RuleTypeSearchResponse +from .user import User +from .user_search_response import UserSearchResponse + +__all__ = [ + "AuditInfo", + "BaseOrg", + "BaseOrgMetadata", + "CombinedEntity", + "CombinedEntityStatus", + "Describable", + "DetailedOrg", + "DetailedOrgMetadata", + "Identifiable", + "Organization", + "PaginatedResult", + "PagingCursors", + "RuleExecutionContext", + "RuleResponse", + "RuleResponseStatus", + "RuleType", + "RuleTypeSearchResponse", + "User", + "UserSearchResponse", +] diff --git a/seed/pydantic/allof/src/seed/api/audit_info.py b/seed/pydantic/allof/src/seed/api/audit_info.py new file mode 100644 index 000000000000..5b00ec223d32 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/audit_info.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .core.serialization import FieldMetadata + + +class AuditInfo(UniversalBaseModel): + """ + Common audit metadata. + """ + + created_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="createdBy"), + pydantic.Field(alias="createdBy", description="The user who created this resource."), + ] = None + created_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="createdDateTime"), + pydantic.Field(alias="createdDateTime", description="When this resource was created."), + ] = None + modified_by: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="modifiedBy"), + pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), + ] = None + modified_date_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], + FieldMetadata(alias="modifiedDateTime"), + pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/base_org.py b/seed/pydantic/allof/src/seed/api/base_org.py new file mode 100644 index 000000000000..512f3ed40d4e --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/base_org.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_org_metadata import BaseOrgMetadata +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class BaseOrg(UniversalBaseModel): + id: str + metadata: typing.Optional[BaseOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/base_org_metadata.py b/seed/pydantic/allof/src/seed/api/base_org_metadata.py new file mode 100644 index 000000000000..8c58e204c200 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/base_org_metadata.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class BaseOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from BaseOrg. + """ + + tier: typing.Optional[str] = pydantic.Field(default=None) + """ + Subscription tier. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/combined_entity.py b/seed/pydantic/allof/src/seed/api/combined_entity.py new file mode 100644 index 000000000000..c158e3278971 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/combined_entity.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .combined_entity_status import CombinedEntityStatus +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CombinedEntity(UniversalBaseModel): + status: CombinedEntityStatus + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Identifiable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/combined_entity_status.py b/seed/pydantic/allof/src/seed/api/combined_entity_status.py new file mode 100644 index 000000000000..42ac60430cd7 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/combined_entity_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/pydantic/allof/src/seed/api/core/__init__.py b/seed/pydantic/allof/src/seed/api/core/__init__.py new file mode 100644 index 000000000000..f9bdc1729e86 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/core/__init__.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .serialization import FieldMetadata + +__all__ = [ + "FieldMetadata", + "IS_PYDANTIC_V2", + "Rfc2822DateTime", + "UniversalBaseModel", + "UniversalRootModel", + "parse_obj_as", + "parse_rfc2822_datetime", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/pydantic/allof/src/seed/api/core/datetime_utils.py b/seed/pydantic/allof/src/seed/api/core/datetime_utils.py new file mode 100644 index 000000000000..a12b2ad03c53 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/core/datetime_utils.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +from email.utils import parsedate_to_datetime +from typing import Any + +import pydantic + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + + +def parse_rfc2822_datetime(v: Any) -> dt.datetime: + """ + Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") + into a datetime object. If the value is already a datetime, return it as-is. + Falls back to ISO 8601 parsing if RFC 2822 parsing fails. + """ + if isinstance(v, dt.datetime): + return v + if isinstance(v, str): + try: + return parsedate_to_datetime(v) + except Exception: + pass + # Fallback to ISO 8601 parsing + return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) + raise ValueError(f"Expected str or datetime, got {type(v)}") + + +class Rfc2822DateTime(dt.datetime): + """A datetime subclass that parses RFC 2822 date strings. + + On Pydantic V1, uses __get_validators__ for pre-validation. + On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. + """ + + @classmethod + def __get_validators__(cls): # type: ignore[no-untyped-def] + yield parse_rfc2822_datetime + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] + from pydantic_core import core_schema + + return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/pydantic/allof/src/seed/api/core/enum.py b/seed/pydantic/allof/src/seed/api/core/enum.py new file mode 100644 index 000000000000..a3d17a67b128 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/core/enum.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +Provides a StrEnum base class that works across Python versions. + +For Python >= 3.11, this re-exports the standard library enum.StrEnum. +For older Python versions, this defines a compatible StrEnum using the +(str, Enum) mixin pattern so that generated SDKs can use a single base +class in all supported Python versions. +""" + +import enum +import sys + +if sys.version_info >= (3, 11): + from enum import StrEnum +else: + + class StrEnum(str, enum.Enum): + pass diff --git a/seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py b/seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py new file mode 100644 index 000000000000..ed95da8bf8ce --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py @@ -0,0 +1,515 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import inspect +import json +import logging +from collections import defaultdict +from dataclasses import asdict +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +import pydantic +import typing_extensions +from pydantic.fields import FieldInfo as _FieldInfo + +_logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from .http_sse._models import ServerSentEvent + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] + _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] + + def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value + return _datetime_adapter.validate_python(value) + + def parse_date(value: Any) -> dt.date: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value.date() + if isinstance(value, dt.date): + return value + return _date_adapter.validate_python(value) + + # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. + from typing import get_args as get_args # type: ignore[assignment] + from typing import get_origin as get_origin # type: ignore[assignment] + + def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return typing_extensions.get_origin(tp) is typing_extensions.Literal + + def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] + + # Inline encoders_by_type to avoid importing from pydantic.v1.json + import re as _re + from collections import deque as _deque + from decimal import Decimal as _Decimal + from enum import Enum as _Enum + from ipaddress import ( + IPv4Address as _IPv4Address, + ) + from ipaddress import ( + IPv4Interface as _IPv4Interface, + ) + from ipaddress import ( + IPv4Network as _IPv4Network, + ) + from ipaddress import ( + IPv6Address as _IPv6Address, + ) + from ipaddress import ( + IPv6Interface as _IPv6Interface, + ) + from ipaddress import ( + IPv6Network as _IPv6Network, + ) + from pathlib import Path as _Path + from types import GeneratorType as _GeneratorType + from uuid import UUID as _UUID + + from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] + + def _decimal_encoder(dec_value: Any) -> Any: + if dec_value.as_tuple().exponent >= 0: + return int(dec_value) + return float(dec_value) + + encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] + bytes: lambda o: o.decode(), + dt.date: lambda o: o.isoformat(), + dt.datetime: lambda o: o.isoformat(), + dt.time: lambda o: o.isoformat(), + dt.timedelta: lambda td: td.total_seconds(), + _Decimal: _decimal_encoder, + _Enum: lambda o: o.value, + frozenset: list, + _deque: list, + _GeneratorType: list, + _IPv4Address: str, + _IPv4Interface: str, + _IPv4Network: str, + _IPv6Address: str, + _IPv6Interface: str, + _IPv6Network: str, + _Path: str, + _re.Pattern: lambda o: o.pattern, + set: list, + _UUID: str, + } +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] + from pydantic.typing import get_args as get_args # type: ignore[no-redef] + from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] + from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] + from pydantic.typing import is_union as is_union # type: ignore[no-redef] + +from .datetime_utils import serialize_datetime +from typing_extensions import TypeAlias + +T = TypeVar("T") +Model = TypeVar("Model", bound=pydantic.BaseModel) + + +def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: + """ + Extract the discriminator field name and union variants from a discriminated union type. + Supports Annotated[Union[...], Field(discriminator=...)] patterns. + Returns (discriminator, variants) or (None, None) if not a discriminated union. + """ + origin = typing_extensions.get_origin(type_) + + if origin is typing_extensions.Annotated: + args = typing_extensions.get_args(type_) + if len(args) >= 2: + inner_type = args[0] + # Check annotations for discriminator + discriminator = None + for annotation in args[1:]: + if hasattr(annotation, "discriminator"): + discriminator = getattr(annotation, "discriminator", None) + break + + if discriminator: + inner_origin = typing_extensions.get_origin(inner_type) + if inner_origin is Union: + variants = list(typing_extensions.get_args(inner_type)) + return discriminator, variants + return None, None + + +def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: + """Get the type annotation of a field from a Pydantic model.""" + if IS_PYDANTIC_V2: + fields = getattr(model, "model_fields", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.annotation) + else: + fields = getattr(model, "__fields__", {}) + field_info = fields.get(field_name) + if field_info: + return cast(Optional[Type[Any]], field_info.outer_type_) + return None + + +def _find_variant_by_discriminator( + variants: List[Type[Any]], + discriminator: str, + discriminator_value: Any, +) -> Optional[Type[Any]]: + """Find the union variant that matches the discriminator value.""" + for variant in variants: + if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): + continue + + disc_annotation = _get_field_annotation(variant, discriminator) + if disc_annotation and is_literal_type(disc_annotation): + literal_args = get_args(disc_annotation) + if literal_args and literal_args[0] == discriminator_value: + return variant + return None + + +def _is_string_type(type_: Type[Any]) -> bool: + """Check if a type is str or Optional[str].""" + if type_ is str: + return True + + origin = typing_extensions.get_origin(type_) + if origin is Union: + args = typing_extensions.get_args(type_) + # Optional[str] = Union[str, None] + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1 and non_none_args[0] is str: + return True + + return False + + +def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: + """ + Parse a ServerSentEvent into the appropriate type. + + Handles two scenarios based on where the discriminator field is located: + + 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. + The union describes the data content, not the SSE envelope. + -> Returns: json.loads(data) parsed into the type + + 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. + The union describes the full SSE event structure. + -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string + + Args: + sse: The ServerSentEvent object to parse + type_: The target discriminated union type + + Returns: + The parsed object of type T + + Note: + This function is only available in SDK contexts where http_sse module exists. + """ + sse_event = asdict(sse) + discriminator, variants = _get_discriminator_and_variants(type_) + + if discriminator is None or variants is None: + # Not a discriminated union - parse the data field as JSON + data_value = sse_event.get("data") + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + data_value = sse_event.get("data") + + # Check if discriminator is at the top level (event-level discrimination) + if discriminator in sse_event: + # Case 2: Event-level discrimination + # Find the matching variant to check if 'data' field needs JSON parsing + disc_value = sse_event.get(discriminator) + matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) + + if matching_variant is not None: + # Check what type the variant expects for 'data' + data_type = _get_field_annotation(matching_variant, "data") + if data_type is not None and not _is_string_type(data_type): + # Variant expects non-string data - parse JSON + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + new_object = dict(sse_event) + new_object["data"] = parsed_data + return parse_obj_as(type_, new_object) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + # Either no matching variant, data is string type, or JSON parse failed + return parse_obj_as(type_, sse_event) + + else: + # Case 1: Data-level discrimination + # The discriminator is inside the data payload - extract and parse data only + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + +def parse_obj_as(type_: Type[T], object_: Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] + return adapter.validate_python(object_) + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] + validate_by_name=True, + validate_by_alias=True, + # Allow fields beginning with `model_` to be used in the model + protected_namespaces=(), + ) + + @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] + def serialize_model(self) -> Any: # type: ignore[name-defined] + def _serialize_recursive(obj: Any) -> Any: + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + elif isinstance(obj, dict): + return {k: _serialize_recursive(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [_serialize_recursive(item) for item in obj] + return obj + + serialized = self.model_dump() # type: ignore[attr-defined] + return _serialize_recursive(serialized) + + else: + + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + # Allow fields beginning with `model_` to be used in the model + protected_namespaces = () + + def json(self, **kwargs: Any) -> str: + kwargs_with_defaults = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: Any) -> Dict[str, Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] + ) + + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + return super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + +def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: + converted_list: List[Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[name-defined, type-arg] + pass + + UniversalRootModel: TypeAlias = V2RootModel +else: + UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] + + +def encode_by_type(o: Any) -> Any: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: Type["Model"], **localns: Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = Callable[..., Any] + + +def universal_root_validator( + pre: bool = False, +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) + + return decorator + + +PydanticField = Union[ModelField, _FieldInfo] + + +def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] + return cast(Mapping[str, PydanticField], model.__fields__) + + +def _get_field_default(field: PydanticField) -> Any: + try: + value = field.get_default() # type: ignore[union-attr] + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/seed/pydantic/allof/src/seed/api/core/serialization.py b/seed/pydantic/allof/src/seed/api/core/serialization.py new file mode 100644 index 000000000000..c36e865cc729 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/core/serialization.py @@ -0,0 +1,276 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import pydantic +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + try: + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + except NameError: + # The TypedDict contains a circular reference, so + # we use the __annotations__ attribute directly. + annotations = getattr(expected_type, "__annotations__", {}) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/seed/pydantic/allof/src/seed/api/describable.py b/seed/pydantic/allof/src/seed/api/describable.py new file mode 100644 index 000000000000..b22612d75808 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/describable.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Describable(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Describable. + """ + + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + A short summary. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/detailed_org.py b/seed/pydantic/allof/src/seed/api/detailed_org.py new file mode 100644 index 000000000000..0779d792bb37 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/detailed_org.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .detailed_org_metadata import DetailedOrgMetadata + + +class DetailedOrg(UniversalBaseModel): + metadata: typing.Optional[DetailedOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/detailed_org_metadata.py b/seed/pydantic/allof/src/seed/api/detailed_org_metadata.py new file mode 100644 index 000000000000..fe3f63b969a5 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/detailed_org_metadata.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DetailedOrgMetadata(UniversalBaseModel): + region: str = pydantic.Field() + """ + Deployment region from DetailedOrg. + """ + + domain: typing.Optional[str] = pydantic.Field(default=None) + """ + Custom domain name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/identifiable.py b/seed/pydantic/allof/src/seed/api/identifiable.py new file mode 100644 index 000000000000..825da5f0d495 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/identifiable.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Identifiable(UniversalBaseModel): + id: str = pydantic.Field() + """ + Unique identifier. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Display name from Identifiable. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/organization.py b/seed/pydantic/allof/src/seed/api/organization.py new file mode 100644 index 000000000000..c997aafe249b --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/organization.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_org_metadata import BaseOrgMetadata +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Organization(UniversalBaseModel): + name: str + id: str + metadata: typing.Optional[BaseOrgMetadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/paginated_result.py b/seed/pydantic/allof/src/seed/api/paginated_result.py new file mode 100644 index 000000000000..089091066549 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/paginated_result.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors + + +class PaginatedResult(UniversalBaseModel): + paging: PagingCursors + results: typing.List[typing.Any] = pydantic.Field() + """ + Current page of results from the requested resource. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/paging_cursors.py b/seed/pydantic/allof/src/seed/api/paging_cursors.py new file mode 100644 index 000000000000..9132a3ed2eaf --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/paging_cursors.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PagingCursors(UniversalBaseModel): + next: str = pydantic.Field() + """ + Cursor for the next page of results. + """ + + previous: typing.Optional[str] = pydantic.Field(default=None) + """ + Cursor for the previous page of results. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/py.typed b/seed/pydantic/allof/src/seed/api/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/pydantic/allof/src/seed/api/rule_execution_context.py b/seed/pydantic/allof/src/seed/api/rule_execution_context.py new file mode 100644 index 000000000000..1d22ed9cabf8 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/rule_execution_context.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/pydantic/allof/src/seed/api/rule_response.py b/seed/pydantic/allof/src/seed/api/rule_response.py new file mode 100644 index 000000000000..fb3fbc248532 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/rule_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from .audit_info import AuditInfo +from .core.pydantic_utilities import IS_PYDANTIC_V2 +from .core.serialization import FieldMetadata +from .rule_execution_context import RuleExecutionContext +from .rule_response_status import RuleResponseStatus + + +class RuleResponse(AuditInfo): + id: str + name: str + status: RuleResponseStatus + execution_context: typing_extensions.Annotated[ + typing.Optional[RuleExecutionContext], + FieldMetadata(alias="executionContext"), + pydantic.Field(alias="executionContext"), + ] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/rule_response_status.py b/seed/pydantic/allof/src/seed/api/rule_response_status.py new file mode 100644 index 000000000000..4cbd106638cb --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/rule_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/pydantic/allof/src/seed/api/rule_type.py b/seed/pydantic/allof/src/seed/api/rule_type.py new file mode 100644 index 000000000000..7f5646c5bc8a --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/rule_type.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RuleType(UniversalBaseModel): + id: str + name: str + description: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/rule_type_search_response.py b/seed/pydantic/allof/src/seed/api/rule_type_search_response.py new file mode 100644 index 000000000000..375bcd2c1d28 --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/rule_type_search_response.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .rule_type import RuleType + + +class RuleTypeSearchResponse(UniversalBaseModel): + results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + paging: PagingCursors + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/user.py b/seed/pydantic/allof/src/seed/api/user.py new file mode 100644 index 000000000000..b737a327a2ce --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/user.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class User(UniversalBaseModel): + id: str + email: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/user_search_response.py b/seed/pydantic/allof/src/seed/api/user_search_response.py new file mode 100644 index 000000000000..f2433ca6d99f --- /dev/null +++ b/seed/pydantic/allof/src/seed/api/user_search_response.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .paging_cursors import PagingCursors +from .user import User + + +class UserSearchResponse(UniversalBaseModel): + results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) + """ + Current page of results from the requested resource. + """ + + paging: PagingCursors + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/tests/custom/test_client.py b/seed/pydantic/allof/tests/custom/test_client.py new file mode 100644 index 000000000000..ab04ce6393ef --- /dev/null +++ b/seed/pydantic/allof/tests/custom/test_client.py @@ -0,0 +1,7 @@ +import pytest + + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True diff --git a/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json b/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json index e489d22a79b5..d3303ad03b0b 100644 --- a/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json +++ b/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "generatorConfig": { "enable_wire_tests": true }, diff --git a/seed/python-sdk/allof-inline/no-custom-config/poetry.lock b/seed/python-sdk/allof-inline/no-custom-config/poetry.lock index 75cfea1d0649..613c65b508b1 100644 --- a/seed/python-sdk/allof-inline/no-custom-config/poetry.lock +++ b/seed/python-sdk/allof-inline/no-custom-config/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -168,6 +168,17 @@ files = [ frozenlist = ">=1.1.0" typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + [[package]] name = "anyio" version = "4.13.0" @@ -1027,177 +1038,153 @@ files = [ [[package]] name = "pydantic" -version = "1.10.26" -description = "Data validation and settings management using python type hints" +version = "2.12.5" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546"}, - {file = "pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572"}, - {file = "pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464"}, - {file = "pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53"}, - {file = "pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945"}, - {file = "pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637"}, - {file = "pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77"}, - {file = "pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f"}, - {file = "pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5"}, - {file = "pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df"}, - {file = "pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f"}, - {file = "pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f"}, - {file = "pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb"}, - {file = "pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b"}, - {file = "pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c"}, - {file = "pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c"}, - {file = "pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba"}, - {file = "pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62"}, - {file = "pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847"}, - {file = "pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314"}, - {file = "pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba"}, - {file = "pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37"}, - {file = "pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a"}, - {file = "pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962"}, - {file = "pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c"}, - {file = "pydantic-1.10.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc5c91a3b3106caf07ac6735ec6efad8ba37b860b9eb569923386debe65039ad"}, - {file = "pydantic-1.10.26-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dde599e0388e04778480d57f49355c9cc7916de818bf674de5d5429f2feebfb6"}, - {file = "pydantic-1.10.26-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be08b5cfe88e58198722861c7aab737c978423c3a27300911767931e5311d0d"}, - {file = "pydantic-1.10.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0141f4bafe5eda539d98c9755128a9ea933654c6ca4306b5059fc87a01a38573"}, - {file = "pydantic-1.10.26-cp38-cp38-win_amd64.whl", hash = "sha256:eb664305ffca8a9766a8629303bb596607d77eae35bb5f32ff9245984881b638"}, - {file = "pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d"}, - {file = "pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15"}, - {file = "pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90"}, - {file = "pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba"}, - {file = "pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96"}, - {file = "pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917"}, - {file = "pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.42.0" +version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.42.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:0ae7d50a47ada2a04f7296be9a7a2bf447118a25855f41fc52c8fc4bfb70c105"}, - {file = "pydantic_core-2.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c9d04d4bd8de1dcd5c8845faf6c11e36cda34c2efffa29d70ad83cc6f6a6c9a8"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e459e89453bb1bc69853272260afb5328ae404f854ddec485f5427fbace8d7e"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:def66968fbe20274093fd4fc85d82b2ec42dbe20d9e51d27bbf3b5c7428c7a10"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:272fab515dc7da0f456c49747b87b4e8721a33ab352a54760cc8fd1a4fd5348a"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa82dec59f36106738ae981878e0001074e2b3a949f21a5b3bea20485b9c6db4"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a70fe4db00ab03a9f976d28471c8e696ebd3b8455ccfa5e36e5d1a2ff301a7"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b4c0f656b4fa218413a485c550ac3e4ddf2f343a9c46b6137394bd77c4128445"}, - {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a4396ffc8b42499d14662f958b3f00656b62a67bde7f156580fd618827bebf5a"}, - {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:36067825f365a5c3065f17d08421a72b036ff4588c450afe54d5750b80cc220d"}, - {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eec64367de940786c0b686d47bd952692018dd7cd895027aa82023186e469b7d"}, - {file = "pydantic_core-2.42.0-cp310-cp310-win32.whl", hash = "sha256:ff9f0737f487277721682d8518434557cfcef141ba55b89381c92700594a8b65"}, - {file = "pydantic_core-2.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:77f0a8ab035d3bc319b759d8215f51846e9ea582dacbabb2777e5e3e135a048e"}, - {file = "pydantic_core-2.42.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1159b9ee73511ae7c5631b108d80373577bc14f22d18d85bb2aa1fa1051dabc"}, - {file = "pydantic_core-2.42.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff8e49b22225445d3e078aaa9bead90c37c852aee8f8a169ba15fdaaa13d1ecb"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe777d9a1a932c6b3ef32b201985324d06d9c74028adef1e1c7ea226fca2ba34"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e92592c1040ed17968d603e05b72acec321662ef9bf88fef443ceae4d1a130c2"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:557a6eb6dc4db8a3f071929710feb29c6b5d7559218ab547a4e60577fb404f2f"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4035f81e7d1a5e065543061376ca52ccb0accaf970911ba0a9ec9d22062806ca"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63a4e073f8def1c7fd100a355b3a96e1bbaf0446b6a8530ae58f1afaa0478a46"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd8469c8d9f6c81befd10c72a0268079e929ba494cd27fa63e868964b0e04fb6"}, - {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bdebfd610a02bdb82f8e36dc7d4683e03e420624a2eda63e1205730970021308"}, - {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9577eb5221abd4e5adf8a232a65f74c509b82b57b7b96b3667dac22f03ff9e94"}, - {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6d36841b61100128c2374341a7c2c0ab347ef4b63aa4b6837b4431465d4d4fd"}, - {file = "pydantic_core-2.42.0-cp311-cp311-win32.whl", hash = "sha256:1d9d45333a28b0b8fb8ecedf67d280dc3318899988093e4d3a81618396270697"}, - {file = "pydantic_core-2.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:4631b4d1a3fe460aadd3822af032bb6c2e7ad77071fbf71c4e95ef9083c7c1a8"}, - {file = "pydantic_core-2.42.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d46bfc6175a4b4b80b9f98f76133fbf68d5a02d7469b3090ca922d40f23d32d"}, - {file = "pydantic_core-2.42.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a11b9115364681779bcc39c6b9cdc20d48a9812a4bf3ed986fec4f694ed3a1e7"}, - {file = "pydantic_core-2.42.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c43088e8a44ccb2a2329d83892110587ebe661090b546dd03624a933fc4cfd0d"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13a7f9dde97c8400de559b2b2dcd9439f7b2b8951dad9b19711ef8c6e3f68ac0"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6380214c627f702993ea6b65b6aa8afc0f1481a179cdd169a2fc80a195e21158"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:606f80d8c61d4680ff82a34e9c49b7ab069b544b93393cc3c5906ac9e8eec7c9"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ab80ae93cb739de6c9ccc06a12cd731b079e1b25b03e2dcdccbc914389cc7e0"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:638f04b55bea04ec5bbda57a4743a51051f24b884abcb155b0ed2c3cb59ba448"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec72ba5c7555f69757b64b398509c7079fb22da705a6c67ac613e3f14a05f729"}, - {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0364f6cd61be57bcd629c34788c197db211e91ce1c3009bf4bf97f6bb0eb21f"}, - {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:856f0fd81173b308cd6ceb714332cd9ea3c66ce43176c7defaed6b2ed51d745c"}, - {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1be705396e480ea96fd3cccd7512affda86823b8a2a8c196d9028ec37cb1ca77"}, - {file = "pydantic_core-2.42.0-cp312-cp312-win32.whl", hash = "sha256:acacf0795d68e42d01ae8cc77ae19a5b3c80593e0fd60e4e2d336ec13d3de906"}, - {file = "pydantic_core-2.42.0-cp312-cp312-win_amd64.whl", hash = "sha256:475a1a5ecf3a748a0d066b56138d258018c8145873ee899745c9f0e0af1cc4d4"}, - {file = "pydantic_core-2.42.0-cp312-cp312-win_arm64.whl", hash = "sha256:e2369cef245dd5aeafe6964cf43d571fb478f317251749c152c0ae564127053a"}, - {file = "pydantic_core-2.42.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:02fd2b4a62efa12e004fce2bfd2648cf8c39efc5dfc5ed5f196eb4ccefc7db4e"}, - {file = "pydantic_core-2.42.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c042694870c20053b8814a57c416cd2c6273fe462a440460005c791c24c39baf"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f905f3a082e7498dfaa70c204b236e92d448ba966ad112a96fcaaba2c4984fba"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4762081e8acc5458bf907373817cf93c927d451a1b294c1d0535b0570890d939"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4a433bbf6304bd114b96b0ce3ed9add2ee686df448892253bca5f622c030f31"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd695305724cfce8b19a18e87809c518f56905e5c03a19e3ad061974970f717d"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f352ffa0ec2983b849a93714571063bfc57413b5df2f1027d7a04b6e8bdd25"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e61f2a194291338d76307a29e4881a8007542150b750900c1217117fc9bb698e"}, - {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:032f990dc1759f11f6b287e5c6eb1b0bcfbc18141779414a77269b420360b3bf"}, - {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:9c28b42768da6b9238554ae23b39291c3bbe6f53c4810aea6414d83efd59b96a"}, - {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b22af1ac75fa873d81a65cce22ada1d840583b73a129b06133097c81f6f9e53b"}, - {file = "pydantic_core-2.42.0-cp313-cp313-win32.whl", hash = "sha256:1de0350645c8643003176659ee70b637cd80e8514a063fff36f088fcda2dba06"}, - {file = "pydantic_core-2.42.0-cp313-cp313-win_amd64.whl", hash = "sha256:d34b481a8a3eba3678a96e166c6e547c0c8b026844c13d9deb70c9f1fd2b0979"}, - {file = "pydantic_core-2.42.0-cp313-cp313-win_arm64.whl", hash = "sha256:5e0a65358eef041d95eef93fcf8834c2c8b83cc5a92d32f84bb3a7955dfe21c9"}, - {file = "pydantic_core-2.42.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:de4c9ad4615983b3fb2ee57f5c570cf964bda13353c6c41a54dac394927f0e54"}, - {file = "pydantic_core-2.42.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:129d5e6357814e4567e18b2ded4c210919aafd9ef0887235561f8d853fd34123"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c45582a5dac4649e512840ad212a5c2f9d168622f8db8863e8a29b54a29dfd"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a97fc19afb730b45de55d2e80093f1a36effc29538dec817204c929add8f2b4a"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45d83d38d94f22ffe9a0f0393b23e25bfefe4804ae63c8013906b76ab8de8ed"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3060192d8b63611a2abb26eccadddff5602a66491b8fafd9ae34fb67302ae84"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f17739150af9dc58b5c8fc3c4a1826ff84461f11b9f8ad5618445fcdd1ccec6"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d14e4c229467a7c27aa7c71e21584b3d77352ccb64e968fdbed4633373f73f7"}, - {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:aaef75e1b54366c7ccfbf4fc949ceaaa0f4c87e106df850354be6c7d45143db0"}, - {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:d2e362dceeeb4d56fd63e649c2de3ad4c3aa448b13ab8a9976e23a669f9c1854"}, - {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:a8edee724b527818bf0a6c8e677549794c0d0caffd14492851bd7a4ceab0f258"}, - {file = "pydantic_core-2.42.0-cp314-cp314-win32.whl", hash = "sha256:a10c105c221f68221cb81be71f063111172f5ddf8b06f6494560e826c148f872"}, - {file = "pydantic_core-2.42.0-cp314-cp314-win_amd64.whl", hash = "sha256:232d86e00870aceee7251aa5f4ab17e3e4864a4656c015f8e03d1223bf8e17ba"}, - {file = "pydantic_core-2.42.0-cp314-cp314-win_arm64.whl", hash = "sha256:9a6fce4e778c2fe2b3f1df63bfaa522c147668517ba040c49ad7f67a66867cff"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f4d1670fbc5488cfb18dd9fc71a2c7c8e12caeeb6e5bb641aa351ac5e01963cf"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:baeae16666139d0110f1006a06809228f5293ab84e77f4b9dda2bdee95d6c4e8"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a77c7a8cedf5557a4e5547dabf55a8ec99949162bd7925b312f6ec37c24101c"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:133fccf13546ff2a0610cc5b978dd4ee2c7f55a7a86b6b722fd6e857694bacc5"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad5dbebfbab92cf0f6d0b13d55bf0a239880a1534377edf6387e2e7a4469f131"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6c0181016cb29ba4824940246606a8e13b1135de8306e00b5bd9d1efbc4cf85"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:020cfd7041cb71eac4dc93a29a6d5ec34f10b1fdc37f4f189c25bcc6748a2f97"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73c6de3ee24f2b614d344491eda5628c4cdf3e7b79c0ac69bb40884ced2d319"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b2b448da50e1e8d5aac786dcf441afa761d26f1be4532b52cdf50864b47bd784"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:0df0488b1f548ef874b45bbc60a70631eee0177b79b5527344d7a253e77a5ed2"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:b8aa32697701dc36c956f4a78172549adbe25eacba952bbfbde786fb66316151"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-win32.whl", hash = "sha256:173de56229897ff81b650ca9ed6f4c62401c49565234d3e9ae251119f6fd45c6"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2db227cf6797c286361f8d1e52b513f358a3ff9ebdede335e55a5edf4c59f06b"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a983862733ecaf0b5c7275145f86397bde4ee1ad84cf650e1d7af7febe5f7073"}, - {file = "pydantic_core-2.42.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fc0834a2d658189c89d7a009ae19462da1d70fc4786d2b8e5c8c6971f4d3bcc1"}, - {file = "pydantic_core-2.42.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff69cf1eb517600d40c903dbc3507360e0a6c1ffa2dcf3cfa49a1c6fe203a46a"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3eab236da1c53a8cdf741765e31190906eb2838837bfedcaa6c0206b8f5975e"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15df82e324fa5b2b1403d5eb1bb186d14214c3ce0aebc9a3594435b82154d402"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ee7047297892d4fec68658898b7495be8c1a8a2932774e2d6810c3de1173783"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aec13272d859be1dd3344b75aab4d1d6690bfef78bd241628f6903c2bf101f8d"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e7adfd7794da8ae101d2d5e6a7be7cb39bb90d45b6aa42ecb502a256e94f8e0"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e3cfcacb42193479ead3aaba26a79e7df4c1c2415aefc43f1a60b57f50f8aa4"}, - {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf89cee72f88db54763f800d32948bd6b1b9bf03e0ecb0a9cb93eac513caec5f"}, - {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c6ae4c08e6c4b08e35eb2b114803d09c5012602983d8bbd3564013d555dfe5fd"}, - {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dfedd24ce01a3ea32f29c257e5a7fc79ed635cff0bd1a1aed12a22d3440cb39f"}, - {file = "pydantic_core-2.42.0-cp39-cp39-win32.whl", hash = "sha256:26ab24eecdec230bdf7ec519b9cd0c65348ec6e97304e87f9d3409749ea3377b"}, - {file = "pydantic_core-2.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:f93228d630913af3bc2d55a50a96e0d33446b219aea9591bfdc0a06677f689ff"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:53ab90bed3a191750a6726fe2570606a9794608696063823d2deea734c100bf6"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:b8d9911a3cdb8062f4102499b666303c9a976202b420200a26606eafa0bfecf8"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe6b7b22dd1d326a1ab23b9e611a69c41d606cb723839755bb00456ebff3f672"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5e36849ca8e2e39828a70f1a86aa2b86f645a1d710223b6653f2fa8a130b703"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4d7e36c2a1f3c0020742190714388884a11282a0179f3d1c55796ee26b32dba5"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:41a702c2ac3dbbafa7d13bea142b3e04c8676d1fca199bac52b5ee24e6cdb737"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad5cb8ed96ffac804a0298f5d03f002769514700d79cbe77b66a27a6e605a65a"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51e33cf940cddcad333f85e15a25a2a949ac0a7f26fe8f43dc2d6816ce974ec4"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495e70705f553c3b8f939965fa7cf77825c81417ff3c7ac046be9509b94c292c"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8757702cc696d48f9fdcb65cb835ca18bda5d83169fe6d13efd706e4195aea81"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32cc3087f38e4a9ee679f6184670a1b6591b8c3840c483f3342e176e215194d1"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e824d8f372aa717eeb435ee220c8247e514283a4fc0ecdc4ce44c09ee485a5b8"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e5900b257abb20371135f28b686d6990202dcdd9b7d8ff2e2290568aa0058280"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:f6705c73ab2abaebef81cad882a75afd6b8a0550e853768933610dce2945705e"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5ed95136324ceef6f33bd96ee3a299d36169175401204590037983aeb5bc73de"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9d729a3934e0ef3bc171025f0414d422aa6397d6bbd8176d5402739140e50616"}, - {file = "pydantic_core-2.42.0.tar.gz", hash = "sha256:34068adadf673c872f01265fa17ec00073e99d7f53f6d499bdfae652f330b3d2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, ] [package.dependencies] @@ -1445,6 +1432,20 @@ files = [ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "urllib3" version = "2.6.3" @@ -1610,4 +1611,4 @@ aiohttp = ["aiohttp", "httpx-aiohttp"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a8bb006c9955dae7967f3694092cf6799a89e0aac94e82e903d8b308c4d0cfc4" +content-hash = "7ac12b5d251e81a278fba2c051cbf4f768fb951f006f23ad0faedb1289539c1a" diff --git a/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml b/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml index 53eee1990c99..dc12b84c3a93 100644 --- a/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml +++ b/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml @@ -45,7 +45,7 @@ python = "^3.10" aiohttp = { version = ">=3.10.0,<4", optional = true} httpx = ">=0.21.2" httpx-aiohttp = { version = "0.1.8", optional = true} -pydantic = "^1.10" +pydantic = ">= 1.9.2" pydantic-core = ">=2.18.2,<2.44.0" typing_extensions = ">= 4.0.0" diff --git a/seed/python-sdk/allof/no-custom-config/.fern/metadata.json b/seed/python-sdk/allof/no-custom-config/.fern/metadata.json index e489d22a79b5..d3303ad03b0b 100644 --- a/seed/python-sdk/allof/no-custom-config/.fern/metadata.json +++ b/seed/python-sdk/allof/no-custom-config/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "generatorConfig": { "enable_wire_tests": true }, diff --git a/seed/python-sdk/allof/no-custom-config/poetry.lock b/seed/python-sdk/allof/no-custom-config/poetry.lock index 75cfea1d0649..613c65b508b1 100644 --- a/seed/python-sdk/allof/no-custom-config/poetry.lock +++ b/seed/python-sdk/allof/no-custom-config/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -168,6 +168,17 @@ files = [ frozenlist = ">=1.1.0" typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + [[package]] name = "anyio" version = "4.13.0" @@ -1027,177 +1038,153 @@ files = [ [[package]] name = "pydantic" -version = "1.10.26" -description = "Data validation and settings management using python type hints" +version = "2.12.5" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546"}, - {file = "pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572"}, - {file = "pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464"}, - {file = "pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53"}, - {file = "pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945"}, - {file = "pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637"}, - {file = "pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77"}, - {file = "pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f"}, - {file = "pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5"}, - {file = "pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df"}, - {file = "pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f"}, - {file = "pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f"}, - {file = "pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb"}, - {file = "pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b"}, - {file = "pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c"}, - {file = "pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c"}, - {file = "pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba"}, - {file = "pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62"}, - {file = "pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847"}, - {file = "pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314"}, - {file = "pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba"}, - {file = "pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37"}, - {file = "pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a"}, - {file = "pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962"}, - {file = "pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c"}, - {file = "pydantic-1.10.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc5c91a3b3106caf07ac6735ec6efad8ba37b860b9eb569923386debe65039ad"}, - {file = "pydantic-1.10.26-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dde599e0388e04778480d57f49355c9cc7916de818bf674de5d5429f2feebfb6"}, - {file = "pydantic-1.10.26-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be08b5cfe88e58198722861c7aab737c978423c3a27300911767931e5311d0d"}, - {file = "pydantic-1.10.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0141f4bafe5eda539d98c9755128a9ea933654c6ca4306b5059fc87a01a38573"}, - {file = "pydantic-1.10.26-cp38-cp38-win_amd64.whl", hash = "sha256:eb664305ffca8a9766a8629303bb596607d77eae35bb5f32ff9245984881b638"}, - {file = "pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d"}, - {file = "pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15"}, - {file = "pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90"}, - {file = "pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba"}, - {file = "pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96"}, - {file = "pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917"}, - {file = "pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.42.0" +version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.42.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:0ae7d50a47ada2a04f7296be9a7a2bf447118a25855f41fc52c8fc4bfb70c105"}, - {file = "pydantic_core-2.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c9d04d4bd8de1dcd5c8845faf6c11e36cda34c2efffa29d70ad83cc6f6a6c9a8"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e459e89453bb1bc69853272260afb5328ae404f854ddec485f5427fbace8d7e"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:def66968fbe20274093fd4fc85d82b2ec42dbe20d9e51d27bbf3b5c7428c7a10"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:272fab515dc7da0f456c49747b87b4e8721a33ab352a54760cc8fd1a4fd5348a"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa82dec59f36106738ae981878e0001074e2b3a949f21a5b3bea20485b9c6db4"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a70fe4db00ab03a9f976d28471c8e696ebd3b8455ccfa5e36e5d1a2ff301a7"}, - {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b4c0f656b4fa218413a485c550ac3e4ddf2f343a9c46b6137394bd77c4128445"}, - {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a4396ffc8b42499d14662f958b3f00656b62a67bde7f156580fd618827bebf5a"}, - {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:36067825f365a5c3065f17d08421a72b036ff4588c450afe54d5750b80cc220d"}, - {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eec64367de940786c0b686d47bd952692018dd7cd895027aa82023186e469b7d"}, - {file = "pydantic_core-2.42.0-cp310-cp310-win32.whl", hash = "sha256:ff9f0737f487277721682d8518434557cfcef141ba55b89381c92700594a8b65"}, - {file = "pydantic_core-2.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:77f0a8ab035d3bc319b759d8215f51846e9ea582dacbabb2777e5e3e135a048e"}, - {file = "pydantic_core-2.42.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1159b9ee73511ae7c5631b108d80373577bc14f22d18d85bb2aa1fa1051dabc"}, - {file = "pydantic_core-2.42.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff8e49b22225445d3e078aaa9bead90c37c852aee8f8a169ba15fdaaa13d1ecb"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe777d9a1a932c6b3ef32b201985324d06d9c74028adef1e1c7ea226fca2ba34"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e92592c1040ed17968d603e05b72acec321662ef9bf88fef443ceae4d1a130c2"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:557a6eb6dc4db8a3f071929710feb29c6b5d7559218ab547a4e60577fb404f2f"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4035f81e7d1a5e065543061376ca52ccb0accaf970911ba0a9ec9d22062806ca"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63a4e073f8def1c7fd100a355b3a96e1bbaf0446b6a8530ae58f1afaa0478a46"}, - {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd8469c8d9f6c81befd10c72a0268079e929ba494cd27fa63e868964b0e04fb6"}, - {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bdebfd610a02bdb82f8e36dc7d4683e03e420624a2eda63e1205730970021308"}, - {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9577eb5221abd4e5adf8a232a65f74c509b82b57b7b96b3667dac22f03ff9e94"}, - {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6d36841b61100128c2374341a7c2c0ab347ef4b63aa4b6837b4431465d4d4fd"}, - {file = "pydantic_core-2.42.0-cp311-cp311-win32.whl", hash = "sha256:1d9d45333a28b0b8fb8ecedf67d280dc3318899988093e4d3a81618396270697"}, - {file = "pydantic_core-2.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:4631b4d1a3fe460aadd3822af032bb6c2e7ad77071fbf71c4e95ef9083c7c1a8"}, - {file = "pydantic_core-2.42.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d46bfc6175a4b4b80b9f98f76133fbf68d5a02d7469b3090ca922d40f23d32d"}, - {file = "pydantic_core-2.42.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a11b9115364681779bcc39c6b9cdc20d48a9812a4bf3ed986fec4f694ed3a1e7"}, - {file = "pydantic_core-2.42.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c43088e8a44ccb2a2329d83892110587ebe661090b546dd03624a933fc4cfd0d"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13a7f9dde97c8400de559b2b2dcd9439f7b2b8951dad9b19711ef8c6e3f68ac0"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6380214c627f702993ea6b65b6aa8afc0f1481a179cdd169a2fc80a195e21158"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:606f80d8c61d4680ff82a34e9c49b7ab069b544b93393cc3c5906ac9e8eec7c9"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ab80ae93cb739de6c9ccc06a12cd731b079e1b25b03e2dcdccbc914389cc7e0"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:638f04b55bea04ec5bbda57a4743a51051f24b884abcb155b0ed2c3cb59ba448"}, - {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec72ba5c7555f69757b64b398509c7079fb22da705a6c67ac613e3f14a05f729"}, - {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0364f6cd61be57bcd629c34788c197db211e91ce1c3009bf4bf97f6bb0eb21f"}, - {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:856f0fd81173b308cd6ceb714332cd9ea3c66ce43176c7defaed6b2ed51d745c"}, - {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1be705396e480ea96fd3cccd7512affda86823b8a2a8c196d9028ec37cb1ca77"}, - {file = "pydantic_core-2.42.0-cp312-cp312-win32.whl", hash = "sha256:acacf0795d68e42d01ae8cc77ae19a5b3c80593e0fd60e4e2d336ec13d3de906"}, - {file = "pydantic_core-2.42.0-cp312-cp312-win_amd64.whl", hash = "sha256:475a1a5ecf3a748a0d066b56138d258018c8145873ee899745c9f0e0af1cc4d4"}, - {file = "pydantic_core-2.42.0-cp312-cp312-win_arm64.whl", hash = "sha256:e2369cef245dd5aeafe6964cf43d571fb478f317251749c152c0ae564127053a"}, - {file = "pydantic_core-2.42.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:02fd2b4a62efa12e004fce2bfd2648cf8c39efc5dfc5ed5f196eb4ccefc7db4e"}, - {file = "pydantic_core-2.42.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c042694870c20053b8814a57c416cd2c6273fe462a440460005c791c24c39baf"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f905f3a082e7498dfaa70c204b236e92d448ba966ad112a96fcaaba2c4984fba"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4762081e8acc5458bf907373817cf93c927d451a1b294c1d0535b0570890d939"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4a433bbf6304bd114b96b0ce3ed9add2ee686df448892253bca5f622c030f31"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd695305724cfce8b19a18e87809c518f56905e5c03a19e3ad061974970f717d"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f352ffa0ec2983b849a93714571063bfc57413b5df2f1027d7a04b6e8bdd25"}, - {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e61f2a194291338d76307a29e4881a8007542150b750900c1217117fc9bb698e"}, - {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:032f990dc1759f11f6b287e5c6eb1b0bcfbc18141779414a77269b420360b3bf"}, - {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:9c28b42768da6b9238554ae23b39291c3bbe6f53c4810aea6414d83efd59b96a"}, - {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b22af1ac75fa873d81a65cce22ada1d840583b73a129b06133097c81f6f9e53b"}, - {file = "pydantic_core-2.42.0-cp313-cp313-win32.whl", hash = "sha256:1de0350645c8643003176659ee70b637cd80e8514a063fff36f088fcda2dba06"}, - {file = "pydantic_core-2.42.0-cp313-cp313-win_amd64.whl", hash = "sha256:d34b481a8a3eba3678a96e166c6e547c0c8b026844c13d9deb70c9f1fd2b0979"}, - {file = "pydantic_core-2.42.0-cp313-cp313-win_arm64.whl", hash = "sha256:5e0a65358eef041d95eef93fcf8834c2c8b83cc5a92d32f84bb3a7955dfe21c9"}, - {file = "pydantic_core-2.42.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:de4c9ad4615983b3fb2ee57f5c570cf964bda13353c6c41a54dac394927f0e54"}, - {file = "pydantic_core-2.42.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:129d5e6357814e4567e18b2ded4c210919aafd9ef0887235561f8d853fd34123"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c45582a5dac4649e512840ad212a5c2f9d168622f8db8863e8a29b54a29dfd"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a97fc19afb730b45de55d2e80093f1a36effc29538dec817204c929add8f2b4a"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45d83d38d94f22ffe9a0f0393b23e25bfefe4804ae63c8013906b76ab8de8ed"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3060192d8b63611a2abb26eccadddff5602a66491b8fafd9ae34fb67302ae84"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f17739150af9dc58b5c8fc3c4a1826ff84461f11b9f8ad5618445fcdd1ccec6"}, - {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d14e4c229467a7c27aa7c71e21584b3d77352ccb64e968fdbed4633373f73f7"}, - {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:aaef75e1b54366c7ccfbf4fc949ceaaa0f4c87e106df850354be6c7d45143db0"}, - {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:d2e362dceeeb4d56fd63e649c2de3ad4c3aa448b13ab8a9976e23a669f9c1854"}, - {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:a8edee724b527818bf0a6c8e677549794c0d0caffd14492851bd7a4ceab0f258"}, - {file = "pydantic_core-2.42.0-cp314-cp314-win32.whl", hash = "sha256:a10c105c221f68221cb81be71f063111172f5ddf8b06f6494560e826c148f872"}, - {file = "pydantic_core-2.42.0-cp314-cp314-win_amd64.whl", hash = "sha256:232d86e00870aceee7251aa5f4ab17e3e4864a4656c015f8e03d1223bf8e17ba"}, - {file = "pydantic_core-2.42.0-cp314-cp314-win_arm64.whl", hash = "sha256:9a6fce4e778c2fe2b3f1df63bfaa522c147668517ba040c49ad7f67a66867cff"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f4d1670fbc5488cfb18dd9fc71a2c7c8e12caeeb6e5bb641aa351ac5e01963cf"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:baeae16666139d0110f1006a06809228f5293ab84e77f4b9dda2bdee95d6c4e8"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a77c7a8cedf5557a4e5547dabf55a8ec99949162bd7925b312f6ec37c24101c"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:133fccf13546ff2a0610cc5b978dd4ee2c7f55a7a86b6b722fd6e857694bacc5"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad5dbebfbab92cf0f6d0b13d55bf0a239880a1534377edf6387e2e7a4469f131"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6c0181016cb29ba4824940246606a8e13b1135de8306e00b5bd9d1efbc4cf85"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:020cfd7041cb71eac4dc93a29a6d5ec34f10b1fdc37f4f189c25bcc6748a2f97"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73c6de3ee24f2b614d344491eda5628c4cdf3e7b79c0ac69bb40884ced2d319"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b2b448da50e1e8d5aac786dcf441afa761d26f1be4532b52cdf50864b47bd784"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:0df0488b1f548ef874b45bbc60a70631eee0177b79b5527344d7a253e77a5ed2"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:b8aa32697701dc36c956f4a78172549adbe25eacba952bbfbde786fb66316151"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-win32.whl", hash = "sha256:173de56229897ff81b650ca9ed6f4c62401c49565234d3e9ae251119f6fd45c6"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2db227cf6797c286361f8d1e52b513f358a3ff9ebdede335e55a5edf4c59f06b"}, - {file = "pydantic_core-2.42.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a983862733ecaf0b5c7275145f86397bde4ee1ad84cf650e1d7af7febe5f7073"}, - {file = "pydantic_core-2.42.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fc0834a2d658189c89d7a009ae19462da1d70fc4786d2b8e5c8c6971f4d3bcc1"}, - {file = "pydantic_core-2.42.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff69cf1eb517600d40c903dbc3507360e0a6c1ffa2dcf3cfa49a1c6fe203a46a"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3eab236da1c53a8cdf741765e31190906eb2838837bfedcaa6c0206b8f5975e"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15df82e324fa5b2b1403d5eb1bb186d14214c3ce0aebc9a3594435b82154d402"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ee7047297892d4fec68658898b7495be8c1a8a2932774e2d6810c3de1173783"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aec13272d859be1dd3344b75aab4d1d6690bfef78bd241628f6903c2bf101f8d"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e7adfd7794da8ae101d2d5e6a7be7cb39bb90d45b6aa42ecb502a256e94f8e0"}, - {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e3cfcacb42193479ead3aaba26a79e7df4c1c2415aefc43f1a60b57f50f8aa4"}, - {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf89cee72f88db54763f800d32948bd6b1b9bf03e0ecb0a9cb93eac513caec5f"}, - {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c6ae4c08e6c4b08e35eb2b114803d09c5012602983d8bbd3564013d555dfe5fd"}, - {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dfedd24ce01a3ea32f29c257e5a7fc79ed635cff0bd1a1aed12a22d3440cb39f"}, - {file = "pydantic_core-2.42.0-cp39-cp39-win32.whl", hash = "sha256:26ab24eecdec230bdf7ec519b9cd0c65348ec6e97304e87f9d3409749ea3377b"}, - {file = "pydantic_core-2.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:f93228d630913af3bc2d55a50a96e0d33446b219aea9591bfdc0a06677f689ff"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:53ab90bed3a191750a6726fe2570606a9794608696063823d2deea734c100bf6"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:b8d9911a3cdb8062f4102499b666303c9a976202b420200a26606eafa0bfecf8"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe6b7b22dd1d326a1ab23b9e611a69c41d606cb723839755bb00456ebff3f672"}, - {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5e36849ca8e2e39828a70f1a86aa2b86f645a1d710223b6653f2fa8a130b703"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4d7e36c2a1f3c0020742190714388884a11282a0179f3d1c55796ee26b32dba5"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:41a702c2ac3dbbafa7d13bea142b3e04c8676d1fca199bac52b5ee24e6cdb737"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad5cb8ed96ffac804a0298f5d03f002769514700d79cbe77b66a27a6e605a65a"}, - {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51e33cf940cddcad333f85e15a25a2a949ac0a7f26fe8f43dc2d6816ce974ec4"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495e70705f553c3b8f939965fa7cf77825c81417ff3c7ac046be9509b94c292c"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8757702cc696d48f9fdcb65cb835ca18bda5d83169fe6d13efd706e4195aea81"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32cc3087f38e4a9ee679f6184670a1b6591b8c3840c483f3342e176e215194d1"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e824d8f372aa717eeb435ee220c8247e514283a4fc0ecdc4ce44c09ee485a5b8"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e5900b257abb20371135f28b686d6990202dcdd9b7d8ff2e2290568aa0058280"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:f6705c73ab2abaebef81cad882a75afd6b8a0550e853768933610dce2945705e"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5ed95136324ceef6f33bd96ee3a299d36169175401204590037983aeb5bc73de"}, - {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9d729a3934e0ef3bc171025f0414d422aa6397d6bbd8176d5402739140e50616"}, - {file = "pydantic_core-2.42.0.tar.gz", hash = "sha256:34068adadf673c872f01265fa17ec00073e99d7f53f6d499bdfae652f330b3d2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, ] [package.dependencies] @@ -1445,6 +1432,20 @@ files = [ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "urllib3" version = "2.6.3" @@ -1610,4 +1611,4 @@ aiohttp = ["aiohttp", "httpx-aiohttp"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a8bb006c9955dae7967f3694092cf6799a89e0aac94e82e903d8b308c4d0cfc4" +content-hash = "7ac12b5d251e81a278fba2c051cbf4f768fb951f006f23ad0faedb1289539c1a" diff --git a/seed/python-sdk/allof/no-custom-config/pyproject.toml b/seed/python-sdk/allof/no-custom-config/pyproject.toml index 8dfe9ad891fd..16a027ebcb5f 100644 --- a/seed/python-sdk/allof/no-custom-config/pyproject.toml +++ b/seed/python-sdk/allof/no-custom-config/pyproject.toml @@ -45,7 +45,7 @@ python = "^3.10" aiohttp = { version = ">=3.10.0,<4", optional = true} httpx = ">=0.21.2" httpx-aiohttp = { version = "0.1.8", optional = true} -pydantic = "^1.10" +pydantic = ">= 1.9.2" pydantic-core = ">=2.18.2,<2.44.0" typing_extensions = ">= 4.0.0" diff --git a/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json b/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json index 885f09fc37f2..4e868ad06d8a 100644 --- a/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json +++ b/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-ruby-sdk-v2", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof/.fern/metadata.json b/seed/ruby-sdk-v2/allof/.fern/metadata.json index 885f09fc37f2..4e868ad06d8a 100644 --- a/seed/ruby-sdk-v2/allof/.fern/metadata.json +++ b/seed/ruby-sdk-v2/allof/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-ruby-sdk-v2", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/rust-model/allof-inline/.fern/metadata.json b/seed/rust-model/allof-inline/.fern/metadata.json new file mode 100644 index 000000000000..a9c2c174fe54 --- /dev/null +++ b/seed/rust-model/allof-inline/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-rust-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/rust-model/allof-inline/snippet.json b/seed/rust-model/allof-inline/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/rust-model/allof-inline/src/audit_info.rs b/seed/rust-model/allof-inline/src/audit_info.rs new file mode 100644 index 000000000000..a958df434a50 --- /dev/null +++ b/seed/rust-model/allof-inline/src/audit_info.rs @@ -0,0 +1,73 @@ +pub use crate::prelude::*; + +/// Common audit metadata. +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct AuditInfo { + /// The user who created this resource. + #[serde(rename = "createdBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + /// When this resource was created. + #[serde(rename = "createdDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub created_date_time: Option>, + /// The user who last modified this resource. + #[serde(rename = "modifiedBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_by: Option, + /// When this resource was last modified. + #[serde(rename = "modifiedDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub modified_date_time: Option>, +} + +impl AuditInfo { + pub fn builder() -> AuditInfoBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct AuditInfoBuilder { + created_by: Option, + created_date_time: Option>, + modified_by: Option, + modified_date_time: Option>, +} + +impl AuditInfoBuilder { + pub fn created_by(mut self, value: impl Into) -> Self { + self.created_by = Some(value.into()); + self + } + + pub fn created_date_time(mut self, value: DateTime) -> Self { + self.created_date_time = Some(value); + self + } + + pub fn modified_by(mut self, value: impl Into) -> Self { + self.modified_by = Some(value.into()); + self + } + + pub fn modified_date_time(mut self, value: DateTime) -> Self { + self.modified_date_time = Some(value); + self + } + + /// Consumes the builder and constructs a [`AuditInfo`]. + pub fn build(self) -> Result { + Ok(AuditInfo { + created_by: self.created_by, + created_date_time: self.created_date_time, + modified_by: self.modified_by, + modified_date_time: self.modified_date_time, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/base_org.rs b/seed/rust-model/allof-inline/src/base_org.rs new file mode 100644 index 000000000000..1747ce8ded53 --- /dev/null +++ b/seed/rust-model/allof-inline/src/base_org.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrg { + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl BaseOrg { + pub fn builder() -> BaseOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgBuilder { + id: Option, + metadata: Option, +} + +impl BaseOrgBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`BaseOrg`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](BaseOrgBuilder::id) + pub fn build(self) -> Result { + Ok(BaseOrg { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/base_org_metadata.rs b/seed/rust-model/allof-inline/src/base_org_metadata.rs new file mode 100644 index 000000000000..797f4434eb49 --- /dev/null +++ b/seed/rust-model/allof-inline/src/base_org_metadata.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrgMetadata { + /// Deployment region from BaseOrg. + #[serde(default)] + pub region: String, + /// Subscription tier. + #[serde(skip_serializing_if = "Option::is_none")] + pub tier: Option, +} + +impl BaseOrgMetadata { + pub fn builder() -> BaseOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgMetadataBuilder { + region: Option, + tier: Option, +} + +impl BaseOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn tier(mut self, value: impl Into) -> Self { + self.tier = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`BaseOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](BaseOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(BaseOrgMetadata { + region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, + tier: self.tier, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/combined_entity.rs b/seed/rust-model/allof-inline/src/combined_entity.rs new file mode 100644 index 000000000000..ded16140ef34 --- /dev/null +++ b/seed/rust-model/allof-inline/src/combined_entity.rs @@ -0,0 +1,65 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct CombinedEntity { + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Describable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + pub status: CombinedEntityStatus, +} + +impl CombinedEntity { + pub fn builder() -> CombinedEntityBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct CombinedEntityBuilder { + id: Option, + name: Option, + summary: Option, + status: Option, +} + +impl CombinedEntityBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + pub fn status(mut self, value: CombinedEntityStatus) -> Self { + self.status = Some(value); + self + } + + /// Consumes the builder and constructs a [`CombinedEntity`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](CombinedEntityBuilder::id) + /// - [`status`](CombinedEntityBuilder::status) + pub fn build(self) -> Result { + Ok(CombinedEntity { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + summary: self.summary, + status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/combined_entity_status.rs b/seed/rust-model/allof-inline/src/combined_entity_status.rs new file mode 100644 index 000000000000..f46cb23eecb2 --- /dev/null +++ b/seed/rust-model/allof-inline/src/combined_entity_status.rs @@ -0,0 +1,42 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CombinedEntityStatus { + Active, + Archived, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for CombinedEntityStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Archived => serializer.serialize_str("archived"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for CombinedEntityStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "archived" => Ok(Self::Archived), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for CombinedEntityStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Archived => write!(f, "archived"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-model/allof-inline/src/describable.rs b/seed/rust-model/allof-inline/src/describable.rs new file mode 100644 index 000000000000..6a8a527bd844 --- /dev/null +++ b/seed/rust-model/allof-inline/src/describable.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Describable { + /// Display name from Describable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, +} + +impl Describable { + pub fn builder() -> DescribableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DescribableBuilder { + name: Option, + summary: Option, +} + +impl DescribableBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Describable`]. + pub fn build(self) -> Result { + Ok(Describable { + name: self.name, + summary: self.summary, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/detailed_org.rs b/seed/rust-model/allof-inline/src/detailed_org.rs new file mode 100644 index 000000000000..fac068d8a2ee --- /dev/null +++ b/seed/rust-model/allof-inline/src/detailed_org.rs @@ -0,0 +1,33 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrg { + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl DetailedOrg { + pub fn builder() -> DetailedOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgBuilder { + metadata: Option, +} + +impl DetailedOrgBuilder { + pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`DetailedOrg`]. + pub fn build(self) -> Result { + Ok(DetailedOrg { + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/detailed_org_metadata.rs b/seed/rust-model/allof-inline/src/detailed_org_metadata.rs new file mode 100644 index 000000000000..06ec63051131 --- /dev/null +++ b/seed/rust-model/allof-inline/src/detailed_org_metadata.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrgMetadata { + /// Deployment region from DetailedOrg. + #[serde(default)] + pub region: String, + /// Custom domain name. + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, +} + +impl DetailedOrgMetadata { + pub fn builder() -> DetailedOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgMetadataBuilder { + region: Option, + domain: Option, +} + +impl DetailedOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn domain(mut self, value: impl Into) -> Self { + self.domain = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](DetailedOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(DetailedOrgMetadata { + region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, + domain: self.domain, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/error.rs b/seed/rust-model/allof-inline/src/error.rs new file mode 100644 index 000000000000..0966ed354a0f --- /dev/null +++ b/seed/rust-model/allof-inline/src/error.rs @@ -0,0 +1,19 @@ +/// Error returned when a required field was not set on a builder. +#[derive(Debug)] +pub struct BuildError { + field: &'static str, +} + +impl BuildError { + pub fn missing_field(field: &'static str) -> Self { + Self { field } + } +} + +impl std::fmt::Display for BuildError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "`{}` was not set but is required", self.field) + } +} + +impl std::error::Error for BuildError {} diff --git a/seed/rust-model/allof-inline/src/identifiable.rs b/seed/rust-model/allof-inline/src/identifiable.rs new file mode 100644 index 000000000000..58fb3eb70734 --- /dev/null +++ b/seed/rust-model/allof-inline/src/identifiable.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Identifiable { + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Identifiable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +impl Identifiable { + pub fn builder() -> IdentifiableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct IdentifiableBuilder { + id: Option, + name: Option, +} + +impl IdentifiableBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Identifiable`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](IdentifiableBuilder::id) + pub fn build(self) -> Result { + Ok(Identifiable { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/lib.rs b/seed/rust-model/allof-inline/src/lib.rs new file mode 100644 index 000000000000..077ee6dee849 --- /dev/null +++ b/seed/rust-model/allof-inline/src/lib.rs @@ -0,0 +1,7 @@ +//! Generated models by Fern + +pub mod error; + +pub mod types; + +pub use types::*; diff --git a/seed/rust-model/allof-inline/src/mod.rs b/seed/rust-model/allof-inline/src/mod.rs new file mode 100644 index 000000000000..c51911de6f27 --- /dev/null +++ b/seed/rust-model/allof-inline/src/mod.rs @@ -0,0 +1,56 @@ +//! Request and response types for the allOf Composition +//! +//! This module contains all data structures used for API communication, +//! including request bodies, response types, and shared models. +//! +//! ## Type Categories +//! +//! - **Request/Response Types**: 6 types for API operations +//! - **Model Types**: 16 types for data representation + +pub mod paginated_result; +pub mod paging_cursors; +pub mod rule_execution_context; +pub mod audit_info; +pub mod rule_type; +pub mod rule_type_search_response; +pub mod user; +pub mod user_search_response; +pub mod rule_response_status; +pub mod rule_response; +pub mod identifiable; +pub mod describable; +pub mod combined_entity_status; +pub mod combined_entity; +pub mod base_org_metadata; +pub mod base_org; +pub mod detailed_org_metadata; +pub mod detailed_org; +pub mod organization_metadata; +pub mod organization; +pub mod rule_create_request; +pub mod search_rule_types_query_request; + +pub use paginated_result::PaginatedResult; +pub use paging_cursors::PagingCursors; +pub use rule_execution_context::RuleExecutionContext; +pub use audit_info::AuditInfo; +pub use rule_type::RuleType; +pub use rule_type_search_response::RuleTypeSearchResponse; +pub use user::User; +pub use user_search_response::UserSearchResponse; +pub use rule_response_status::RuleResponseStatus; +pub use rule_response::RuleResponse; +pub use identifiable::Identifiable; +pub use describable::Describable; +pub use combined_entity_status::CombinedEntityStatus; +pub use combined_entity::CombinedEntity; +pub use base_org_metadata::BaseOrgMetadata; +pub use base_org::BaseOrg; +pub use detailed_org_metadata::DetailedOrgMetadata; +pub use detailed_org::DetailedOrg; +pub use organization_metadata::OrganizationMetadata; +pub use organization::Organization; +pub use rule_create_request::RuleCreateRequest; +pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; + diff --git a/seed/rust-model/allof-inline/src/organization.rs b/seed/rust-model/allof-inline/src/organization.rs new file mode 100644 index 000000000000..3f727a6b8212 --- /dev/null +++ b/seed/rust-model/allof-inline/src/organization.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Organization { + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + #[serde(default)] + pub name: String, +} + +impl Organization { + pub fn builder() -> OrganizationBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct OrganizationBuilder { + id: Option, + metadata: Option, + name: Option, +} + +impl OrganizationBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: OrganizationMetadata) -> Self { + self.metadata = Some(value); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Organization`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](OrganizationBuilder::id) + /// - [`name`](OrganizationBuilder::name) + pub fn build(self) -> Result { + Ok(Organization { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/organization_metadata.rs b/seed/rust-model/allof-inline/src/organization_metadata.rs new file mode 100644 index 000000000000..b77a61312aa1 --- /dev/null +++ b/seed/rust-model/allof-inline/src/organization_metadata.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct OrganizationMetadata { + /// Deployment region from DetailedOrg. + #[serde(default)] + pub region: String, + /// Custom domain name. + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, +} + +impl OrganizationMetadata { + pub fn builder() -> OrganizationMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct OrganizationMetadataBuilder { + region: Option, + domain: Option, +} + +impl OrganizationMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn domain(mut self, value: impl Into) -> Self { + self.domain = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`OrganizationMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](OrganizationMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(OrganizationMetadata { + region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, + domain: self.domain, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/paginated_result.rs b/seed/rust-model/allof-inline/src/paginated_result.rs new file mode 100644 index 000000000000..d91039ad7253 --- /dev/null +++ b/seed/rust-model/allof-inline/src/paginated_result.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct PaginatedResult { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(default)] + pub results: Vec, +} + +impl PaginatedResult { + pub fn builder() -> PaginatedResultBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PaginatedResultBuilder { + paging: Option, + results: Option>, +} + +impl PaginatedResultBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`PaginatedResult`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](PaginatedResultBuilder::paging) + /// - [`results`](PaginatedResultBuilder::results) + pub fn build(self) -> Result { + Ok(PaginatedResult { + paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, + results: self.results.ok_or_else(|| BuildError::missing_field("results"))?, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/paging_cursors.rs b/seed/rust-model/allof-inline/src/paging_cursors.rs new file mode 100644 index 000000000000..a3a785119b57 --- /dev/null +++ b/seed/rust-model/allof-inline/src/paging_cursors.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct PagingCursors { + /// Cursor for the next page of results. + #[serde(default)] + pub next: String, + /// Cursor for the previous page of results. + #[serde(skip_serializing_if = "Option::is_none")] + pub previous: Option, +} + +impl PagingCursors { + pub fn builder() -> PagingCursorsBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PagingCursorsBuilder { + next: Option, + previous: Option, +} + +impl PagingCursorsBuilder { + pub fn next(mut self, value: impl Into) -> Self { + self.next = Some(value.into()); + self + } + + pub fn previous(mut self, value: impl Into) -> Self { + self.previous = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`PagingCursors`]. + /// This method will fail if any of the following fields are not set: + /// - [`next`](PagingCursorsBuilder::next) + pub fn build(self) -> Result { + Ok(PagingCursors { + next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, + previous: self.previous, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/rule_create_request.rs b/seed/rust-model/allof-inline/src/rule_create_request.rs new file mode 100644 index 000000000000..3401318222ee --- /dev/null +++ b/seed/rust-model/allof-inline/src/rule_create_request.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleCreateRequest { + #[serde(default)] + pub name: String, + #[serde(rename = "executionContext")] + pub execution_context: RuleExecutionContext, +} + +impl RuleCreateRequest { + pub fn builder() -> RuleCreateRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleCreateRequestBuilder { + name: Option, + execution_context: Option, +} + +impl RuleCreateRequestBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleCreateRequest`]. + /// This method will fail if any of the following fields are not set: + /// - [`name`](RuleCreateRequestBuilder::name) + /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) + pub fn build(self) -> Result { + Ok(RuleCreateRequest { + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + execution_context: self.execution_context.ok_or_else(|| BuildError::missing_field("execution_context"))?, + }) + } +} + diff --git a/seed/rust-model/allof-inline/src/rule_execution_context.rs b/seed/rust-model/allof-inline/src/rule_execution_context.rs new file mode 100644 index 000000000000..7f8fa5bd56e1 --- /dev/null +++ b/seed/rust-model/allof-inline/src/rule_execution_context.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +/// Execution environment for a rule. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleExecutionContext { + Prod, + Staging, + Dev, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleExecutionContext { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Prod => serializer.serialize_str("prod"), + Self::Staging => serializer.serialize_str("staging"), + Self::Dev => serializer.serialize_str("dev"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleExecutionContext { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "prod" => Ok(Self::Prod), + "staging" => Ok(Self::Staging), + "dev" => Ok(Self::Dev), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleExecutionContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Prod => write!(f, "prod"), + Self::Staging => write!(f, "staging"), + Self::Dev => write!(f, "dev"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-model/allof-inline/src/rule_response.rs b/seed/rust-model/allof-inline/src/rule_response.rs new file mode 100644 index 000000000000..c9aa002691af --- /dev/null +++ b/seed/rust-model/allof-inline/src/rule_response.rs @@ -0,0 +1,112 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleResponse { + /// The user who created this resource. + #[serde(rename = "createdBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + /// When this resource was created. + #[serde(rename = "createdDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub created_date_time: Option>, + /// The user who last modified this resource. + #[serde(rename = "modifiedBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_by: Option, + /// When this resource was last modified. + #[serde(rename = "modifiedDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub modified_date_time: Option>, + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + pub status: RuleResponseStatus, + #[serde(rename = "executionContext")] + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_context: Option, +} + +impl RuleResponse { + pub fn builder() -> RuleResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleResponseBuilder { + created_by: Option, + created_date_time: Option>, + modified_by: Option, + modified_date_time: Option>, + id: Option, + name: Option, + status: Option, + execution_context: Option, +} + +impl RuleResponseBuilder { + pub fn created_by(mut self, value: impl Into) -> Self { + self.created_by = Some(value.into()); + self + } + + pub fn created_date_time(mut self, value: DateTime) -> Self { + self.created_date_time = Some(value); + self + } + + pub fn modified_by(mut self, value: impl Into) -> Self { + self.modified_by = Some(value.into()); + self + } + + pub fn modified_date_time(mut self, value: DateTime) -> Self { + self.modified_date_time = Some(value); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn status(mut self, value: RuleResponseStatus) -> Self { + self.status = Some(value); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](RuleResponseBuilder::id) + /// - [`name`](RuleResponseBuilder::name) + /// - [`status`](RuleResponseBuilder::status) + pub fn build(self) -> Result { + Ok(RuleResponse { + created_by: self.created_by, + created_date_time: self.created_date_time, + modified_by: self.modified_by, + modified_date_time: self.modified_date_time, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, + execution_context: self.execution_context, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/rule_response_status.rs b/seed/rust-model/allof-inline/src/rule_response_status.rs new file mode 100644 index 000000000000..5d2462ecd349 --- /dev/null +++ b/seed/rust-model/allof-inline/src/rule_response_status.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleResponseStatus { + Active, + Inactive, + Draft, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleResponseStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Inactive => serializer.serialize_str("inactive"), + Self::Draft => serializer.serialize_str("draft"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleResponseStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "inactive" => Ok(Self::Inactive), + "draft" => Ok(Self::Draft), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleResponseStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Inactive => write!(f, "inactive"), + Self::Draft => write!(f, "draft"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-model/allof-inline/src/rule_type.rs b/seed/rust-model/allof-inline/src/rule_type.rs new file mode 100644 index 000000000000..69f0317c1923 --- /dev/null +++ b/seed/rust-model/allof-inline/src/rule_type.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleType { + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +impl RuleType { + pub fn builder() -> RuleTypeBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeBuilder { + id: Option, + name: Option, + description: Option, +} + +impl RuleTypeBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn description(mut self, value: impl Into) -> Self { + self.description = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`RuleType`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](RuleTypeBuilder::id) + /// - [`name`](RuleTypeBuilder::name) + pub fn build(self) -> Result { + Ok(RuleType { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + description: self.description, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/rule_type_search_response.rs b/seed/rust-model/allof-inline/src/rule_type_search_response.rs new file mode 100644 index 000000000000..d56fa0fa7a8d --- /dev/null +++ b/seed/rust-model/allof-inline/src/rule_type_search_response.rs @@ -0,0 +1,45 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleTypeSearchResponse { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, +} + +impl RuleTypeSearchResponse { + pub fn builder() -> RuleTypeSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeSearchResponseBuilder { + paging: Option, + results: Option>, +} + +impl RuleTypeSearchResponseBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](RuleTypeSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(RuleTypeSearchResponse { + paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, + results: self.results, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/search_rule_types_query_request.rs b/seed/rust-model/allof-inline/src/search_rule_types_query_request.rs new file mode 100644 index 000000000000..ae31f5e24826 --- /dev/null +++ b/seed/rust-model/allof-inline/src/search_rule_types_query_request.rs @@ -0,0 +1,35 @@ +pub use crate::prelude::*; + +/// Query parameters for searchRuleTypes +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct SearchRuleTypesQueryRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, +} + +impl SearchRuleTypesQueryRequest { + pub fn builder() -> SearchRuleTypesQueryRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct SearchRuleTypesQueryRequestBuilder { + query: Option, +} + +impl SearchRuleTypesQueryRequestBuilder { + pub fn query(mut self, value: impl Into) -> Self { + self.query = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. + pub fn build(self) -> Result { + Ok(SearchRuleTypesQueryRequest { + query: self.query, + }) + } +} + diff --git a/seed/rust-model/allof-inline/src/user.rs b/seed/rust-model/allof-inline/src/user.rs new file mode 100644 index 000000000000..347520d397a2 --- /dev/null +++ b/seed/rust-model/allof-inline/src/user.rs @@ -0,0 +1,45 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct User { + #[serde(default)] + pub id: String, + #[serde(default)] + pub email: String, +} + +impl User { + pub fn builder() -> UserBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserBuilder { + id: Option, + email: Option, +} + +impl UserBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn email(mut self, value: impl Into) -> Self { + self.email = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`User`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](UserBuilder::id) + /// - [`email`](UserBuilder::email) + pub fn build(self) -> Result { + Ok(User { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + email: self.email.ok_or_else(|| BuildError::missing_field("email"))?, + }) + } +} diff --git a/seed/rust-model/allof-inline/src/user_search_response.rs b/seed/rust-model/allof-inline/src/user_search_response.rs new file mode 100644 index 000000000000..599feca7ab64 --- /dev/null +++ b/seed/rust-model/allof-inline/src/user_search_response.rs @@ -0,0 +1,45 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct UserSearchResponse { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, +} + +impl UserSearchResponse { + pub fn builder() -> UserSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserSearchResponseBuilder { + paging: Option, + results: Option>, +} + +impl UserSearchResponseBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`UserSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](UserSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(UserSearchResponse { + paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, + results: self.results, + }) + } +} diff --git a/seed/rust-model/allof/.fern/metadata.json b/seed/rust-model/allof/.fern/metadata.json new file mode 100644 index 000000000000..a9c2c174fe54 --- /dev/null +++ b/seed/rust-model/allof/.fern/metadata.json @@ -0,0 +1,7 @@ +{ + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-rust-model", + "generatorVersion": "local", + "originGitCommit": "DUMMY", + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/rust-model/allof/snippet.json b/seed/rust-model/allof/snippet.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/seed/rust-model/allof/src/audit_info.rs b/seed/rust-model/allof/src/audit_info.rs new file mode 100644 index 000000000000..a958df434a50 --- /dev/null +++ b/seed/rust-model/allof/src/audit_info.rs @@ -0,0 +1,73 @@ +pub use crate::prelude::*; + +/// Common audit metadata. +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct AuditInfo { + /// The user who created this resource. + #[serde(rename = "createdBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + /// When this resource was created. + #[serde(rename = "createdDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub created_date_time: Option>, + /// The user who last modified this resource. + #[serde(rename = "modifiedBy")] + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_by: Option, + /// When this resource was last modified. + #[serde(rename = "modifiedDateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(with = "crate::core::flexible_datetime::offset::option")] + pub modified_date_time: Option>, +} + +impl AuditInfo { + pub fn builder() -> AuditInfoBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct AuditInfoBuilder { + created_by: Option, + created_date_time: Option>, + modified_by: Option, + modified_date_time: Option>, +} + +impl AuditInfoBuilder { + pub fn created_by(mut self, value: impl Into) -> Self { + self.created_by = Some(value.into()); + self + } + + pub fn created_date_time(mut self, value: DateTime) -> Self { + self.created_date_time = Some(value); + self + } + + pub fn modified_by(mut self, value: impl Into) -> Self { + self.modified_by = Some(value.into()); + self + } + + pub fn modified_date_time(mut self, value: DateTime) -> Self { + self.modified_date_time = Some(value); + self + } + + /// Consumes the builder and constructs a [`AuditInfo`]. + pub fn build(self) -> Result { + Ok(AuditInfo { + created_by: self.created_by, + created_date_time: self.created_date_time, + modified_by: self.modified_by, + modified_date_time: self.modified_date_time, + }) + } +} diff --git a/seed/rust-model/allof/src/base_org.rs b/seed/rust-model/allof/src/base_org.rs new file mode 100644 index 000000000000..1747ce8ded53 --- /dev/null +++ b/seed/rust-model/allof/src/base_org.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrg { + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl BaseOrg { + pub fn builder() -> BaseOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgBuilder { + id: Option, + metadata: Option, +} + +impl BaseOrgBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`BaseOrg`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](BaseOrgBuilder::id) + pub fn build(self) -> Result { + Ok(BaseOrg { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-model/allof/src/base_org_metadata.rs b/seed/rust-model/allof/src/base_org_metadata.rs new file mode 100644 index 000000000000..797f4434eb49 --- /dev/null +++ b/seed/rust-model/allof/src/base_org_metadata.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct BaseOrgMetadata { + /// Deployment region from BaseOrg. + #[serde(default)] + pub region: String, + /// Subscription tier. + #[serde(skip_serializing_if = "Option::is_none")] + pub tier: Option, +} + +impl BaseOrgMetadata { + pub fn builder() -> BaseOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct BaseOrgMetadataBuilder { + region: Option, + tier: Option, +} + +impl BaseOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn tier(mut self, value: impl Into) -> Self { + self.tier = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`BaseOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](BaseOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(BaseOrgMetadata { + region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, + tier: self.tier, + }) + } +} diff --git a/seed/rust-model/allof/src/combined_entity.rs b/seed/rust-model/allof/src/combined_entity.rs new file mode 100644 index 000000000000..0b567ccb3ee5 --- /dev/null +++ b/seed/rust-model/allof/src/combined_entity.rs @@ -0,0 +1,65 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct CombinedEntity { + pub status: CombinedEntityStatus, + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Identifiable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, +} + +impl CombinedEntity { + pub fn builder() -> CombinedEntityBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct CombinedEntityBuilder { + status: Option, + id: Option, + name: Option, + summary: Option, +} + +impl CombinedEntityBuilder { + pub fn status(mut self, value: CombinedEntityStatus) -> Self { + self.status = Some(value); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`CombinedEntity`]. + /// This method will fail if any of the following fields are not set: + /// - [`status`](CombinedEntityBuilder::status) + /// - [`id`](CombinedEntityBuilder::id) + pub fn build(self) -> Result { + Ok(CombinedEntity { + status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + summary: self.summary, + }) + } +} diff --git a/seed/rust-model/allof/src/combined_entity_status.rs b/seed/rust-model/allof/src/combined_entity_status.rs new file mode 100644 index 000000000000..f46cb23eecb2 --- /dev/null +++ b/seed/rust-model/allof/src/combined_entity_status.rs @@ -0,0 +1,42 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CombinedEntityStatus { + Active, + Archived, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for CombinedEntityStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Archived => serializer.serialize_str("archived"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for CombinedEntityStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "archived" => Ok(Self::Archived), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for CombinedEntityStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Archived => write!(f, "archived"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-model/allof/src/describable.rs b/seed/rust-model/allof/src/describable.rs new file mode 100644 index 000000000000..6a8a527bd844 --- /dev/null +++ b/seed/rust-model/allof/src/describable.rs @@ -0,0 +1,44 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Describable { + /// Display name from Describable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A short summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, +} + +impl Describable { + pub fn builder() -> DescribableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DescribableBuilder { + name: Option, + summary: Option, +} + +impl DescribableBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn summary(mut self, value: impl Into) -> Self { + self.summary = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Describable`]. + pub fn build(self) -> Result { + Ok(Describable { + name: self.name, + summary: self.summary, + }) + } +} diff --git a/seed/rust-model/allof/src/detailed_org.rs b/seed/rust-model/allof/src/detailed_org.rs new file mode 100644 index 000000000000..fac068d8a2ee --- /dev/null +++ b/seed/rust-model/allof/src/detailed_org.rs @@ -0,0 +1,33 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrg { + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl DetailedOrg { + pub fn builder() -> DetailedOrgBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgBuilder { + metadata: Option, +} + +impl DetailedOrgBuilder { + pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`DetailedOrg`]. + pub fn build(self) -> Result { + Ok(DetailedOrg { + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-model/allof/src/detailed_org_metadata.rs b/seed/rust-model/allof/src/detailed_org_metadata.rs new file mode 100644 index 000000000000..06ec63051131 --- /dev/null +++ b/seed/rust-model/allof/src/detailed_org_metadata.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct DetailedOrgMetadata { + /// Deployment region from DetailedOrg. + #[serde(default)] + pub region: String, + /// Custom domain name. + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, +} + +impl DetailedOrgMetadata { + pub fn builder() -> DetailedOrgMetadataBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct DetailedOrgMetadataBuilder { + region: Option, + domain: Option, +} + +impl DetailedOrgMetadataBuilder { + pub fn region(mut self, value: impl Into) -> Self { + self.region = Some(value.into()); + self + } + + pub fn domain(mut self, value: impl Into) -> Self { + self.domain = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. + /// This method will fail if any of the following fields are not set: + /// - [`region`](DetailedOrgMetadataBuilder::region) + pub fn build(self) -> Result { + Ok(DetailedOrgMetadata { + region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, + domain: self.domain, + }) + } +} diff --git a/seed/rust-model/allof/src/error.rs b/seed/rust-model/allof/src/error.rs new file mode 100644 index 000000000000..0966ed354a0f --- /dev/null +++ b/seed/rust-model/allof/src/error.rs @@ -0,0 +1,19 @@ +/// Error returned when a required field was not set on a builder. +#[derive(Debug)] +pub struct BuildError { + field: &'static str, +} + +impl BuildError { + pub fn missing_field(field: &'static str) -> Self { + Self { field } + } +} + +impl std::fmt::Display for BuildError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "`{}` was not set but is required", self.field) + } +} + +impl std::error::Error for BuildError {} diff --git a/seed/rust-model/allof/src/identifiable.rs b/seed/rust-model/allof/src/identifiable.rs new file mode 100644 index 000000000000..58fb3eb70734 --- /dev/null +++ b/seed/rust-model/allof/src/identifiable.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Identifiable { + /// Unique identifier. + #[serde(default)] + pub id: String, + /// Display name from Identifiable. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +impl Identifiable { + pub fn builder() -> IdentifiableBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct IdentifiableBuilder { + id: Option, + name: Option, +} + +impl IdentifiableBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`Identifiable`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](IdentifiableBuilder::id) + pub fn build(self) -> Result { + Ok(Identifiable { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name, + }) + } +} diff --git a/seed/rust-model/allof/src/lib.rs b/seed/rust-model/allof/src/lib.rs new file mode 100644 index 000000000000..077ee6dee849 --- /dev/null +++ b/seed/rust-model/allof/src/lib.rs @@ -0,0 +1,7 @@ +//! Generated models by Fern + +pub mod error; + +pub mod types; + +pub use types::*; diff --git a/seed/rust-model/allof/src/mod.rs b/seed/rust-model/allof/src/mod.rs new file mode 100644 index 000000000000..564b9380a75b --- /dev/null +++ b/seed/rust-model/allof/src/mod.rs @@ -0,0 +1,54 @@ +//! Request and response types for the allOf Composition +//! +//! This module contains all data structures used for API communication, +//! including request bodies, response types, and shared models. +//! +//! ## Type Categories +//! +//! - **Request/Response Types**: 6 types for API operations +//! - **Model Types**: 15 types for data representation + +pub mod paginated_result; +pub mod paging_cursors; +pub mod rule_execution_context; +pub mod audit_info; +pub mod rule_type; +pub mod rule_type_search_response; +pub mod user; +pub mod user_search_response; +pub mod rule_response_status; +pub mod rule_response; +pub mod identifiable; +pub mod describable; +pub mod combined_entity_status; +pub mod combined_entity; +pub mod base_org_metadata; +pub mod base_org; +pub mod detailed_org_metadata; +pub mod detailed_org; +pub mod organization; +pub mod rule_create_request; +pub mod search_rule_types_query_request; + +pub use paginated_result::PaginatedResult; +pub use paging_cursors::PagingCursors; +pub use rule_execution_context::RuleExecutionContext; +pub use audit_info::AuditInfo; +pub use rule_type::RuleType; +pub use rule_type_search_response::RuleTypeSearchResponse; +pub use user::User; +pub use user_search_response::UserSearchResponse; +pub use rule_response_status::RuleResponseStatus; +pub use rule_response::RuleResponse; +pub use identifiable::Identifiable; +pub use describable::Describable; +pub use combined_entity_status::CombinedEntityStatus; +pub use combined_entity::CombinedEntity; +pub use base_org_metadata::BaseOrgMetadata; +pub use base_org::BaseOrg; +pub use detailed_org_metadata::DetailedOrgMetadata; +pub use detailed_org::DetailedOrg; +pub use organization::Organization; +pub use rule_create_request::RuleCreateRequest; +pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; + diff --git a/seed/rust-model/allof/src/organization.rs b/seed/rust-model/allof/src/organization.rs new file mode 100644 index 000000000000..ec2ed3f22d66 --- /dev/null +++ b/seed/rust-model/allof/src/organization.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct Organization { + #[serde(default)] + pub name: String, + #[serde(default)] + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl Organization { + pub fn builder() -> OrganizationBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct OrganizationBuilder { + name: Option, + id: Option, + metadata: Option, +} + +impl OrganizationBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { + self.metadata = Some(value); + self + } + + /// Consumes the builder and constructs a [`Organization`]. + /// This method will fail if any of the following fields are not set: + /// - [`name`](OrganizationBuilder::name) + /// - [`id`](OrganizationBuilder::id) + pub fn build(self) -> Result { + Ok(Organization { + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + metadata: self.metadata, + }) + } +} diff --git a/seed/rust-model/allof/src/paginated_result.rs b/seed/rust-model/allof/src/paginated_result.rs new file mode 100644 index 000000000000..d91039ad7253 --- /dev/null +++ b/seed/rust-model/allof/src/paginated_result.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct PaginatedResult { + #[serde(default)] + pub paging: PagingCursors, + /// Current page of results from the requested resource. + #[serde(default)] + pub results: Vec, +} + +impl PaginatedResult { + pub fn builder() -> PaginatedResultBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PaginatedResultBuilder { + paging: Option, + results: Option>, +} + +impl PaginatedResultBuilder { + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + /// Consumes the builder and constructs a [`PaginatedResult`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](PaginatedResultBuilder::paging) + /// - [`results`](PaginatedResultBuilder::results) + pub fn build(self) -> Result { + Ok(PaginatedResult { + paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, + results: self.results.ok_or_else(|| BuildError::missing_field("results"))?, + }) + } +} diff --git a/seed/rust-model/allof/src/paging_cursors.rs b/seed/rust-model/allof/src/paging_cursors.rs new file mode 100644 index 000000000000..a3a785119b57 --- /dev/null +++ b/seed/rust-model/allof/src/paging_cursors.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct PagingCursors { + /// Cursor for the next page of results. + #[serde(default)] + pub next: String, + /// Cursor for the previous page of results. + #[serde(skip_serializing_if = "Option::is_none")] + pub previous: Option, +} + +impl PagingCursors { + pub fn builder() -> PagingCursorsBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct PagingCursorsBuilder { + next: Option, + previous: Option, +} + +impl PagingCursorsBuilder { + pub fn next(mut self, value: impl Into) -> Self { + self.next = Some(value.into()); + self + } + + pub fn previous(mut self, value: impl Into) -> Self { + self.previous = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`PagingCursors`]. + /// This method will fail if any of the following fields are not set: + /// - [`next`](PagingCursorsBuilder::next) + pub fn build(self) -> Result { + Ok(PagingCursors { + next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, + previous: self.previous, + }) + } +} diff --git a/seed/rust-model/allof/src/rule_create_request.rs b/seed/rust-model/allof/src/rule_create_request.rs new file mode 100644 index 000000000000..3401318222ee --- /dev/null +++ b/seed/rust-model/allof/src/rule_create_request.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleCreateRequest { + #[serde(default)] + pub name: String, + #[serde(rename = "executionContext")] + pub execution_context: RuleExecutionContext, +} + +impl RuleCreateRequest { + pub fn builder() -> RuleCreateRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleCreateRequestBuilder { + name: Option, + execution_context: Option, +} + +impl RuleCreateRequestBuilder { + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleCreateRequest`]. + /// This method will fail if any of the following fields are not set: + /// - [`name`](RuleCreateRequestBuilder::name) + /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) + pub fn build(self) -> Result { + Ok(RuleCreateRequest { + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + execution_context: self.execution_context.ok_or_else(|| BuildError::missing_field("execution_context"))?, + }) + } +} + diff --git a/seed/rust-model/allof/src/rule_execution_context.rs b/seed/rust-model/allof/src/rule_execution_context.rs new file mode 100644 index 000000000000..7f8fa5bd56e1 --- /dev/null +++ b/seed/rust-model/allof/src/rule_execution_context.rs @@ -0,0 +1,47 @@ +pub use crate::prelude::*; + +/// Execution environment for a rule. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleExecutionContext { + Prod, + Staging, + Dev, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleExecutionContext { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Prod => serializer.serialize_str("prod"), + Self::Staging => serializer.serialize_str("staging"), + Self::Dev => serializer.serialize_str("dev"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleExecutionContext { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "prod" => Ok(Self::Prod), + "staging" => Ok(Self::Staging), + "dev" => Ok(Self::Dev), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleExecutionContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Prod => write!(f, "prod"), + Self::Staging => write!(f, "staging"), + Self::Dev => write!(f, "dev"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-model/allof/src/rule_response.rs b/seed/rust-model/allof/src/rule_response.rs new file mode 100644 index 000000000000..53159e627a3f --- /dev/null +++ b/seed/rust-model/allof/src/rule_response.rs @@ -0,0 +1,74 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct RuleResponse { + #[serde(flatten)] + pub audit_info_fields: AuditInfo, + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + pub status: RuleResponseStatus, + #[serde(rename = "executionContext")] + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_context: Option, +} + +impl RuleResponse { + pub fn builder() -> RuleResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleResponseBuilder { + audit_info_fields: Option, + id: Option, + name: Option, + status: Option, + execution_context: Option, +} + +impl RuleResponseBuilder { + pub fn audit_info_fields(mut self, value: AuditInfo) -> Self { + self.audit_info_fields = Some(value); + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn status(mut self, value: RuleResponseStatus) -> Self { + self.status = Some(value); + self + } + + pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { + self.execution_context = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`audit_info_fields`](RuleResponseBuilder::audit_info_fields) + /// - [`id`](RuleResponseBuilder::id) + /// - [`name`](RuleResponseBuilder::name) + /// - [`status`](RuleResponseBuilder::status) + pub fn build(self) -> Result { + Ok(RuleResponse { + audit_info_fields: self.audit_info_fields.ok_or_else(|| BuildError::missing_field("audit_info_fields"))?, + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, + execution_context: self.execution_context, + }) + } +} diff --git a/seed/rust-model/allof/src/rule_response_status.rs b/seed/rust-model/allof/src/rule_response_status.rs new file mode 100644 index 000000000000..5d2462ecd349 --- /dev/null +++ b/seed/rust-model/allof/src/rule_response_status.rs @@ -0,0 +1,46 @@ +pub use crate::prelude::*; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RuleResponseStatus { + Active, + Inactive, + Draft, + /// This variant is used for forward compatibility. + /// If the server sends a value not recognized by the current SDK version, + /// it will be captured here with the raw string value. + __Unknown(String), +} +impl Serialize for RuleResponseStatus { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Active => serializer.serialize_str("active"), + Self::Inactive => serializer.serialize_str("inactive"), + Self::Draft => serializer.serialize_str("draft"), + Self::__Unknown(val) => serializer.serialize_str(val), + } + } +} + +impl<'de> Deserialize<'de> for RuleResponseStatus { + fn deserialize>(deserializer: D) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "active" => Ok(Self::Active), + "inactive" => Ok(Self::Inactive), + "draft" => Ok(Self::Draft), + _ => Ok(Self::__Unknown(value)), + } + } +} + +impl fmt::Display for RuleResponseStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Active => write!(f, "active"), + Self::Inactive => write!(f, "inactive"), + Self::Draft => write!(f, "draft"), + Self::__Unknown(val) => write!(f, "{}", val), + } + } +} diff --git a/seed/rust-model/allof/src/rule_type.rs b/seed/rust-model/allof/src/rule_type.rs new file mode 100644 index 000000000000..69f0317c1923 --- /dev/null +++ b/seed/rust-model/allof/src/rule_type.rs @@ -0,0 +1,54 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleType { + #[serde(default)] + pub id: String, + #[serde(default)] + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +impl RuleType { + pub fn builder() -> RuleTypeBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeBuilder { + id: Option, + name: Option, + description: Option, +} + +impl RuleTypeBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn name(mut self, value: impl Into) -> Self { + self.name = Some(value.into()); + self + } + + pub fn description(mut self, value: impl Into) -> Self { + self.description = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`RuleType`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](RuleTypeBuilder::id) + /// - [`name`](RuleTypeBuilder::name) + pub fn build(self) -> Result { + Ok(RuleType { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, + description: self.description, + }) + } +} diff --git a/seed/rust-model/allof/src/rule_type_search_response.rs b/seed/rust-model/allof/src/rule_type_search_response.rs new file mode 100644 index 000000000000..9d9cd3e185c3 --- /dev/null +++ b/seed/rust-model/allof/src/rule_type_search_response.rs @@ -0,0 +1,45 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct RuleTypeSearchResponse { + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, + #[serde(default)] + pub paging: PagingCursors, +} + +impl RuleTypeSearchResponse { + pub fn builder() -> RuleTypeSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct RuleTypeSearchResponseBuilder { + results: Option>, + paging: Option, +} + +impl RuleTypeSearchResponseBuilder { + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](RuleTypeSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(RuleTypeSearchResponse { + results: self.results, + paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, + }) + } +} diff --git a/seed/rust-model/allof/src/search_rule_types_query_request.rs b/seed/rust-model/allof/src/search_rule_types_query_request.rs new file mode 100644 index 000000000000..ae31f5e24826 --- /dev/null +++ b/seed/rust-model/allof/src/search_rule_types_query_request.rs @@ -0,0 +1,35 @@ +pub use crate::prelude::*; + +/// Query parameters for searchRuleTypes +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct SearchRuleTypesQueryRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, +} + +impl SearchRuleTypesQueryRequest { + pub fn builder() -> SearchRuleTypesQueryRequestBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct SearchRuleTypesQueryRequestBuilder { + query: Option, +} + +impl SearchRuleTypesQueryRequestBuilder { + pub fn query(mut self, value: impl Into) -> Self { + self.query = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. + pub fn build(self) -> Result { + Ok(SearchRuleTypesQueryRequest { + query: self.query, + }) + } +} + diff --git a/seed/rust-model/allof/src/user.rs b/seed/rust-model/allof/src/user.rs new file mode 100644 index 000000000000..347520d397a2 --- /dev/null +++ b/seed/rust-model/allof/src/user.rs @@ -0,0 +1,45 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct User { + #[serde(default)] + pub id: String, + #[serde(default)] + pub email: String, +} + +impl User { + pub fn builder() -> UserBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserBuilder { + id: Option, + email: Option, +} + +impl UserBuilder { + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn email(mut self, value: impl Into) -> Self { + self.email = Some(value.into()); + self + } + + /// Consumes the builder and constructs a [`User`]. + /// This method will fail if any of the following fields are not set: + /// - [`id`](UserBuilder::id) + /// - [`email`](UserBuilder::email) + pub fn build(self) -> Result { + Ok(User { + id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, + email: self.email.ok_or_else(|| BuildError::missing_field("email"))?, + }) + } +} diff --git a/seed/rust-model/allof/src/user_search_response.rs b/seed/rust-model/allof/src/user_search_response.rs new file mode 100644 index 000000000000..820735acecb4 --- /dev/null +++ b/seed/rust-model/allof/src/user_search_response.rs @@ -0,0 +1,45 @@ +pub use crate::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] +pub struct UserSearchResponse { + /// Current page of results from the requested resource. + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option>, + #[serde(default)] + pub paging: PagingCursors, +} + +impl UserSearchResponse { + pub fn builder() -> UserSearchResponseBuilder { + ::default() + } +} + +#[derive(Clone, PartialEq, Default, Debug)] +#[non_exhaustive] +pub struct UserSearchResponseBuilder { + results: Option>, + paging: Option, +} + +impl UserSearchResponseBuilder { + pub fn results(mut self, value: Vec) -> Self { + self.results = Some(value); + self + } + + pub fn paging(mut self, value: PagingCursors) -> Self { + self.paging = Some(value); + self + } + + /// Consumes the builder and constructs a [`UserSearchResponse`]. + /// This method will fail if any of the following fields are not set: + /// - [`paging`](UserSearchResponseBuilder::paging) + pub fn build(self) -> Result { + Ok(UserSearchResponse { + results: self.results, + paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, + }) + } +} diff --git a/seed/rust-sdk/allof-inline/.fern/metadata.json b/seed/rust-sdk/allof-inline/.fern/metadata.json index 479cc3770032..8cc2145de5c0 100644 --- a/seed/rust-sdk/allof-inline/.fern/metadata.json +++ b/seed/rust-sdk/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-rust-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/rust-sdk/allof/.fern/metadata.json b/seed/rust-sdk/allof/.fern/metadata.json index 479cc3770032..8cc2145de5c0 100644 --- a/seed/rust-sdk/allof/.fern/metadata.json +++ b/seed/rust-sdk/allof/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-rust-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/.fern/metadata.json b/seed/swift-sdk/allof-inline/.fern/metadata.json index c78c560bfd6a..56c0dd230a46 100644 --- a/seed/swift-sdk/allof-inline/.fern/metadata.json +++ b/seed/swift-sdk/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-swift-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "generatorConfig": {}, "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" diff --git a/seed/swift-sdk/allof/.fern/metadata.json b/seed/swift-sdk/allof/.fern/metadata.json index c78c560bfd6a..56c0dd230a46 100644 --- a/seed/swift-sdk/allof/.fern/metadata.json +++ b/seed/swift-sdk/allof/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-swift-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "generatorConfig": {}, "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" diff --git a/seed/ts-sdk/allof-inline/.fern/metadata.json b/seed/ts-sdk/allof-inline/.fern/metadata.json index 9da357e1fedb..7b69b32c1f15 100644 --- a/seed/ts-sdk/allof-inline/.fern/metadata.json +++ b/seed/ts-sdk/allof-inline/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } diff --git a/seed/ts-sdk/allof/.fern/metadata.json b/seed/ts-sdk/allof/.fern/metadata.json index 9da357e1fedb..7b69b32c1f15 100644 --- a/seed/ts-sdk/allof/.fern/metadata.json +++ b/seed/ts-sdk/allof/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "DUMMY", "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "latest", + "generatorVersion": "local", "originGitCommit": "DUMMY", "sdkVersion": "0.0.1" } From ba0d10a910632a2f8172486616c8c1b279ee7dee Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:43:53 +0000 Subject: [PATCH 13/29] fix: update convertIRtoJsonSchema snapshots for new allof types Co-Authored-By: bot_apk --- .../allof-inline/type__BaseOrg.json | 46 +++++++++++++++++ .../allof-inline/type__BaseOrgMetadata.json | 23 +++++++++ .../allof-inline/type__DetailedOrg.json | 40 +++++++++++++++ .../type__DetailedOrgMetadata.json | 23 +++++++++ .../allof-inline/type__Organization.json | 50 +++++++++++++++++++ .../type__OrganizationMetadata.json | 23 +++++++++ .../__snapshots__/allof/type__BaseOrg.json | 46 +++++++++++++++++ .../allof/type__BaseOrgMetadata.json | 23 +++++++++ .../allof/type__DetailedOrg.json | 40 +++++++++++++++ .../allof/type__DetailedOrgMetadata.json | 23 +++++++++ .../allof/type__Organization.json | 50 +++++++++++++++++++ 11 files changed, 387 insertions(+) create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrg.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrgMetadata.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrg.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrgMetadata.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Organization.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__OrganizationMetadata.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrg.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrgMetadata.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrg.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrgMetadata.json create mode 100644 packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Organization.json diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrg.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrg.json new file mode 100644 index 000000000000..f8fc1f538fc5 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrg.json @@ -0,0 +1,46 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "metadata": { + "oneOf": [ + { + "$ref": "#/definitions/BaseOrgMetadata" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id" + ], + "additionalProperties": false, + "definitions": { + "BaseOrgMetadata": { + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "tier": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrgMetadata.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrgMetadata.json new file mode 100644 index 000000000000..edae048bcc87 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__BaseOrgMetadata.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "tier": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrg.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrg.json new file mode 100644 index 000000000000..38508701f690 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrg.json @@ -0,0 +1,40 @@ +{ + "type": "object", + "properties": { + "metadata": { + "oneOf": [ + { + "$ref": "#/definitions/DetailedOrgMetadata" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "DetailedOrgMetadata": { + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "domain": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrgMetadata.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrgMetadata.json new file mode 100644 index 000000000000..33ffe43bc4ce --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__DetailedOrgMetadata.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "domain": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Organization.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Organization.json new file mode 100644 index 000000000000..e698e1988e28 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__Organization.json @@ -0,0 +1,50 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "metadata": { + "oneOf": [ + { + "$ref": "#/definitions/OrganizationMetadata" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false, + "definitions": { + "OrganizationMetadata": { + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "domain": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__OrganizationMetadata.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__OrganizationMetadata.json new file mode 100644 index 000000000000..33ffe43bc4ce --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof-inline/type__OrganizationMetadata.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "domain": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrg.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrg.json new file mode 100644 index 000000000000..f8fc1f538fc5 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrg.json @@ -0,0 +1,46 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "metadata": { + "oneOf": [ + { + "$ref": "#/definitions/BaseOrgMetadata" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id" + ], + "additionalProperties": false, + "definitions": { + "BaseOrgMetadata": { + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "tier": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrgMetadata.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrgMetadata.json new file mode 100644 index 000000000000..edae048bcc87 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__BaseOrgMetadata.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "tier": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrg.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrg.json new file mode 100644 index 000000000000..38508701f690 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrg.json @@ -0,0 +1,40 @@ +{ + "type": "object", + "properties": { + "metadata": { + "oneOf": [ + { + "$ref": "#/definitions/DetailedOrgMetadata" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "DetailedOrgMetadata": { + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "domain": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrgMetadata.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrgMetadata.json new file mode 100644 index 000000000000..33ffe43bc4ce --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__DetailedOrgMetadata.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "domain": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Organization.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Organization.json new file mode 100644 index 000000000000..ce41ccc70d3a --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/allof/type__Organization.json @@ -0,0 +1,50 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "metadata": { + "oneOf": [ + { + "$ref": "#/definitions/BaseOrgMetadata" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "name", + "id" + ], + "additionalProperties": false, + "definitions": { + "BaseOrgMetadata": { + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "tier": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "region" + ], + "additionalProperties": false + } + } +} \ No newline at end of file From aca9275ddf44b1d79fe3fa2edb0cb7900c5c5ce7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:45:50 +0000 Subject: [PATCH 14/29] fix: thread visitedRefs across recursive SchemaConverter calls for cross-schema cycle detection Co-Authored-By: bot_apk --- .../src/converters/schema/SchemaConverter.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index 522c423bb058..ac3d92d0a268 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -31,6 +31,7 @@ export declare namespace SchemaConverter { schema: OpenAPIV3_1.SchemaObject; inlined?: boolean; nameOverride?: string; + visitedRefs?: Set; } export interface ConvertedSchema { @@ -51,13 +52,15 @@ export class SchemaConverter extends AbstractConverter; - constructor({ context, breadcrumbs, schema, id, inlined = false, nameOverride }: SchemaConverter.Args) { + constructor({ context, breadcrumbs, schema, id, inlined = false, nameOverride, visitedRefs }: SchemaConverter.Args) { super({ context, breadcrumbs }); this.schema = schema; this.id = id; this.inlined = inlined; this.nameOverride = nameOverride; + this.visitedRefs = visitedRefs ?? new Set(); this.audiences = this.context.getAudiences({ operation: this.schema, @@ -179,7 +182,8 @@ export class SchemaConverter extends AbstractConverter = {}; - const resolvedRefs = new Set(); + const resolvedRefs = new Set(this.visitedRefs); let hasCycle = false; for (const allOfSchema of this.schema.allOf ?? []) { let schemaToMerge: OpenAPIV3_1.SchemaObject; @@ -283,7 +287,8 @@ export class SchemaConverter extends AbstractConverter Date: Fri, 10 Apr 2026 15:48:07 +0000 Subject: [PATCH 15/29] fix: format SchemaConverter.ts constructor for biome Co-Authored-By: bot_apk --- .../src/converters/schema/SchemaConverter.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index ac3d92d0a268..d369ca07e8d5 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -54,7 +54,15 @@ export class SchemaConverter extends AbstractConverter; - constructor({ context, breadcrumbs, schema, id, inlined = false, nameOverride, visitedRefs }: SchemaConverter.Args) { + constructor({ + context, + breadcrumbs, + schema, + id, + inlined = false, + nameOverride, + visitedRefs + }: SchemaConverter.Args) { super({ context, breadcrumbs }); this.schema = schema; this.id = id; From 8e673f2557aa97bea0d7631add44a24274d8b16c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:55:26 +0000 Subject: [PATCH 16/29] fix: update ir-generator-tests test-definitions for allof-inline Co-Authored-By: bot_apk --- .../test-definitions/allof-inline.json | 618 ++ .../test-definitions/allof-inline.json | 7030 ++++++++++------- 2 files changed, 4967 insertions(+), 2681 deletions(-) diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json index e57b3935c3d8..e4219645fe14 100644 --- a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof-inline.json @@ -1684,6 +1684,582 @@ ], "extends": null, "additionalProperties": false + }, + "type_:BaseOrgMetadata": { + "type": "object", + "declaration": { + "name": { + "originalName": "BaseOrgMetadata", + "camelCase": { + "unsafeName": "baseOrgMetadata", + "safeName": "baseOrgMetadata" + }, + "snakeCase": { + "unsafeName": "base_org_metadata", + "safeName": "base_org_metadata" + }, + "screamingSnakeCase": { + "unsafeName": "BASE_ORG_METADATA", + "safeName": "BASE_ORG_METADATA" + }, + "pascalCase": { + "unsafeName": "BaseOrgMetadata", + "safeName": "BaseOrgMetadata" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "region", + "name": { + "originalName": "region", + "camelCase": { + "unsafeName": "region", + "safeName": "region" + }, + "snakeCase": { + "unsafeName": "region", + "safeName": "region" + }, + "screamingSnakeCase": { + "unsafeName": "REGION", + "safeName": "REGION" + }, + "pascalCase": { + "unsafeName": "Region", + "safeName": "Region" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "tier", + "name": { + "originalName": "tier", + "camelCase": { + "unsafeName": "tier", + "safeName": "tier" + }, + "snakeCase": { + "unsafeName": "tier", + "safeName": "tier" + }, + "screamingSnakeCase": { + "unsafeName": "TIER", + "safeName": "TIER" + }, + "pascalCase": { + "unsafeName": "Tier", + "safeName": "Tier" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:BaseOrg": { + "type": "object", + "declaration": { + "name": { + "originalName": "BaseOrg", + "camelCase": { + "unsafeName": "baseOrg", + "safeName": "baseOrg" + }, + "snakeCase": { + "unsafeName": "base_org", + "safeName": "base_org" + }, + "screamingSnakeCase": { + "unsafeName": "BASE_ORG", + "safeName": "BASE_ORG" + }, + "pascalCase": { + "unsafeName": "BaseOrg", + "safeName": "BaseOrg" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:BaseOrgMetadata" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:DetailedOrgMetadata": { + "type": "object", + "declaration": { + "name": { + "originalName": "DetailedOrgMetadata", + "camelCase": { + "unsafeName": "detailedOrgMetadata", + "safeName": "detailedOrgMetadata" + }, + "snakeCase": { + "unsafeName": "detailed_org_metadata", + "safeName": "detailed_org_metadata" + }, + "screamingSnakeCase": { + "unsafeName": "DETAILED_ORG_METADATA", + "safeName": "DETAILED_ORG_METADATA" + }, + "pascalCase": { + "unsafeName": "DetailedOrgMetadata", + "safeName": "DetailedOrgMetadata" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "region", + "name": { + "originalName": "region", + "camelCase": { + "unsafeName": "region", + "safeName": "region" + }, + "snakeCase": { + "unsafeName": "region", + "safeName": "region" + }, + "screamingSnakeCase": { + "unsafeName": "REGION", + "safeName": "REGION" + }, + "pascalCase": { + "unsafeName": "Region", + "safeName": "Region" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "domain", + "name": { + "originalName": "domain", + "camelCase": { + "unsafeName": "domain", + "safeName": "domain" + }, + "snakeCase": { + "unsafeName": "domain", + "safeName": "domain" + }, + "screamingSnakeCase": { + "unsafeName": "DOMAIN", + "safeName": "DOMAIN" + }, + "pascalCase": { + "unsafeName": "Domain", + "safeName": "Domain" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:DetailedOrg": { + "type": "object", + "declaration": { + "name": { + "originalName": "DetailedOrg", + "camelCase": { + "unsafeName": "detailedOrg", + "safeName": "detailedOrg" + }, + "snakeCase": { + "unsafeName": "detailed_org", + "safeName": "detailed_org" + }, + "screamingSnakeCase": { + "unsafeName": "DETAILED_ORG", + "safeName": "DETAILED_ORG" + }, + "pascalCase": { + "unsafeName": "DetailedOrg", + "safeName": "DetailedOrg" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:DetailedOrgMetadata" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:OrganizationMetadata": { + "type": "object", + "declaration": { + "name": { + "originalName": "OrganizationMetadata", + "camelCase": { + "unsafeName": "organizationMetadata", + "safeName": "organizationMetadata" + }, + "snakeCase": { + "unsafeName": "organization_metadata", + "safeName": "organization_metadata" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_METADATA", + "safeName": "ORGANIZATION_METADATA" + }, + "pascalCase": { + "unsafeName": "OrganizationMetadata", + "safeName": "OrganizationMetadata" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "region", + "name": { + "originalName": "region", + "camelCase": { + "unsafeName": "region", + "safeName": "region" + }, + "snakeCase": { + "unsafeName": "region", + "safeName": "region" + }, + "screamingSnakeCase": { + "unsafeName": "REGION", + "safeName": "REGION" + }, + "pascalCase": { + "unsafeName": "Region", + "safeName": "Region" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "domain", + "name": { + "originalName": "domain", + "camelCase": { + "unsafeName": "domain", + "safeName": "domain" + }, + "snakeCase": { + "unsafeName": "domain", + "safeName": "domain" + }, + "screamingSnakeCase": { + "unsafeName": "DOMAIN", + "safeName": "DOMAIN" + }, + "pascalCase": { + "unsafeName": "Domain", + "safeName": "Domain" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Organization": { + "type": "object", + "declaration": { + "name": { + "originalName": "Organization", + "camelCase": { + "unsafeName": "organization", + "safeName": "organization" + }, + "snakeCase": { + "unsafeName": "organization", + "safeName": "organization" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION", + "safeName": "ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "Organization", + "safeName": "Organization" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:OrganizationMetadata" + } + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false } }, "headers": [], @@ -2017,6 +2593,48 @@ "type": "json" }, "examples": null + }, + "endpoint_.getOrganization": { + "auth": null, + "declaration": { + "name": { + "originalName": "getOrganization", + "camelCase": { + "unsafeName": "getOrganization", + "safeName": "getOrganization" + }, + "snakeCase": { + "unsafeName": "get_organization", + "safeName": "get_organization" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION", + "safeName": "GET_ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "GetOrganization", + "safeName": "GetOrganization" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/organizations" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null } }, "pathParameters": [], diff --git a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json index 2acecdd505c6..8e2274e0813e 100644 --- a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json +++ b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof-inline.json @@ -1326,134 +1326,2540 @@ "v2Examples": null, "availability": null, "docs": null - } - }, - "errors": {}, - "services": { - "service_": { - "availability": null, + }, + "type_:BaseOrgMetadata": { + "inline": true, "name": { + "name": "BaseOrgMetadata", "fernFilepath": { "allParts": [], "packagePath": [], "file": null - } + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata" }, - "displayName": null, - "basePath": { - "head": "", - "parts": [] + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "region", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Deployment region from BaseOrg." + }, + { + "name": "tier", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Subscription tier." + } + ], + "extra-properties": false, + "extendedProperties": [] }, - "headers": [], - "pathParameters": [], + "referencedTypes": [], "encoding": { "json": {}, "proto": null }, - "transport": { - "type": "http" + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:BaseOrg": { + "inline": null, + "name": { + "name": "BaseOrg", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrg" }, - "endpoints": [ - { - "id": "endpoint_.searchRuleTypes", - "name": "searchRuleTypes", - "displayName": "Search rule types with paginated results", - "auth": false, - "security": null, - "idempotent": false, - "baseUrl": null, - "v2BaseUrls": null, - "method": "GET", - "basePath": null, - "path": { - "head": "/rule-types", - "parts": [] - }, - "fullPath": { - "head": "rule-types", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": "query", - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null } - }, - "allowMultiple": false, - "clientDefault": null, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - }, - "explode": null, - "availability": null, - "docs": null - } - ], - "headers": [], - "requestBody": null, - "v2RequestBodies": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": "SearchRuleTypesRequest", - "bodyKey": "body", - "includePathParameters": false, - "onlyPathParameters": false + } }, - "requestParameterName": "request", - "streamParameter": null + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null }, - "response": { - "body": { - "type": "json", - "value": { - "type": "response", - "responseBodyType": { + { + "name": "metadata", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { "_type": "named", - "name": "RuleTypeSearchResponse", + "name": "BaseOrgMetadata", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:BaseOrgMetadata", "default": null, "inline": null - }, - "docs": "Paginated list of rule types", - "v2Examples": null + } } }, - "status-code": 200, - "isWildcardStatusCode": null, - "docs": "Paginated list of rule types" + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:BaseOrgMetadata" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:DetailedOrgMetadata": { + "inline": true, + "name": { + "name": "DetailedOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:DetailedOrgMetadata" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "region", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Deployment region from DetailedOrg." }, - "v2Responses": null, - "errors": [], - "userSpecifiedExamples": [ - { - "example": { - "id": "b6434d4c", - "name": null, + { + "name": "domain", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Custom domain name." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:DetailedOrg": { + "inline": null, + "name": { + "name": "DetailedOrg", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:DetailedOrg" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "metadata", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": "DetailedOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:DetailedOrgMetadata", + "default": null, + "inline": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:DetailedOrgMetadata" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:OrganizationMetadata": { + "inline": true, + "name": { + "name": "OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:OrganizationMetadata" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "region", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Deployment region from DetailedOrg." + }, + { + "name": "domain", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Custom domain name." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:Organization": { + "inline": null, + "name": { + "name": "Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Organization" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "metadata", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": "OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:OrganizationMetadata", + "default": null, + "inline": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:OrganizationMetadata" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "displayName": null, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {}, + "proto": null + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_.searchRuleTypes", + "name": "searchRuleTypes", + "displayName": "Search rule types with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/rule-types", + "parts": [] + }, + "fullPath": { + "head": "rule-types", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [ + { + "name": "query", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "allowMultiple": false, + "clientDefault": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "explode": null, + "availability": null, + "docs": null + } + ], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "SearchRuleTypesRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of rule types", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of rule types" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "b6434d4c", + "name": null, + "url": "/rule-types", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "previous", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "results", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "description", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "b2c39ee3", "url": "/rule-types", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "propertyAccess": null + }, + { + "name": "previous", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "propertyAccess": null + }, + { + "name": "results", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + }, + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.createRule", + "name": "createRule", + "displayName": "Create a rule with constrained execution context", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/rules", + "parts": [] + }, + "fullPath": { + "head": "rules", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "inlinedRequestBody", + "name": "RuleCreateRequest", + "extends": [], + "properties": [ + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + }, + { + "name": "executionContext", + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [], + "docs": null, + "v2Examples": null, + "contentType": "application/json" + }, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "RuleCreateRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse", + "default": null, + "inline": null + }, + "docs": "Created rule", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Created rule" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "c1cf878e", + "name": null, + "url": "/rules", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": null + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "createdBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "status", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponseStatus", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "active" + } + }, + "jsonExample": "active" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z", + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "e09bf9ea", + "url": "/rules", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + } + }, + { + "name": "executionContext", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + } + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "createdBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "status", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "active" + }, + "typeName": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus" + } + }, + "jsonExample": "active" + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + } + }, + "jsonExample": { + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z", + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.listUsers", + "name": "listUsers", + "displayName": "List users with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/users", + "parts": [] + }, + "fullPath": { + "head": "users", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of users", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of users" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "55942cbc", + "name": null, + "url": "/users", "rootPathParameters": [], "endpointPathParameters": [], "servicePathParameters": [], @@ -1469,13 +3875,13 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "displayName": null }, "shape": { @@ -1581,13 +3987,13 @@ } }, "originalTypeDeclaration": { - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "displayName": null }, "propertyAccess": null @@ -1609,13 +4015,13 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleType", + "typeId": "type_:User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleType", + "name": "User", "displayName": null }, "shape": { @@ -1636,85 +4042,39 @@ "jsonExample": "id" }, "originalTypeDeclaration": { - "typeId": "type_:RuleType", + "typeId": "type_:User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleType", + "name": "User", "displayName": null }, "propertyAccess": null }, { - "name": "name", + "name": "email", "value": { "shape": { "type": "primitive", "primitive": { "type": "string", "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleType", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleType", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "description", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "description" - } - } - }, - "jsonExample": "description" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } + "original": "email" } } }, - "jsonExample": "description" + "jsonExample": "email" }, "originalTypeDeclaration": { - "typeId": "type_:RuleType", + "typeId": "type_:User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleType", + "name": "User", "displayName": null }, "propertyAccess": null @@ -1725,21 +4085,20 @@ }, "jsonExample": { "id": "id", - "name": "name", - "description": "description" + "email": "email" } } ], "itemType": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -1748,8 +4107,7 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, @@ -1759,14 +4117,14 @@ "_type": "list", "list": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -1777,19 +4135,18 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, "originalTypeDeclaration": { - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "displayName": null }, "propertyAccess": null @@ -1806,8 +4163,7 @@ "results": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] } @@ -1822,8 +4178,8 @@ "autogeneratedExamples": [ { "example": { - "id": "b2c39ee3", - "url": "/rule-types", + "id": "e7c56b0b", + "url": "/users", "name": null, "endpointHeaders": [], "endpointPathParameters": [], @@ -1845,14 +4201,14 @@ { "name": "paging", "originalTypeDeclaration": { - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse" + "typeId": "type_:UserSearchResponse" }, "value": { "shape": { @@ -1956,14 +4312,14 @@ { "name": "results", "originalTypeDeclaration": { - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse" + "typeId": "type_:UserSearchResponse" }, "value": { "shape": { @@ -1985,14 +4341,14 @@ { "name": "id", "originalTypeDeclaration": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" }, "value": { "shape": { @@ -2009,16 +4365,16 @@ "propertyAccess": null }, { - "name": "name", + "name": "email", "originalTypeDeclaration": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" }, "value": { "shape": { @@ -2026,57 +4382,11 @@ "primitive": { "type": "string", "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "propertyAccess": null - }, - { - "name": "description", - "originalTypeDeclaration": { - "name": "RuleType", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleType" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "description" - } - } - }, - "jsonExample": "description" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } + "original": "email" } } }, - "jsonExample": "description" + "jsonExample": "email" }, "propertyAccess": null } @@ -2084,20 +4394,19 @@ "extraProperties": null }, "typeName": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" } }, "jsonExample": { "id": "id", - "name": "name", - "description": "description" + "email": "email" } }, { @@ -2109,14 +4418,14 @@ { "name": "id", "originalTypeDeclaration": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" }, "value": { "shape": { @@ -2130,77 +4439,31 @@ }, "jsonExample": "id" }, - "propertyAccess": null - }, - { - "name": "name", - "originalTypeDeclaration": { - "name": "RuleType", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleType" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "propertyAccess": null - }, - { - "name": "description", - "originalTypeDeclaration": { - "name": "RuleType", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleType" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "description" - } - } - }, - "jsonExample": "description" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } + "propertyAccess": null + }, + { + "name": "email", + "originalTypeDeclaration": { + "name": "User", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" } } }, - "jsonExample": "description" + "jsonExample": "email" }, "propertyAccess": null } @@ -2208,33 +4471,32 @@ "extraProperties": null }, "typeName": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" } }, "jsonExample": { "id": "id", - "name": "name", - "description": "description" + "email": "email" } } ], "itemType": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -2243,13 +4505,11 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" }, { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, @@ -2259,14 +4519,14 @@ "_type": "list", "list": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -2277,13 +4537,11 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" }, { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, @@ -2293,14 +4551,14 @@ "extraProperties": null }, "typeName": { - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse" + "typeId": "type_:UserSearchResponse" } }, "jsonExample": { @@ -2311,13 +4569,11 @@ "results": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" }, { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] } @@ -2340,96 +4596,31 @@ "docs": null }, { - "id": "endpoint_.createRule", - "name": "createRule", - "displayName": "Create a rule with constrained execution context", + "id": "endpoint_.getEntity", + "name": "getEntity", + "displayName": "Get an entity that combines multiple parents", "auth": false, "security": null, "idempotent": false, "baseUrl": null, "v2BaseUrls": null, - "method": "POST", + "method": "GET", "basePath": null, "path": { - "head": "/rules", + "head": "/entities", "parts": [] }, "fullPath": { - "head": "rules", + "head": "entities", "parts": [] }, "pathParameters": [], "allPathParameters": [], "queryParameters": [], "headers": [], - "requestBody": { - "type": "inlinedRequestBody", - "name": "RuleCreateRequest", - "extends": [], - "properties": [ - { - "name": "name", - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - }, - "propertyAccess": null, - "availability": null, - "docs": null - }, - { - "name": "executionContext", - "valueType": { - "_type": "named", - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext", - "default": null, - "inline": null - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - }, - "propertyAccess": null, - "availability": null, - "docs": null - } - ], - "extra-properties": false, - "extendedProperties": [], - "docs": null, - "v2Examples": null, - "contentType": "application/json" - }, + "requestBody": null, "v2RequestBodies": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": "RuleCreateRequest", - "bodyKey": "body", - "includePathParameters": false, - "onlyPathParameters": false - }, - "requestParameterName": "request", - "streamParameter": null - }, + "sdkRequest": null, "response": { "body": { "type": "json", @@ -2437,89 +4628,40 @@ "type": "response", "responseBodyType": { "_type": "named", - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "default": null, "inline": null }, - "docs": "Created rule", + "docs": "An entity with properties from multiple parents", "v2Examples": null } }, "status-code": 200, "isWildcardStatusCode": null, - "docs": "Created rule" + "docs": "An entity with properties from multiple parents" }, "v2Responses": null, "errors": [], "userSpecifiedExamples": [ { "example": { - "id": "c1cf878e", + "id": "b2b07150", "name": null, - "url": "/rules", + "url": "/entities", "rootPathParameters": [], "endpointPathParameters": [], "servicePathParameters": [], "endpointHeaders": [], "serviceHeaders": [], "queryParameters": [], - "request": { - "type": "inlinedRequestBody", - "properties": [ - { - "name": "name", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "originalTypeDeclaration": null - }, - { - "name": "executionContext", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleExecutionContext", - "displayName": null - }, - "shape": { - "type": "enum", - "value": "prod" - } - }, - "jsonExample": "prod" - }, - "originalTypeDeclaration": null - } - ], - "extraProperties": null, - "jsonExample": { - "name": "name", - "executionContext": "prod" - } - }, + "request": null, "response": { "type": "ok", "value": { @@ -2528,107 +4670,46 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, "shape": { "type": "object", "properties": [ { - "name": "createdBy", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "createdBy" - } - } - }, - "jsonExample": "createdBy" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "createdBy" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleResponse", - "displayName": null - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "createdDateTime", + "name": "id", "value": { "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" - } - }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "DATE_TIME", - "v2": null - } + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" } } }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "id" }, "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null }, { - "name": "modifiedBy", + "name": "name", "value": { "shape": { "type": "container", @@ -2640,11 +4721,11 @@ "primitive": { "type": "string", "string": { - "original": "modifiedBy" + "original": "name" } } }, - "jsonExample": "modifiedBy" + "jsonExample": "name" }, "valueType": { "_type": "primitive", @@ -2659,22 +4740,22 @@ } } }, - "jsonExample": "modifiedBy" + "jsonExample": "name" }, "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null }, { - "name": "modifiedDateTime", + "name": "summary", "value": { "shape": { "type": "container", @@ -2684,84 +4765,37 @@ "shape": { "type": "primitive", "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" + "type": "string", + "string": { + "original": "summary" + } } }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "summary" }, "valueType": { "_type": "primitive", "primitive": { - "v1": "DATE_TIME", - "v2": null + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } } } } }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleResponse", - "displayName": null - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "id", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleResponse", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "name", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" + "jsonExample": "summary" }, "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, "propertyAccess": null @@ -2772,13 +4806,13 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleResponseStatus", + "typeId": "type_:CombinedEntityStatus", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponseStatus", + "name": "CombinedEntityStatus", "displayName": null }, "shape": { @@ -2789,69 +4823,13 @@ "jsonExample": "active" }, "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleResponse", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "executionContext", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleExecutionContext", - "displayName": null - }, - "shape": { - "type": "enum", - "value": "prod" - } - }, - "jsonExample": "prod" - }, - "valueType": { - "_type": "named", - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext", - "default": null, - "inline": null - } - } - }, - "jsonExample": "prod" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, "propertyAccess": null @@ -2861,14 +4839,10 @@ } }, "jsonExample": { - "createdBy": "createdBy", - "createdDateTime": "2024-01-15T09:30:00Z", - "modifiedBy": "modifiedBy", - "modifiedDateTime": "2024-01-15T09:30:00Z", "id": "id", "name": "name", - "status": "active", - "executionContext": "prod" + "summary": "summary", + "status": "active" } } } @@ -2881,65 +4855,16 @@ "autogeneratedExamples": [ { "example": { - "id": "e09bf9ea", - "url": "/rules", - "name": null, - "endpointHeaders": [], - "endpointPathParameters": [], - "queryParameters": [], - "servicePathParameters": [], - "serviceHeaders": [], - "rootPathParameters": [], - "request": { - "type": "inlinedRequestBody", - "properties": [ - { - "name": "name", - "originalTypeDeclaration": null, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - } - }, - { - "name": "executionContext", - "originalTypeDeclaration": null, - "value": { - "shape": { - "type": "named", - "shape": { - "type": "enum", - "value": "prod" - }, - "typeName": { - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext" - } - }, - "jsonExample": "prod" - } - } - ], - "extraProperties": null, - "jsonExample": { - "name": "name", - "executionContext": "prod" - } - }, + "id": "54665e53", + "url": "/entities", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, "response": { "type": "ok", "value": { @@ -2951,103 +4876,42 @@ "type": "object", "properties": [ { - "name": "createdBy", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "createdBy" - } - } - }, - "jsonExample": "createdBy" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "createdBy" - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "createdDateTime", + "name": "id", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" - } - }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "DATE_TIME", - "v2": null - } + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" } } }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "id" }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null }, { - "name": "modifiedBy", + "name": "name", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { @@ -3060,11 +4924,11 @@ "primitive": { "type": "string", "string": { - "original": "modifiedBy" + "original": "name" } } }, - "jsonExample": "modifiedBy" + "jsonExample": "name" }, "valueType": { "_type": "primitive", @@ -3079,21 +4943,21 @@ } } }, - "jsonExample": "modifiedBy" + "jsonExample": "name" }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null }, { - "name": "modifiedDateTime", + "name": "summary", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { @@ -3104,89 +4968,42 @@ "shape": { "type": "primitive", "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" + "type": "string", + "string": { + "original": "summary" + } } }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "summary" }, "valueType": { "_type": "primitive", "primitive": { - "v1": "DATE_TIME", - "v2": null + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } } } } }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "id", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "propertyAccess": null - }, - { - "name": "name", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" + "jsonExample": "summary" }, "propertyAccess": null }, { "name": "status", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { @@ -3196,99 +5013,39 @@ "value": "active" }, "typeName": { - "name": "RuleResponseStatus", + "name": "CombinedEntityStatus", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponseStatus" + "typeId": "type_:CombinedEntityStatus" } }, "jsonExample": "active" }, "propertyAccess": null - }, - { - "name": "executionContext", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "named", - "shape": { - "type": "enum", - "value": "prod" - }, - "typeName": { - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext" - } - }, - "jsonExample": "prod" - }, - "valueType": { - "_type": "named", - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext", - "default": null, - "inline": null - } - } - }, - "jsonExample": "prod" - }, - "propertyAccess": null } ], "extraProperties": null }, "typeName": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" } }, "jsonExample": { - "createdBy": "createdBy", - "createdDateTime": "2024-01-15T09:30:00Z", - "modifiedBy": "modifiedBy", - "modifiedDateTime": "2024-01-15T09:30:00Z", "id": "id", - "name": "name", - "status": "active", - "executionContext": "prod" + "name": "name", + "summary": "summary", + "status": "active" } } } @@ -3309,9 +5066,9 @@ "docs": null }, { - "id": "endpoint_.listUsers", - "name": "listUsers", - "displayName": "List users with paginated results", + "id": "endpoint_.getOrganization", + "name": "getOrganization", + "displayName": "Get an organization with merged object-typed properties", "auth": false, "security": null, "idempotent": false, @@ -3320,11 +5077,11 @@ "method": "GET", "basePath": null, "path": { - "head": "/users", + "head": "/organizations", "parts": [] }, "fullPath": { - "head": "users", + "head": "organizations", "parts": [] }, "pathParameters": [], @@ -3341,33 +5098,33 @@ "type": "response", "responseBodyType": { "_type": "named", - "name": "UserSearchResponse", + "name": "Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:UserSearchResponse", + "typeId": "type_:Organization", "default": null, "inline": null }, - "docs": "Paginated list of users", + "docs": "An organization whose metadata is merged from two parents", "v2Examples": null } }, "status-code": 200, "isWildcardStatusCode": null, - "docs": "Paginated list of users" + "docs": "An organization whose metadata is merged from two parents" }, "v2Responses": null, "errors": [], "userSpecifiedExamples": [ { "example": { - "id": "55942cbc", + "id": "dfb0bc71", "name": null, - "url": "/users", + "url": "/organizations", "rootPathParameters": [], "endpointPathParameters": [], "servicePathParameters": [], @@ -3383,131 +5140,46 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:UserSearchResponse", + "typeId": "type_:Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "UserSearchResponse", + "name": "Organization", "displayName": null }, "shape": { "type": "object", "properties": [ { - "name": "paging", + "name": "id", "value": { "shape": { - "type": "named", - "typeName": { - "typeId": "type_:PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "PagingCursors", - "displayName": null - }, - "shape": { - "type": "object", - "properties": [ - { - "name": "next", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "next" - } - } - }, - "jsonExample": "next" - }, - "originalTypeDeclaration": { - "typeId": "type_:PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "PagingCursors", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "previous", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "previous" - } - } - }, - "jsonExample": "previous" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "previous" - }, - "originalTypeDeclaration": { - "typeId": "type_:PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "PagingCursors", - "displayName": null - }, - "propertyAccess": null - } - ], - "extraProperties": null + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } } }, - "jsonExample": { - "next": "next", - "previous": "previous" - } + "jsonExample": "id" }, "originalTypeDeclaration": { - "typeId": "type_:UserSearchResponse", + "typeId": "type_:Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "UserSearchResponse", + "name": "Organization", "displayName": null }, "propertyAccess": null }, { - "name": "results", + "name": "metadata", "value": { "shape": { "type": "container", @@ -3515,146 +5187,155 @@ "type": "optional", "optional": { "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "User", - "displayName": null - }, - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "originalTypeDeclaration": { - "typeId": "type_:User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "User", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "email", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "email" - } - } - }, - "jsonExample": "email" - }, - "originalTypeDeclaration": { - "typeId": "type_:User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "User", - "displayName": null - }, - "propertyAccess": null - } - ], - "extraProperties": null - } - }, - "jsonExample": { - "id": "id", - "email": "email" - } - } - ], - "itemType": { - "_type": "named", - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null - } - } - }, - "jsonExample": [ - { - "id": "id", - "email": "email" - } - ] - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": "User", + "type": "named", + "typeName": { + "typeId": "type_:OrganizationMetadata", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null + "name": "OrganizationMetadata", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "region", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "region" + } + } + }, + "jsonExample": "region" + }, + "originalTypeDeclaration": { + "typeId": "type_:OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "OrganizationMetadata", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "domain", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "domain" + } + } + }, + "jsonExample": "domain" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "domain" + }, + "originalTypeDeclaration": { + "typeId": "type_:OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "OrganizationMetadata", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null } + }, + "jsonExample": { + "region": "region", + "domain": "domain" } + }, + "valueType": { + "_type": "named", + "name": "OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:OrganizationMetadata", + "default": null, + "inline": null } } }, - "jsonExample": [ - { - "id": "id", - "email": "email" + "jsonExample": { + "region": "region", + "domain": "domain" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "Organization", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } } - ] + }, + "jsonExample": "name" }, "originalTypeDeclaration": { - "typeId": "type_:UserSearchResponse", + "typeId": "type_:Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "UserSearchResponse", + "name": "Organization", "displayName": null }, "propertyAccess": null @@ -3664,16 +5345,12 @@ } }, "jsonExample": { - "paging": { - "next": "next", - "previous": "previous" + "id": "id", + "metadata": { + "region": "region", + "domain": "domain" }, - "results": [ - { - "id": "id", - "email": "email" - } - ] + "name": "name" } } } @@ -3686,8 +5363,8 @@ "autogeneratedExamples": [ { "example": { - "id": "e7c56b0b", - "url": "/users", + "id": "ce204bc0", + "url": "/organizations", "name": null, "endpointHeaders": [], "endpointPathParameters": [], @@ -3707,351 +5384,190 @@ "type": "object", "properties": [ { - "name": "paging", - "originalTypeDeclaration": { - "name": "UserSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:UserSearchResponse" - }, - "value": { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "next", - "originalTypeDeclaration": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:PagingCursors" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "next" - } - } - }, - "jsonExample": "next" - }, - "propertyAccess": null - }, - { - "name": "previous", - "originalTypeDeclaration": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:PagingCursors" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "previous" - } - } - }, - "jsonExample": "previous" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "previous" - }, - "propertyAccess": null - } - ], - "extraProperties": null - }, - "typeName": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:PagingCursors" - } - }, - "jsonExample": { - "next": "next", - "previous": "previous" - } - }, - "propertyAccess": null - }, - { - "name": "results", - "originalTypeDeclaration": { - "name": "UserSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:UserSearchResponse" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "propertyAccess": null - }, - { - "name": "email", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "email" - } - } - }, - "jsonExample": "email" - }, - "propertyAccess": null + "name": "id", + "originalTypeDeclaration": { + "name": "Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Organization" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "metadata", + "originalTypeDeclaration": { + "name": "Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Organization" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "region", + "originalTypeDeclaration": { + "name": "OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:OrganizationMetadata" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "region" } - ], - "extraProperties": null + } }, - "typeName": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - } + "jsonExample": "region" }, - "jsonExample": { - "id": "id", - "email": "email" - } + "propertyAccess": null }, { - "shape": { - "type": "named", + "name": "domain", + "originalTypeDeclaration": { + "name": "OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:OrganizationMetadata" + }, + "value": { "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "domain" } - }, - "jsonExample": "id" + } }, - "propertyAccess": null + "jsonExample": "domain" }, - { - "name": "email", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "email" - } - } - }, - "jsonExample": "email" - }, - "propertyAccess": null + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } } - ], - "extraProperties": null + } }, - "typeName": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - } + "jsonExample": "domain" }, - "jsonExample": { - "id": "id", - "email": "email" - } + "propertyAccess": null } ], - "itemType": { - "_type": "named", - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null - } - } - }, - "jsonExample": [ - { - "id": "id", - "email": "email" + "extraProperties": null }, - { - "id": "id", - "email": "email" - } - ] - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": "User", + "typeName": { + "name": "OrganizationMetadata", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null + "typeId": "type_:OrganizationMetadata" } + }, + "jsonExample": { + "region": "region", + "domain": "domain" } + }, + "valueType": { + "_type": "named", + "name": "OrganizationMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:OrganizationMetadata", + "default": null, + "inline": null } } }, - "jsonExample": [ - { - "id": "id", - "email": "email" - }, - { - "id": "id", - "email": "email" + "jsonExample": { + "region": "region", + "domain": "domain" + } + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Organization" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } } - ] + }, + "jsonExample": "name" }, "propertyAccess": null } @@ -4059,601 +5575,708 @@ "extraProperties": null }, "typeName": { - "name": "UserSearchResponse", + "name": "Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:UserSearchResponse" + "typeId": "type_:Organization" } }, "jsonExample": { - "paging": { - "next": "next", - "previous": "previous" + "id": "id", + "metadata": { + "region": "region", + "domain": "domain" }, - "results": [ - { - "id": "id", - "email": "email" - }, - { - "id": "id", - "email": "email" - } - ] + "name": "name" } } } }, - "docs": null + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + } + ], + "audiences": null + } + }, + "constants": { + "errorInstanceIdKey": "errorInstanceId" + }, + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": "Default", + "url": "https://api.example.com", + "audiences": null, + "defaultUrl": null, + "urlTemplate": null, + "urlVariables": null, + "docs": null + } + ] + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_": [ + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:RuleType", + "type_:RuleTypeSearchResponse", + "type_:User", + "type_:UserSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse", + "type_:CombinedEntityStatus", + "type_:CombinedEntity", + "type_:OrganizationMetadata", + "type_:Organization" + ] + }, + "sharedTypes": [ + "type_:PaginatedResult", + "type_:AuditInfo", + "type_:Identifiable", + "type_:Describable", + "type_:BaseOrgMetadata", + "type_:BaseOrg", + "type_:DetailedOrgMetadata", + "type_:DetailedOrg" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "publishConfig": null, + "dynamic": { + "version": "1.0.0", + "types": { + "type_:PaginatedResult": { + "type": "object", + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "list", + "value": { + "type": "unknown" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:PagingCursors": { + "type": "object", + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleExecutionContext": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" } - ], - "pagination": null, - "transport": null, - "v2Examples": null, - "source": null, - "audiences": null, - "retries": null, - "apiPlayground": null, - "responseHeaders": [], - "availability": null, - "docs": null + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } }, - { - "id": "endpoint_.getEntity", - "name": "getEntity", - "displayName": "Get an entity that combines multiple parents", - "auth": false, - "security": null, - "idempotent": false, - "baseUrl": null, - "v2BaseUrls": null, - "method": "GET", - "basePath": null, - "path": { - "head": "/entities", - "parts": [] + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" + } + } }, - "fullPath": { - "head": "entities", - "parts": [] + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [], - "headers": [], - "requestBody": null, - "v2RequestBodies": null, - "sdkRequest": null, - "response": { - "body": { - "type": "json", + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ] + }, + "type_:AuditInfo": { + "type": "object", + "declaration": { + "name": { + "originalName": "AuditInfo", + "camelCase": { + "unsafeName": "auditInfo", + "safeName": "auditInfo" + }, + "snakeCase": { + "unsafeName": "audit_info", + "safeName": "audit_info" + }, + "screamingSnakeCase": { + "unsafeName": "AUDIT_INFO", + "safeName": "AUDIT_INFO" + }, + "pascalCase": { + "unsafeName": "AuditInfo", + "safeName": "AuditInfo" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", "value": { - "type": "response", - "responseBodyType": { - "_type": "named", - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity", - "default": null, - "inline": null + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" }, - "docs": "An entity with properties from multiple parents", - "v2Examples": null + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" } }, - "status-code": 200, - "isWildcardStatusCode": null, - "docs": "An entity with properties from multiple parents" + "propertyAccess": "READ_ONLY", + "variable": null }, - "v2Responses": null, - "errors": [], - "userSpecifiedExamples": [ - { - "example": { - "id": "b2b07150", - "name": null, - "url": "/entities", - "rootPathParameters": [], - "endpointPathParameters": [], - "servicePathParameters": [], - "endpointHeaders": [], - "serviceHeaders": [], - "queryParameters": [], - "request": null, - "response": { - "type": "ok", - "value": { - "type": "body", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "name", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "name" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "summary", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "summary" - } - } - }, - "jsonExample": "summary" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "summary" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "status", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:CombinedEntityStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntityStatus", - "displayName": null - }, - "shape": { - "type": "enum", - "value": "active" - } - }, - "jsonExample": "active" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - } - ], - "extraProperties": null - } - }, - "jsonExample": { - "id": "id", - "name": "name", - "summary": "summary", - "status": "active" - } - } - } + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" }, - "docs": null - }, - "codeSamples": null + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleType": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleType", + "camelCase": { + "unsafeName": "ruleType", + "safeName": "ruleType" + }, + "snakeCase": { + "unsafeName": "rule_type", + "safeName": "rule_type" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE", + "safeName": "RULE_TYPE" + }, + "pascalCase": { + "unsafeName": "RuleType", + "safeName": "RuleType" } - ], - "autogeneratedExamples": [ - { - "example": { - "id": "54665e53", - "url": "/entities", - "name": null, - "endpointHeaders": [], - "endpointPathParameters": [], - "queryParameters": [], - "servicePathParameters": [], - "serviceHeaders": [], - "rootPathParameters": [], - "request": null, - "response": { - "type": "ok", - "value": { - "type": "body", - "value": { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "propertyAccess": null - }, - { - "name": "name", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "name" - }, - "propertyAccess": null - }, - { - "name": "summary", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "summary" - } - } - }, - "jsonExample": "summary" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "summary" - }, - "propertyAccess": null - }, - { - "name": "status", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "named", - "shape": { - "type": "enum", - "value": "active" - }, - "typeName": { - "name": "CombinedEntityStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntityStatus" - } - }, - "jsonExample": "active" - }, - "propertyAccess": null - } - ], - "extraProperties": null - }, - "typeName": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - } - }, - "jsonExample": { - "id": "id", - "name": "name", - "summary": "summary", - "status": "active" - } - } - } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" }, - "docs": null + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } } - } - ], - "pagination": null, - "transport": null, - "v2Examples": null, - "source": null, - "audiences": null, - "retries": null, - "apiPlayground": null, - "responseHeaders": [], - "availability": null, - "docs": null - } - ], - "audiences": null - } - }, - "constants": { - "errorInstanceIdKey": "errorInstanceId" - }, - "environments": { - "defaultEnvironment": "Default", - "environments": { - "type": "singleBaseUrl", - "environments": [ - { - "id": "Default", - "name": "Default", - "url": "https://api.example.com", - "audiences": null, - "defaultUrl": null, - "urlTemplate": null, - "urlVariables": null, - "docs": null - } - ] - } - }, - "errorDiscriminationStrategy": { - "type": "statusCode" - }, - "basePath": null, - "pathParameters": [], - "variables": [], - "serviceTypeReferenceInfo": { - "typesReferencedOnlyByService": { - "service_": [ - "type_:PagingCursors", - "type_:RuleExecutionContext", - "type_:RuleType", - "type_:RuleTypeSearchResponse", - "type_:User", - "type_:UserSearchResponse", - "type_:RuleResponseStatus", - "type_:RuleResponse", - "type_:CombinedEntityStatus", - "type_:CombinedEntity" - ] - }, - "sharedTypes": [ - "type_:PaginatedResult", - "type_:AuditInfo", - "type_:Identifiable", - "type_:Describable" - ] - }, - "webhookGroups": {}, - "websocketChannels": {}, - "readmeConfig": null, - "sourceConfig": null, - "publishConfig": null, - "dynamic": { - "version": "1.0.0", - "types": { - "type_:PaginatedResult": { + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleTypeSearchResponse": { "type": "object", "declaration": { "name": { - "originalName": "PaginatedResult", + "originalName": "RuleTypeSearchResponse", "camelCase": { - "unsafeName": "paginatedResult", - "safeName": "paginatedResult" + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" }, "snakeCase": { - "unsafeName": "paginated_result", - "safeName": "paginated_result" + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" }, "screamingSnakeCase": { - "unsafeName": "PAGINATED_RESULT", - "safeName": "PAGINATED_RESULT" + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" }, "pascalCase": { - "unsafeName": "PaginatedResult", - "safeName": "PaginatedResult" + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" } }, "fernFilepath": { @@ -4717,9 +6340,13 @@ } }, "typeReference": { - "type": "list", + "type": "optional", "value": { - "type": "unknown" + "type": "list", + "value": { + "type": "named", + "value": "type_:RuleType" + } } }, "propertyAccess": null, @@ -4729,26 +6356,119 @@ "extends": null, "additionalProperties": false }, - "type_:PagingCursors": { + "type_:User": { "type": "object", "declaration": { "name": { - "originalName": "PagingCursors", + "originalName": "User", "camelCase": { - "unsafeName": "pagingCursors", - "safeName": "pagingCursors" + "unsafeName": "user", + "safeName": "user" }, "snakeCase": { - "unsafeName": "paging_cursors", - "safeName": "paging_cursors" + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "email", + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:UserSearchResponse": { + "type": "object", + "declaration": { + "name": { + "originalName": "UserSearchResponse", + "camelCase": { + "unsafeName": "userSearchResponse", + "safeName": "userSearchResponse" + }, + "snakeCase": { + "unsafeName": "user_search_response", + "safeName": "user_search_response" }, "screamingSnakeCase": { - "unsafeName": "PAGING_CURSORS", - "safeName": "PAGING_CURSORS" + "unsafeName": "USER_SEARCH_RESPONSE", + "safeName": "USER_SEARCH_RESPONSE" }, "pascalCase": { - "unsafeName": "PagingCursors", - "safeName": "PagingCursors" + "unsafeName": "UserSearchResponse", + "safeName": "UserSearchResponse" } }, "fernFilepath": { @@ -4760,62 +6480,65 @@ "properties": [ { "name": { - "wireValue": "next", + "wireValue": "paging", "name": { - "originalName": "next", + "originalName": "paging", "camelCase": { - "unsafeName": "next", - "safeName": "next" + "unsafeName": "paging", + "safeName": "paging" }, "snakeCase": { - "unsafeName": "next", - "safeName": "next" + "unsafeName": "paging", + "safeName": "paging" }, "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" + "unsafeName": "PAGING", + "safeName": "PAGING" }, "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" + "unsafeName": "Paging", + "safeName": "Paging" } } }, "typeReference": { - "type": "primitive", - "value": "STRING" + "type": "named", + "value": "type_:PagingCursors" }, "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "previous", + "wireValue": "results", "name": { - "originalName": "previous", + "originalName": "results", "camelCase": { - "unsafeName": "previous", - "safeName": "previous" + "unsafeName": "results", + "safeName": "results" }, "snakeCase": { - "unsafeName": "previous", - "safeName": "previous" + "unsafeName": "results", + "safeName": "results" }, "screamingSnakeCase": { - "unsafeName": "PREVIOUS", - "safeName": "PREVIOUS" + "unsafeName": "RESULTS", + "safeName": "RESULTS" }, "pascalCase": { - "unsafeName": "Previous", - "safeName": "Previous" + "unsafeName": "Results", + "safeName": "Results" } } }, "typeReference": { "type": "optional", "value": { - "type": "primitive", - "value": "STRING" + "type": "list", + "value": { + "type": "named", + "value": "type_:User" + } } }, "propertyAccess": null, @@ -4825,26 +6548,26 @@ "extends": null, "additionalProperties": false }, - "type_:RuleExecutionContext": { + "type_:RuleResponseStatus": { "type": "enum", "declaration": { "name": { - "originalName": "RuleExecutionContext", + "originalName": "RuleResponseStatus", "camelCase": { - "unsafeName": "ruleExecutionContext", - "safeName": "ruleExecutionContext" + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" }, "snakeCase": { - "unsafeName": "rule_execution_context", - "safeName": "rule_execution_context" + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" }, "screamingSnakeCase": { - "unsafeName": "RULE_EXECUTION_CONTEXT", - "safeName": "RULE_EXECUTION_CONTEXT" + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" }, "pascalCase": { - "unsafeName": "RuleExecutionContext", - "safeName": "RuleExecutionContext" + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" } }, "fernFilepath": { @@ -4855,93 +6578,93 @@ }, "values": [ { - "wireValue": "prod", + "wireValue": "active", "name": { - "originalName": "prod", + "originalName": "active", "camelCase": { - "unsafeName": "prod", - "safeName": "prod" + "unsafeName": "active", + "safeName": "active" }, "snakeCase": { - "unsafeName": "prod", - "safeName": "prod" + "unsafeName": "active", + "safeName": "active" }, "screamingSnakeCase": { - "unsafeName": "PROD", - "safeName": "PROD" + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" }, "pascalCase": { - "unsafeName": "Prod", - "safeName": "Prod" + "unsafeName": "Active", + "safeName": "Active" } } }, { - "wireValue": "staging", + "wireValue": "inactive", "name": { - "originalName": "staging", + "originalName": "inactive", "camelCase": { - "unsafeName": "staging", - "safeName": "staging" + "unsafeName": "inactive", + "safeName": "inactive" }, "snakeCase": { - "unsafeName": "staging", - "safeName": "staging" + "unsafeName": "inactive", + "safeName": "inactive" }, "screamingSnakeCase": { - "unsafeName": "STAGING", - "safeName": "STAGING" + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" }, "pascalCase": { - "unsafeName": "Staging", - "safeName": "Staging" + "unsafeName": "Inactive", + "safeName": "Inactive" } } }, { - "wireValue": "dev", + "wireValue": "draft", "name": { - "originalName": "dev", + "originalName": "draft", "camelCase": { - "unsafeName": "dev", - "safeName": "dev" + "unsafeName": "draft", + "safeName": "draft" }, "snakeCase": { - "unsafeName": "dev", - "safeName": "dev" + "unsafeName": "draft", + "safeName": "draft" }, "screamingSnakeCase": { - "unsafeName": "DEV", - "safeName": "DEV" + "unsafeName": "DRAFT", + "safeName": "DRAFT" }, "pascalCase": { - "unsafeName": "Dev", - "safeName": "Dev" + "unsafeName": "Draft", + "safeName": "Draft" } } } ] }, - "type_:AuditInfo": { + "type_:RuleResponse": { "type": "object", "declaration": { "name": { - "originalName": "AuditInfo", + "originalName": "RuleResponse", "camelCase": { - "unsafeName": "auditInfo", - "safeName": "auditInfo" + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" }, "snakeCase": { - "unsafeName": "audit_info", - "safeName": "audit_info" + "unsafeName": "rule_response", + "safeName": "rule_response" }, "screamingSnakeCase": { - "unsafeName": "AUDIT_INFO", - "safeName": "AUDIT_INFO" + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" }, "pascalCase": { - "unsafeName": "AuditInfo", - "safeName": "AuditInfo" + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" } }, "fernFilepath": { @@ -5082,40 +6805,7 @@ }, "propertyAccess": "READ_ONLY", "variable": null - } - ], - "extends": null, - "additionalProperties": false - }, - "type_:RuleType": { - "type": "object", - "declaration": { - "name": { - "originalName": "RuleType", - "camelCase": { - "unsafeName": "ruleType", - "safeName": "ruleType" - }, - "snakeCase": { - "unsafeName": "rule_type", - "safeName": "rule_type" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_TYPE", - "safeName": "RULE_TYPE" - }, - "pascalCase": { - "unsafeName": "RuleType", - "safeName": "RuleType" - } }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - } - }, - "properties": [ { "name": { "wireValue": "id", @@ -5163,146 +6853,77 @@ "unsafeName": "NAME", "safeName": "NAME" }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - } - }, - "typeReference": { - "type": "primitive", - "value": "STRING" - }, - "propertyAccess": null, - "variable": null - }, - { - "name": { - "wireValue": "description", - "name": { - "originalName": "description", - "camelCase": { - "unsafeName": "description", - "safeName": "description" - }, - "snakeCase": { - "unsafeName": "description", - "safeName": "description" - }, - "screamingSnakeCase": { - "unsafeName": "DESCRIPTION", - "safeName": "DESCRIPTION" - }, - "pascalCase": { - "unsafeName": "Description", - "safeName": "Description" - } - } - }, - "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } - }, - "propertyAccess": null, - "variable": null - } - ], - "extends": null, - "additionalProperties": false - }, - "type_:RuleTypeSearchResponse": { - "type": "object", - "declaration": { - "name": { - "originalName": "RuleTypeSearchResponse", - "camelCase": { - "unsafeName": "ruleTypeSearchResponse", - "safeName": "ruleTypeSearchResponse" - }, - "snakeCase": { - "unsafeName": "rule_type_search_response", - "safeName": "rule_type_search_response" + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } }, - "screamingSnakeCase": { - "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", - "safeName": "RULE_TYPE_SEARCH_RESPONSE" + "typeReference": { + "type": "primitive", + "value": "STRING" }, - "pascalCase": { - "unsafeName": "RuleTypeSearchResponse", - "safeName": "RuleTypeSearchResponse" - } + "propertyAccess": null, + "variable": null }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - } - }, - "properties": [ { "name": { - "wireValue": "paging", + "wireValue": "status", "name": { - "originalName": "paging", + "originalName": "status", "camelCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "status", + "safeName": "status" }, "snakeCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "status", + "safeName": "status" }, "screamingSnakeCase": { - "unsafeName": "PAGING", - "safeName": "PAGING" + "unsafeName": "STATUS", + "safeName": "STATUS" }, "pascalCase": { - "unsafeName": "Paging", - "safeName": "Paging" + "unsafeName": "Status", + "safeName": "Status" } } }, "typeReference": { "type": "named", - "value": "type_:PagingCursors" + "value": "type_:RuleResponseStatus" }, "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "results", + "wireValue": "executionContext", "name": { - "originalName": "results", + "originalName": "executionContext", "camelCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "executionContext", + "safeName": "executionContext" }, "snakeCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "execution_context", + "safeName": "execution_context" }, "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" }, "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" } } }, "typeReference": { "type": "optional", "value": { - "type": "list", - "value": { - "type": "named", - "value": "type_:RuleType" - } + "type": "named", + "value": "type_:RuleExecutionContext" } }, "propertyAccess": null, @@ -5312,26 +6933,26 @@ "extends": null, "additionalProperties": false }, - "type_:User": { + "type_:Identifiable": { "type": "object", "declaration": { "name": { - "originalName": "User", + "originalName": "Identifiable", "camelCase": { - "unsafeName": "user", - "safeName": "user" + "unsafeName": "identifiable", + "safeName": "identifiable" }, "snakeCase": { - "unsafeName": "user", - "safeName": "user" + "unsafeName": "identifiable", + "safeName": "identifiable" }, "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" + "unsafeName": "IDENTIFIABLE", + "safeName": "IDENTIFIABLE" }, "pascalCase": { - "unsafeName": "User", - "safeName": "User" + "unsafeName": "Identifiable", + "safeName": "Identifiable" } }, "fernFilepath": { @@ -5373,30 +6994,33 @@ }, { "name": { - "wireValue": "email", + "wireValue": "name", "name": { - "originalName": "email", + "originalName": "name", "camelCase": { - "unsafeName": "email", - "safeName": "email" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "email", - "safeName": "email" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "EMAIL", - "safeName": "EMAIL" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Email", - "safeName": "Email" + "unsafeName": "Name", + "safeName": "Name" } } }, "typeReference": { - "type": "primitive", - "value": "STRING" + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } }, "propertyAccess": null, "variable": null @@ -5405,26 +7029,26 @@ "extends": null, "additionalProperties": false }, - "type_:UserSearchResponse": { + "type_:Describable": { "type": "object", "declaration": { "name": { - "originalName": "UserSearchResponse", + "originalName": "Describable", "camelCase": { - "unsafeName": "userSearchResponse", - "safeName": "userSearchResponse" + "unsafeName": "describable", + "safeName": "describable" }, "snakeCase": { - "unsafeName": "user_search_response", - "safeName": "user_search_response" + "unsafeName": "describable", + "safeName": "describable" }, "screamingSnakeCase": { - "unsafeName": "USER_SEARCH_RESPONSE", - "safeName": "USER_SEARCH_RESPONSE" + "unsafeName": "DESCRIBABLE", + "safeName": "DESCRIBABLE" }, "pascalCase": { - "unsafeName": "UserSearchResponse", - "safeName": "UserSearchResponse" + "unsafeName": "Describable", + "safeName": "Describable" } }, "fernFilepath": { @@ -5436,65 +7060,65 @@ "properties": [ { "name": { - "wireValue": "paging", + "wireValue": "name", "name": { - "originalName": "paging", + "originalName": "name", "camelCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "PAGING", - "safeName": "PAGING" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Paging", - "safeName": "Paging" + "unsafeName": "Name", + "safeName": "Name" } } }, "typeReference": { - "type": "named", - "value": "type_:PagingCursors" + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } }, "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "results", + "wireValue": "summary", "name": { - "originalName": "results", + "originalName": "summary", "camelCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "summary", + "safeName": "summary" }, "snakeCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "summary", + "safeName": "summary" }, "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" }, "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" + "unsafeName": "Summary", + "safeName": "Summary" } } }, "typeReference": { "type": "optional", "value": { - "type": "list", - "value": { - "type": "named", - "value": "type_:User" - } + "type": "primitive", + "value": "STRING" } }, "propertyAccess": null, @@ -5504,26 +7128,26 @@ "extends": null, "additionalProperties": false }, - "type_:RuleResponseStatus": { + "type_:CombinedEntityStatus": { "type": "enum", "declaration": { "name": { - "originalName": "RuleResponseStatus", + "originalName": "CombinedEntityStatus", "camelCase": { - "unsafeName": "ruleResponseStatus", - "safeName": "ruleResponseStatus" + "unsafeName": "combinedEntityStatus", + "safeName": "combinedEntityStatus" }, "snakeCase": { - "unsafeName": "rule_response_status", - "safeName": "rule_response_status" + "unsafeName": "combined_entity_status", + "safeName": "combined_entity_status" }, "screamingSnakeCase": { - "unsafeName": "RULE_RESPONSE_STATUS", - "safeName": "RULE_RESPONSE_STATUS" + "unsafeName": "COMBINED_ENTITY_STATUS", + "safeName": "COMBINED_ENTITY_STATUS" }, "pascalCase": { - "unsafeName": "RuleResponseStatus", - "safeName": "RuleResponseStatus" + "unsafeName": "CombinedEntityStatus", + "safeName": "CombinedEntityStatus" } }, "fernFilepath": { @@ -5556,71 +7180,49 @@ } }, { - "wireValue": "inactive", - "name": { - "originalName": "inactive", - "camelCase": { - "unsafeName": "inactive", - "safeName": "inactive" - }, - "snakeCase": { - "unsafeName": "inactive", - "safeName": "inactive" - }, - "screamingSnakeCase": { - "unsafeName": "INACTIVE", - "safeName": "INACTIVE" - }, - "pascalCase": { - "unsafeName": "Inactive", - "safeName": "Inactive" - } - } - }, - { - "wireValue": "draft", + "wireValue": "archived", "name": { - "originalName": "draft", + "originalName": "archived", "camelCase": { - "unsafeName": "draft", - "safeName": "draft" + "unsafeName": "archived", + "safeName": "archived" }, "snakeCase": { - "unsafeName": "draft", - "safeName": "draft" + "unsafeName": "archived", + "safeName": "archived" }, "screamingSnakeCase": { - "unsafeName": "DRAFT", - "safeName": "DRAFT" + "unsafeName": "ARCHIVED", + "safeName": "ARCHIVED" }, "pascalCase": { - "unsafeName": "Draft", - "safeName": "Draft" + "unsafeName": "Archived", + "safeName": "Archived" } } } ] }, - "type_:RuleResponse": { + "type_:CombinedEntity": { "type": "object", "declaration": { "name": { - "originalName": "RuleResponse", + "originalName": "CombinedEntity", "camelCase": { - "unsafeName": "ruleResponse", - "safeName": "ruleResponse" + "unsafeName": "combinedEntity", + "safeName": "combinedEntity" }, "snakeCase": { - "unsafeName": "rule_response", - "safeName": "rule_response" + "unsafeName": "combined_entity", + "safeName": "combined_entity" }, "screamingSnakeCase": { - "unsafeName": "RULE_RESPONSE", - "safeName": "RULE_RESPONSE" + "unsafeName": "COMBINED_ENTITY", + "safeName": "COMBINED_ENTITY" }, "pascalCase": { - "unsafeName": "RuleResponse", - "safeName": "RuleResponse" + "unsafeName": "CombinedEntity", + "safeName": "CombinedEntity" } }, "fernFilepath": { @@ -5632,57 +7234,54 @@ "properties": [ { "name": { - "wireValue": "createdBy", + "wireValue": "id", "name": { - "originalName": "createdBy", + "originalName": "id", "camelCase": { - "unsafeName": "createdBy", - "safeName": "createdBy" + "unsafeName": "id", + "safeName": "id" }, "snakeCase": { - "unsafeName": "created_by", - "safeName": "created_by" + "unsafeName": "id", + "safeName": "id" }, "screamingSnakeCase": { - "unsafeName": "CREATED_BY", - "safeName": "CREATED_BY" + "unsafeName": "ID", + "safeName": "ID" }, "pascalCase": { - "unsafeName": "CreatedBy", - "safeName": "CreatedBy" + "unsafeName": "ID", + "safeName": "ID" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } + "type": "primitive", + "value": "STRING" }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "createdDateTime", + "wireValue": "name", "name": { - "originalName": "createdDateTime", + "originalName": "name", "camelCase": { - "unsafeName": "createdDateTime", - "safeName": "createdDateTime" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "created_date_time", - "safeName": "created_date_time" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "CREATED_DATE_TIME", - "safeName": "CREATED_DATE_TIME" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "CreatedDateTime", - "safeName": "CreatedDateTime" + "unsafeName": "Name", + "safeName": "Name" } } }, @@ -5690,32 +7289,32 @@ "type": "optional", "value": { "type": "primitive", - "value": "DATE_TIME" + "value": "STRING" } }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "modifiedBy", + "wireValue": "summary", "name": { - "originalName": "modifiedBy", + "originalName": "summary", "camelCase": { - "unsafeName": "modifiedBy", - "safeName": "modifiedBy" + "unsafeName": "summary", + "safeName": "summary" }, "snakeCase": { - "unsafeName": "modified_by", - "safeName": "modified_by" + "unsafeName": "summary", + "safeName": "summary" }, "screamingSnakeCase": { - "unsafeName": "MODIFIED_BY", - "safeName": "MODIFIED_BY" + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" }, "pascalCase": { - "unsafeName": "ModifiedBy", - "safeName": "ModifiedBy" + "unsafeName": "Summary", + "safeName": "Summary" } } }, @@ -5726,92 +7325,92 @@ "value": "STRING" } }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "modifiedDateTime", + "wireValue": "status", "name": { - "originalName": "modifiedDateTime", + "originalName": "status", "camelCase": { - "unsafeName": "modifiedDateTime", - "safeName": "modifiedDateTime" + "unsafeName": "status", + "safeName": "status" }, "snakeCase": { - "unsafeName": "modified_date_time", - "safeName": "modified_date_time" + "unsafeName": "status", + "safeName": "status" }, "screamingSnakeCase": { - "unsafeName": "MODIFIED_DATE_TIME", - "safeName": "MODIFIED_DATE_TIME" + "unsafeName": "STATUS", + "safeName": "STATUS" }, "pascalCase": { - "unsafeName": "ModifiedDateTime", - "safeName": "ModifiedDateTime" + "unsafeName": "Status", + "safeName": "Status" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "DATE_TIME" - } + "type": "named", + "value": "type_:CombinedEntityStatus" }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null - }, - { - "name": { - "wireValue": "id", - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "ID", - "safeName": "ID" - } - } + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:BaseOrgMetadata": { + "type": "object", + "declaration": { + "name": { + "originalName": "BaseOrgMetadata", + "camelCase": { + "unsafeName": "baseOrgMetadata", + "safeName": "baseOrgMetadata" }, - "typeReference": { - "type": "primitive", - "value": "STRING" + "snakeCase": { + "unsafeName": "base_org_metadata", + "safeName": "base_org_metadata" }, - "propertyAccess": null, - "variable": null + "screamingSnakeCase": { + "unsafeName": "BASE_ORG_METADATA", + "safeName": "BASE_ORG_METADATA" + }, + "pascalCase": { + "unsafeName": "BaseOrgMetadata", + "safeName": "BaseOrgMetadata" + } }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ { "name": { - "wireValue": "name", + "wireValue": "region", "name": { - "originalName": "name", + "originalName": "region", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "REGION", + "safeName": "REGION" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Region", + "safeName": "Region" } } }, @@ -5824,62 +7423,32 @@ }, { "name": { - "wireValue": "status", - "name": { - "originalName": "status", - "camelCase": { - "unsafeName": "status", - "safeName": "status" - }, - "snakeCase": { - "unsafeName": "status", - "safeName": "status" - }, - "screamingSnakeCase": { - "unsafeName": "STATUS", - "safeName": "STATUS" - }, - "pascalCase": { - "unsafeName": "Status", - "safeName": "Status" - } - } - }, - "typeReference": { - "type": "named", - "value": "type_:RuleResponseStatus" - }, - "propertyAccess": null, - "variable": null - }, - { - "name": { - "wireValue": "executionContext", + "wireValue": "tier", "name": { - "originalName": "executionContext", + "originalName": "tier", "camelCase": { - "unsafeName": "executionContext", - "safeName": "executionContext" + "unsafeName": "tier", + "safeName": "tier" }, "snakeCase": { - "unsafeName": "execution_context", - "safeName": "execution_context" + "unsafeName": "tier", + "safeName": "tier" }, "screamingSnakeCase": { - "unsafeName": "EXECUTION_CONTEXT", - "safeName": "EXECUTION_CONTEXT" + "unsafeName": "TIER", + "safeName": "TIER" }, "pascalCase": { - "unsafeName": "ExecutionContext", - "safeName": "ExecutionContext" + "unsafeName": "Tier", + "safeName": "Tier" } } }, "typeReference": { "type": "optional", "value": { - "type": "named", - "value": "type_:RuleExecutionContext" + "type": "primitive", + "value": "STRING" } }, "propertyAccess": null, @@ -5889,26 +7458,26 @@ "extends": null, "additionalProperties": false }, - "type_:Identifiable": { + "type_:BaseOrg": { "type": "object", "declaration": { "name": { - "originalName": "Identifiable", + "originalName": "BaseOrg", "camelCase": { - "unsafeName": "identifiable", - "safeName": "identifiable" + "unsafeName": "baseOrg", + "safeName": "baseOrg" }, "snakeCase": { - "unsafeName": "identifiable", - "safeName": "identifiable" + "unsafeName": "base_org", + "safeName": "base_org" }, "screamingSnakeCase": { - "unsafeName": "IDENTIFIABLE", - "safeName": "IDENTIFIABLE" + "unsafeName": "BASE_ORG", + "safeName": "BASE_ORG" }, "pascalCase": { - "unsafeName": "Identifiable", - "safeName": "Identifiable" + "unsafeName": "BaseOrg", + "safeName": "BaseOrg" } }, "fernFilepath": { @@ -5950,32 +7519,32 @@ }, { "name": { - "wireValue": "name", + "wireValue": "metadata", "name": { - "originalName": "name", + "originalName": "metadata", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "metadata", + "safeName": "metadata" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "metadata", + "safeName": "metadata" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "METADATA", + "safeName": "METADATA" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Metadata", + "safeName": "Metadata" } } }, "typeReference": { "type": "optional", "value": { - "type": "primitive", - "value": "STRING" + "type": "named", + "value": "type_:BaseOrgMetadata" } }, "propertyAccess": null, @@ -5985,26 +7554,26 @@ "extends": null, "additionalProperties": false }, - "type_:Describable": { + "type_:DetailedOrgMetadata": { "type": "object", "declaration": { "name": { - "originalName": "Describable", + "originalName": "DetailedOrgMetadata", "camelCase": { - "unsafeName": "describable", - "safeName": "describable" + "unsafeName": "detailedOrgMetadata", + "safeName": "detailedOrgMetadata" }, "snakeCase": { - "unsafeName": "describable", - "safeName": "describable" + "unsafeName": "detailed_org_metadata", + "safeName": "detailed_org_metadata" }, "screamingSnakeCase": { - "unsafeName": "DESCRIBABLE", - "safeName": "DESCRIBABLE" + "unsafeName": "DETAILED_ORG_METADATA", + "safeName": "DETAILED_ORG_METADATA" }, "pascalCase": { - "unsafeName": "Describable", - "safeName": "Describable" + "unsafeName": "DetailedOrgMetadata", + "safeName": "DetailedOrgMetadata" } }, "fernFilepath": { @@ -6016,57 +7585,54 @@ "properties": [ { "name": { - "wireValue": "name", + "wireValue": "region", "name": { - "originalName": "name", + "originalName": "region", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "REGION", + "safeName": "REGION" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Region", + "safeName": "Region" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } + "type": "primitive", + "value": "STRING" }, "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "summary", + "wireValue": "domain", "name": { - "originalName": "summary", + "originalName": "domain", "camelCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "domain", + "safeName": "domain" }, "snakeCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "domain", + "safeName": "domain" }, "screamingSnakeCase": { - "unsafeName": "SUMMARY", - "safeName": "SUMMARY" + "unsafeName": "DOMAIN", + "safeName": "DOMAIN" }, "pascalCase": { - "unsafeName": "Summary", - "safeName": "Summary" + "unsafeName": "Domain", + "safeName": "Domain" } } }, @@ -6084,26 +7650,26 @@ "extends": null, "additionalProperties": false }, - "type_:CombinedEntityStatus": { - "type": "enum", + "type_:DetailedOrg": { + "type": "object", "declaration": { "name": { - "originalName": "CombinedEntityStatus", + "originalName": "DetailedOrg", "camelCase": { - "unsafeName": "combinedEntityStatus", - "safeName": "combinedEntityStatus" + "unsafeName": "detailedOrg", + "safeName": "detailedOrg" }, "snakeCase": { - "unsafeName": "combined_entity_status", - "safeName": "combined_entity_status" + "unsafeName": "detailed_org", + "safeName": "detailed_org" }, "screamingSnakeCase": { - "unsafeName": "COMBINED_ENTITY_STATUS", - "safeName": "COMBINED_ENTITY_STATUS" + "unsafeName": "DETAILED_ORG", + "safeName": "DETAILED_ORG" }, "pascalCase": { - "unsafeName": "CombinedEntityStatus", - "safeName": "CombinedEntityStatus" + "unsafeName": "DetailedOrg", + "safeName": "DetailedOrg" } }, "fernFilepath": { @@ -6112,73 +7678,64 @@ "file": null } }, - "values": [ + "properties": [ { - "wireValue": "active", "name": { - "originalName": "active", - "camelCase": { - "unsafeName": "active", - "safeName": "active" - }, - "snakeCase": { - "unsafeName": "active", - "safeName": "active" - }, - "screamingSnakeCase": { - "unsafeName": "ACTIVE", - "safeName": "ACTIVE" - }, - "pascalCase": { - "unsafeName": "Active", - "safeName": "Active" + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } } - } - }, - { - "wireValue": "archived", - "name": { - "originalName": "archived", - "camelCase": { - "unsafeName": "archived", - "safeName": "archived" - }, - "snakeCase": { - "unsafeName": "archived", - "safeName": "archived" - }, - "screamingSnakeCase": { - "unsafeName": "ARCHIVED", - "safeName": "ARCHIVED" - }, - "pascalCase": { - "unsafeName": "Archived", - "safeName": "Archived" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:DetailedOrgMetadata" } - } + }, + "propertyAccess": null, + "variable": null } - ] + ], + "extends": null, + "additionalProperties": false }, - "type_:CombinedEntity": { + "type_:OrganizationMetadata": { "type": "object", "declaration": { "name": { - "originalName": "CombinedEntity", + "originalName": "OrganizationMetadata", "camelCase": { - "unsafeName": "combinedEntity", - "safeName": "combinedEntity" + "unsafeName": "organizationMetadata", + "safeName": "organizationMetadata" }, "snakeCase": { - "unsafeName": "combined_entity", - "safeName": "combined_entity" + "unsafeName": "organization_metadata", + "safeName": "organization_metadata" }, "screamingSnakeCase": { - "unsafeName": "COMBINED_ENTITY", - "safeName": "COMBINED_ENTITY" + "unsafeName": "ORGANIZATION_METADATA", + "safeName": "ORGANIZATION_METADATA" }, "pascalCase": { - "unsafeName": "CombinedEntity", - "safeName": "CombinedEntity" + "unsafeName": "OrganizationMetadata", + "safeName": "OrganizationMetadata" } }, "fernFilepath": { @@ -6190,24 +7747,24 @@ "properties": [ { "name": { - "wireValue": "id", + "wireValue": "region", "name": { - "originalName": "id", + "originalName": "region", "camelCase": { - "unsafeName": "id", - "safeName": "id" + "unsafeName": "region", + "safeName": "region" }, "snakeCase": { - "unsafeName": "id", - "safeName": "id" + "unsafeName": "region", + "safeName": "region" }, "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" + "unsafeName": "REGION", + "safeName": "REGION" }, "pascalCase": { - "unsafeName": "ID", - "safeName": "ID" + "unsafeName": "Region", + "safeName": "Region" } } }, @@ -6220,24 +7777,24 @@ }, { "name": { - "wireValue": "name", + "wireValue": "domain", "name": { - "originalName": "name", + "originalName": "domain", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "domain", + "safeName": "domain" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "domain", + "safeName": "domain" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "DOMAIN", + "safeName": "DOMAIN" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Domain", + "safeName": "Domain" } } }, @@ -6250,35 +7807,98 @@ }, "propertyAccess": null, "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Organization": { + "type": "object", + "declaration": { + "name": { + "originalName": "Organization", + "camelCase": { + "unsafeName": "organization", + "safeName": "organization" + }, + "snakeCase": { + "unsafeName": "organization", + "safeName": "organization" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION", + "safeName": "ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "Organization", + "safeName": "Organization" + } }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ { "name": { - "wireValue": "summary", + "wireValue": "id", "name": { - "originalName": "summary", + "originalName": "id", "camelCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "id", + "safeName": "id" }, "snakeCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "id", + "safeName": "id" }, "screamingSnakeCase": { - "unsafeName": "SUMMARY", - "safeName": "SUMMARY" + "unsafeName": "ID", + "safeName": "ID" }, "pascalCase": { - "unsafeName": "Summary", - "safeName": "Summary" + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" } } }, "typeReference": { "type": "optional", "value": { - "type": "primitive", - "value": "STRING" + "type": "named", + "value": "type_:OrganizationMetadata" } }, "propertyAccess": null, @@ -6286,30 +7906,30 @@ }, { "name": { - "wireValue": "status", + "wireValue": "name", "name": { - "originalName": "status", + "originalName": "name", "camelCase": { - "unsafeName": "status", - "safeName": "status" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "status", - "safeName": "status" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "STATUS", - "safeName": "STATUS" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Status", - "safeName": "Status" + "unsafeName": "Name", + "safeName": "Name" } } }, "typeReference": { - "type": "named", - "value": "type_:CombinedEntityStatus" + "type": "primitive", + "value": "STRING" }, "propertyAccess": null, "variable": null @@ -6650,6 +8270,48 @@ "type": "json" }, "examples": null + }, + "endpoint_.getOrganization": { + "auth": null, + "declaration": { + "name": { + "originalName": "getOrganization", + "camelCase": { + "unsafeName": "getOrganization", + "safeName": "getOrganization" + }, + "snakeCase": { + "unsafeName": "get_organization", + "safeName": "get_organization" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION", + "safeName": "GET_ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "GetOrganization", + "safeName": "GetOrganization" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/organizations" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null } }, "pathParameters": [], @@ -6719,7 +8381,13 @@ "type_:Identifiable", "type_:Describable", "type_:CombinedEntityStatus", - "type_:CombinedEntity" + "type_:CombinedEntity", + "type_:BaseOrgMetadata", + "type_:BaseOrg", + "type_:DetailedOrgMetadata", + "type_:DetailedOrg", + "type_:OrganizationMetadata", + "type_:Organization" ], "errors": [], "subpackages": [], From a042fe629e998055be14408eca133ef8090dee9d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:00:20 +0000 Subject: [PATCH 17/29] fix: update ir-generator-tests test-definitions for allof Co-Authored-By: bot_apk --- .../__test__/test-definitions/allof.json | 522 ++ .../ir/__test__/test-definitions/allof.json | 6934 ++++++++++------- 2 files changed, 4734 insertions(+), 2722 deletions(-) diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json index 02f579045a52..90136a2672c4 100644 --- a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/allof.json @@ -1686,6 +1686,486 @@ ], "extends": null, "additionalProperties": false + }, + "type_:BaseOrgMetadata": { + "type": "object", + "declaration": { + "name": { + "originalName": "BaseOrgMetadata", + "camelCase": { + "unsafeName": "baseOrgMetadata", + "safeName": "baseOrgMetadata" + }, + "snakeCase": { + "unsafeName": "base_org_metadata", + "safeName": "base_org_metadata" + }, + "screamingSnakeCase": { + "unsafeName": "BASE_ORG_METADATA", + "safeName": "BASE_ORG_METADATA" + }, + "pascalCase": { + "unsafeName": "BaseOrgMetadata", + "safeName": "BaseOrgMetadata" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "region", + "name": { + "originalName": "region", + "camelCase": { + "unsafeName": "region", + "safeName": "region" + }, + "snakeCase": { + "unsafeName": "region", + "safeName": "region" + }, + "screamingSnakeCase": { + "unsafeName": "REGION", + "safeName": "REGION" + }, + "pascalCase": { + "unsafeName": "Region", + "safeName": "Region" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "tier", + "name": { + "originalName": "tier", + "camelCase": { + "unsafeName": "tier", + "safeName": "tier" + }, + "snakeCase": { + "unsafeName": "tier", + "safeName": "tier" + }, + "screamingSnakeCase": { + "unsafeName": "TIER", + "safeName": "TIER" + }, + "pascalCase": { + "unsafeName": "Tier", + "safeName": "Tier" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:BaseOrg": { + "type": "object", + "declaration": { + "name": { + "originalName": "BaseOrg", + "camelCase": { + "unsafeName": "baseOrg", + "safeName": "baseOrg" + }, + "snakeCase": { + "unsafeName": "base_org", + "safeName": "base_org" + }, + "screamingSnakeCase": { + "unsafeName": "BASE_ORG", + "safeName": "BASE_ORG" + }, + "pascalCase": { + "unsafeName": "BaseOrg", + "safeName": "BaseOrg" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:BaseOrgMetadata" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:DetailedOrgMetadata": { + "type": "object", + "declaration": { + "name": { + "originalName": "DetailedOrgMetadata", + "camelCase": { + "unsafeName": "detailedOrgMetadata", + "safeName": "detailedOrgMetadata" + }, + "snakeCase": { + "unsafeName": "detailed_org_metadata", + "safeName": "detailed_org_metadata" + }, + "screamingSnakeCase": { + "unsafeName": "DETAILED_ORG_METADATA", + "safeName": "DETAILED_ORG_METADATA" + }, + "pascalCase": { + "unsafeName": "DetailedOrgMetadata", + "safeName": "DetailedOrgMetadata" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "region", + "name": { + "originalName": "region", + "camelCase": { + "unsafeName": "region", + "safeName": "region" + }, + "snakeCase": { + "unsafeName": "region", + "safeName": "region" + }, + "screamingSnakeCase": { + "unsafeName": "REGION", + "safeName": "REGION" + }, + "pascalCase": { + "unsafeName": "Region", + "safeName": "Region" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "domain", + "name": { + "originalName": "domain", + "camelCase": { + "unsafeName": "domain", + "safeName": "domain" + }, + "snakeCase": { + "unsafeName": "domain", + "safeName": "domain" + }, + "screamingSnakeCase": { + "unsafeName": "DOMAIN", + "safeName": "DOMAIN" + }, + "pascalCase": { + "unsafeName": "Domain", + "safeName": "Domain" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:DetailedOrg": { + "type": "object", + "declaration": { + "name": { + "originalName": "DetailedOrg", + "camelCase": { + "unsafeName": "detailedOrg", + "safeName": "detailedOrg" + }, + "snakeCase": { + "unsafeName": "detailed_org", + "safeName": "detailed_org" + }, + "screamingSnakeCase": { + "unsafeName": "DETAILED_ORG", + "safeName": "DETAILED_ORG" + }, + "pascalCase": { + "unsafeName": "DetailedOrg", + "safeName": "DetailedOrg" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:DetailedOrgMetadata" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:Organization": { + "type": "object", + "declaration": { + "name": { + "originalName": "Organization", + "camelCase": { + "unsafeName": "organization", + "safeName": "organization" + }, + "snakeCase": { + "unsafeName": "organization", + "safeName": "organization" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION", + "safeName": "ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "Organization", + "safeName": "Organization" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:BaseOrgMetadata" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false } }, "headers": [], @@ -2019,6 +2499,48 @@ "type": "json" }, "examples": null + }, + "endpoint_.getOrganization": { + "auth": null, + "declaration": { + "name": { + "originalName": "getOrganization", + "camelCase": { + "unsafeName": "getOrganization", + "safeName": "getOrganization" + }, + "snakeCase": { + "unsafeName": "get_organization", + "safeName": "get_organization" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION", + "safeName": "GET_ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "GetOrganization", + "safeName": "GetOrganization" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/organizations" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null } }, "pathParameters": [], diff --git a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json index 5b073307626a..8d8a67d109d9 100644 --- a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json +++ b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/allof.json @@ -1339,134 +1339,2460 @@ "v2Examples": null, "availability": null, "docs": null - } - }, - "errors": {}, - "services": { - "service_": { - "availability": null, + }, + "type_:BaseOrgMetadata": { + "inline": true, "name": { + "name": "BaseOrgMetadata", "fernFilepath": { "allParts": [], "packagePath": [], "file": null - } + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata" }, - "displayName": null, - "basePath": { - "head": "", - "parts": [] + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "region", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Deployment region from BaseOrg." + }, + { + "name": "tier", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Subscription tier." + } + ], + "extra-properties": false, + "extendedProperties": [] }, - "headers": [], - "pathParameters": [], + "referencedTypes": [], "encoding": { "json": {}, "proto": null }, - "transport": { - "type": "http" + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:BaseOrg": { + "inline": null, + "name": { + "name": "BaseOrg", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrg" }, - "endpoints": [ - { - "id": "endpoint_.searchRuleTypes", - "name": "searchRuleTypes", - "displayName": "Search rule types with paginated results", - "auth": false, - "security": null, - "idempotent": false, - "baseUrl": null, - "v2BaseUrls": null, - "method": "GET", - "basePath": null, - "path": { - "head": "/rule-types", - "parts": [] - }, - "fullPath": { - "head": "rule-types", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": "query", - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null } - }, - "allowMultiple": false, - "clientDefault": null, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - }, - "explode": null, - "availability": null, - "docs": null - } - ], - "headers": [], - "requestBody": null, - "v2RequestBodies": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": "SearchRuleTypesRequest", - "bodyKey": "body", - "includePathParameters": false, - "onlyPathParameters": false + } }, - "requestParameterName": "request", - "streamParameter": null + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null }, - "response": { - "body": { - "type": "json", - "value": { - "type": "response", - "responseBodyType": { + { + "name": "metadata", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { "_type": "named", - "name": "RuleTypeSearchResponse", + "name": "BaseOrgMetadata", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:BaseOrgMetadata", "default": null, "inline": null - }, - "docs": "Paginated list of rule types", - "v2Examples": null + } } }, - "status-code": 200, - "isWildcardStatusCode": null, - "docs": "Paginated list of rule types" + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:BaseOrgMetadata" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:DetailedOrgMetadata": { + "inline": true, + "name": { + "name": "DetailedOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:DetailedOrgMetadata" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "region", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Deployment region from DetailedOrg." }, - "v2Responses": null, - "errors": [], - "userSpecifiedExamples": [ - { - "example": { - "id": "b6434d4c", - "name": null, + { + "name": "domain", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": "Custom domain name." + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:DetailedOrg": { + "inline": null, + "name": { + "name": "DetailedOrg", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:DetailedOrg" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "metadata", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": "DetailedOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:DetailedOrgMetadata", + "default": null, + "inline": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:DetailedOrgMetadata" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:Organization": { + "inline": null, + "name": { + "name": "Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Organization" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "id", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": "metadata", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": "BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata", + "default": null, + "inline": null + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:BaseOrgMetadata" + ], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "displayName": null, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {}, + "proto": null + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_.searchRuleTypes", + "name": "searchRuleTypes", + "displayName": "Search rule types with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/rule-types", + "parts": [] + }, + "fullPath": { + "head": "rule-types", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [ + { + "name": "query", + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "allowMultiple": false, + "clientDefault": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "explode": null, + "availability": null, + "docs": null + } + ], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "SearchRuleTypesRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of rule types", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of rule types" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "b6434d4c", + "name": null, + "url": "/rule-types", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "paging", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "previous", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "originalTypeDeclaration": { + "typeId": "type_:PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "PagingCursors", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "results", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "description", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleType", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleTypeSearchResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "paging": { + "next": "next", + "previous": "previous" + }, + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + } + ] + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "fa817ef7", "url": "/rule-types", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "results", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + }, + { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "description", + "originalTypeDeclaration": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "description" + } + } + }, + "jsonExample": "description" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "description" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "description": "description" + } + } + ], + "itemType": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": "RuleType", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleType", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ] + }, + "propertyAccess": null + }, + { + "name": "paging", + "originalTypeDeclaration": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "next", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "next" + } + } + }, + "jsonExample": "next" + }, + "propertyAccess": null + }, + { + "name": "previous", + "originalTypeDeclaration": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "previous" + } + } + }, + "jsonExample": "previous" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "previous" + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:PagingCursors" + } + }, + "jsonExample": { + "next": "next", + "previous": "previous" + } + }, + "propertyAccess": null + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleTypeSearchResponse" + } + }, + "jsonExample": { + "results": [ + { + "id": "id", + "name": "name", + "description": "description" + }, + { + "id": "id", + "name": "name", + "description": "description" + } + ], + "paging": { + "next": "next", + "previous": "previous" + } + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.createRule", + "name": "createRule", + "displayName": "Create a rule with constrained execution context", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/rules", + "parts": [] + }, + "fullPath": { + "head": "rules", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "inlinedRequestBody", + "name": "RuleCreateRequest", + "extends": [], + "properties": [ + { + "name": "name", + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + }, + { + "name": "executionContext", + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [], + "docs": null, + "v2Examples": null, + "contentType": "application/json" + }, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": "RuleCreateRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false + }, + "requestParameterName": "request", + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse", + "default": null, + "inline": null + }, + "docs": "Created rule", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Created rule" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "c1cf878e", + "name": null, + "url": "/rules", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": null + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "createdBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "originalTypeDeclaration": { + "name": "AuditInfo", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:AuditInfo" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "id", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "status", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponseStatus", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "active" + } + }, + "jsonExample": "active" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleExecutionContext", + "displayName": null + }, + "shape": { + "type": "enum", + "value": "prod" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "originalTypeDeclaration": { + "typeId": "type_:RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "RuleResponse", + "displayName": null + }, + "propertyAccess": null + } + ], + "extraProperties": null + } + }, + "jsonExample": { + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z", + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod" + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "4c8a497e", + "url": "/rules", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": "name", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + } + }, + { + "name": "executionContext", + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + } + } + ], + "extraProperties": null, + "jsonExample": { + "name": "name", + "executionContext": "prod" + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "id", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "propertyAccess": null + }, + { + "name": "name", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "status", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "active" + }, + "typeName": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponseStatus" + } + }, + "jsonExample": "active" + }, + "propertyAccess": null + }, + { + "name": "executionContext", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "shape": { + "type": "enum", + "value": "prod" + }, + "typeName": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext" + } + }, + "jsonExample": "prod" + }, + "valueType": { + "_type": "named", + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleExecutionContext", + "default": null, + "inline": null + } + } + }, + "jsonExample": "prod" + }, + "propertyAccess": null + }, + { + "name": "createdBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "createdBy" + } + } + }, + "jsonExample": "createdBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "createdBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "createdDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedBy", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "modifiedBy" + } + } + }, + "jsonExample": "modifiedBy" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "modifiedBy" + }, + "propertyAccess": "READ_ONLY" + }, + { + "name": "modifiedDateTime", + "originalTypeDeclaration": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "datetime", + "datetime": "2024-01-15T09:30:00.000Z", + "raw": "2024-01-15T09:30:00Z" + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DATE_TIME", + "v2": null + } + } + } + }, + "jsonExample": "2024-01-15T09:30:00Z" + }, + "propertyAccess": "READ_ONLY" + } + ], + "extraProperties": null + }, + "typeName": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:RuleResponse" + } + }, + "jsonExample": { + "id": "id", + "name": "name", + "status": "active", + "executionContext": "prod", + "createdBy": "createdBy", + "createdDateTime": "2024-01-15T09:30:00Z", + "modifiedBy": "modifiedBy", + "modifiedDateTime": "2024-01-15T09:30:00Z" + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + }, + { + "id": "endpoint_.listUsers", + "name": "listUsers", + "displayName": "List users with paginated results", + "auth": false, + "security": null, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/users", + "parts": [] + }, + "fullPath": { + "head": "users", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": null, + "v2RequestBodies": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": "UserSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:UserSearchResponse", + "default": null, + "inline": null + }, + "docs": "Paginated list of users", + "v2Examples": null + } + }, + "status-code": 200, + "isWildcardStatusCode": null, + "docs": "Paginated list of users" + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "55942cbc", + "name": null, + "url": "/users", "rootPathParameters": [], "endpointPathParameters": [], "servicePathParameters": [], @@ -1482,13 +3808,13 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "displayName": null }, "shape": { @@ -1594,13 +3920,13 @@ } }, "originalTypeDeclaration": { - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "displayName": null }, "propertyAccess": null @@ -1622,13 +3948,13 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleType", + "typeId": "type_:User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleType", + "name": "User", "displayName": null }, "shape": { @@ -1649,85 +3975,39 @@ "jsonExample": "id" }, "originalTypeDeclaration": { - "typeId": "type_:RuleType", + "typeId": "type_:User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleType", + "name": "User", "displayName": null }, "propertyAccess": null }, { - "name": "name", + "name": "email", "value": { "shape": { "type": "primitive", "primitive": { "type": "string", "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleType", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleType", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "description", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "description" - } - } - }, - "jsonExample": "description" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } + "original": "email" } } }, - "jsonExample": "description" + "jsonExample": "email" }, "originalTypeDeclaration": { - "typeId": "type_:RuleType", + "typeId": "type_:User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleType", + "name": "User", "displayName": null }, "propertyAccess": null @@ -1738,21 +4018,20 @@ }, "jsonExample": { "id": "id", - "name": "name", - "description": "description" + "email": "email" } } ], "itemType": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -1761,8 +4040,7 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, @@ -1772,14 +4050,14 @@ "_type": "list", "list": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -1790,19 +4068,18 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, "originalTypeDeclaration": { - "typeId": "type_:RuleTypeSearchResponse", + "typeId": "type_:UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "displayName": null }, "propertyAccess": null @@ -1819,8 +4096,7 @@ "results": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] } @@ -1835,8 +4111,8 @@ "autogeneratedExamples": [ { "example": { - "id": "fa817ef7", - "url": "/rule-types", + "id": "53509ec7", + "url": "/users", "name": null, "endpointHeaders": [], "endpointPathParameters": [], @@ -1858,14 +4134,14 @@ { "name": "results", "originalTypeDeclaration": { - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse" + "typeId": "type_:UserSearchResponse" }, "value": { "shape": { @@ -1887,14 +4163,14 @@ { "name": "id", "originalTypeDeclaration": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" }, "value": { "shape": { @@ -1911,16 +4187,16 @@ "propertyAccess": null }, { - "name": "name", + "name": "email", "originalTypeDeclaration": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" }, "value": { "shape": { @@ -1928,57 +4204,11 @@ "primitive": { "type": "string", "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "propertyAccess": null - }, - { - "name": "description", - "originalTypeDeclaration": { - "name": "RuleType", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleType" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "description" - } - } - }, - "jsonExample": "description" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } + "original": "email" } } }, - "jsonExample": "description" + "jsonExample": "email" }, "propertyAccess": null } @@ -1986,20 +4216,19 @@ "extraProperties": null }, "typeName": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" } }, "jsonExample": { "id": "id", - "name": "name", - "description": "description" + "email": "email" } }, { @@ -2011,14 +4240,14 @@ { "name": "id", "originalTypeDeclaration": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" }, "value": { "shape": { @@ -2035,16 +4264,16 @@ "propertyAccess": null }, { - "name": "name", + "name": "email", "originalTypeDeclaration": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" }, "value": { "shape": { @@ -2052,57 +4281,11 @@ "primitive": { "type": "string", "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "propertyAccess": null - }, - { - "name": "description", - "originalTypeDeclaration": { - "name": "RuleType", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleType" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "description" - } - } - }, - "jsonExample": "description" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } + "original": "email" } } }, - "jsonExample": "description" + "jsonExample": "email" }, "propertyAccess": null } @@ -2110,33 +4293,32 @@ "extraProperties": null }, "typeName": { - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType" + "typeId": "type_:User" } }, "jsonExample": { "id": "id", - "name": "name", - "description": "description" + "email": "email" } } ], "itemType": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -2145,13 +4327,11 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" }, { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, @@ -2161,14 +4341,14 @@ "_type": "list", "list": { "_type": "named", - "name": "RuleType", + "name": "User", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleType", + "typeId": "type_:User", "default": null, "inline": null } @@ -2179,13 +4359,11 @@ "jsonExample": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" }, { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ] }, @@ -2194,14 +4372,14 @@ { "name": "paging", "originalTypeDeclaration": { - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse" + "typeId": "type_:UserSearchResponse" }, "value": { "shape": { @@ -2306,27 +4484,25 @@ "extraProperties": null }, "typeName": { - "name": "RuleTypeSearchResponse", + "name": "UserSearchResponse", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleTypeSearchResponse" + "typeId": "type_:UserSearchResponse" } }, "jsonExample": { "results": [ { "id": "id", - "name": "name", - "description": "description" + "email": "email" }, { "id": "id", - "name": "name", - "description": "description" + "email": "email" } ], "paging": { @@ -2353,96 +4529,31 @@ "docs": null }, { - "id": "endpoint_.createRule", - "name": "createRule", - "displayName": "Create a rule with constrained execution context", + "id": "endpoint_.getEntity", + "name": "getEntity", + "displayName": "Get an entity that combines multiple parents", "auth": false, "security": null, "idempotent": false, "baseUrl": null, "v2BaseUrls": null, - "method": "POST", + "method": "GET", "basePath": null, "path": { - "head": "/rules", + "head": "/entities", "parts": [] }, "fullPath": { - "head": "rules", + "head": "entities", "parts": [] }, "pathParameters": [], "allPathParameters": [], "queryParameters": [], "headers": [], - "requestBody": { - "type": "inlinedRequestBody", - "name": "RuleCreateRequest", - "extends": [], - "properties": [ - { - "name": "name", - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - }, - "propertyAccess": null, - "availability": null, - "docs": null - }, - { - "name": "executionContext", - "valueType": { - "_type": "named", - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext", - "default": null, - "inline": null - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - }, - "propertyAccess": null, - "availability": null, - "docs": null - } - ], - "extra-properties": false, - "extendedProperties": [], - "docs": null, - "v2Examples": null, - "contentType": "application/json" - }, + "requestBody": null, "v2RequestBodies": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": "RuleCreateRequest", - "bodyKey": "body", - "includePathParameters": false, - "onlyPathParameters": false - }, - "requestParameterName": "request", - "streamParameter": null - }, + "sdkRequest": null, "response": { "body": { "type": "json", @@ -2450,89 +4561,40 @@ "type": "response", "responseBodyType": { "_type": "named", - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "default": null, "inline": null }, - "docs": "Created rule", + "docs": "An entity with properties from multiple parents", "v2Examples": null } }, "status-code": 200, "isWildcardStatusCode": null, - "docs": "Created rule" + "docs": "An entity with properties from multiple parents" }, "v2Responses": null, "errors": [], "userSpecifiedExamples": [ { "example": { - "id": "c1cf878e", + "id": "b2b07150", "name": null, - "url": "/rules", + "url": "/entities", "rootPathParameters": [], "endpointPathParameters": [], "servicePathParameters": [], "endpointHeaders": [], "serviceHeaders": [], "queryParameters": [], - "request": { - "type": "inlinedRequestBody", - "properties": [ - { - "name": "name", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "originalTypeDeclaration": null - }, - { - "name": "executionContext", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleExecutionContext", - "displayName": null - }, - "shape": { - "type": "enum", - "value": "prod" - } - }, - "jsonExample": "prod" - }, - "originalTypeDeclaration": null - } - ], - "extraProperties": null, - "jsonExample": { - "name": "name", - "executionContext": "prod" - } - }, + "request": null, "response": { "type": "ok", "value": { @@ -2541,107 +4603,20 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, "shape": { "type": "object", "properties": [ { - "name": "createdBy", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "createdBy" - } - } - }, - "jsonExample": "createdBy" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "createdBy" - }, - "originalTypeDeclaration": { - "name": "AuditInfo", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:AuditInfo" - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "createdDateTime", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" - } - }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "DATE_TIME", - "v2": null - } - } - } - }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "originalTypeDeclaration": { - "name": "AuditInfo", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:AuditInfo" - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "modifiedBy", + "name": "name", "value": { "shape": { "type": "container", @@ -2653,11 +4628,11 @@ "primitive": { "type": "string", "string": { - "original": "modifiedBy" + "original": "name" } } }, - "jsonExample": "modifiedBy" + "jsonExample": "name" }, "valueType": { "_type": "primitive", @@ -2672,22 +4647,22 @@ } } }, - "jsonExample": "modifiedBy" + "jsonExample": "name" }, "originalTypeDeclaration": { - "name": "AuditInfo", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "displayName": null, - "typeId": "type_:AuditInfo" + "name": "CombinedEntity", + "displayName": null }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null }, { - "name": "modifiedDateTime", + "name": "summary", "value": { "shape": { "type": "container", @@ -2697,35 +4672,40 @@ "shape": { "type": "primitive", "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" + "type": "string", + "string": { + "original": "summary" + } } }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "summary" }, "valueType": { "_type": "primitive", "primitive": { - "v1": "DATE_TIME", - "v2": null + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } } } } }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "summary" }, "originalTypeDeclaration": { - "name": "AuditInfo", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "displayName": null, - "typeId": "type_:AuditInfo" + "name": "CombinedEntity", + "displayName": null }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null }, { "name": "id", @@ -2742,39 +4722,13 @@ "jsonExample": "id" }, "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleResponse", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "name", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, "propertyAccess": null @@ -2785,13 +4739,13 @@ "shape": { "type": "named", "typeName": { - "typeId": "type_:RuleResponseStatus", + "typeId": "type_:CombinedEntityStatus", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponseStatus", + "name": "CombinedEntityStatus", "displayName": null }, "shape": { @@ -2802,69 +4756,13 @@ "jsonExample": "active" }, "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleResponse", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "executionContext", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "RuleExecutionContext", - "displayName": null - }, - "shape": { - "type": "enum", - "value": "prod" - } - }, - "jsonExample": "prod" - }, - "valueType": { - "_type": "named", - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext", - "default": null, - "inline": null - } - } - }, - "jsonExample": "prod" - }, - "originalTypeDeclaration": { - "typeId": "type_:RuleResponse", + "typeId": "type_:CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "RuleResponse", + "name": "CombinedEntity", "displayName": null }, "propertyAccess": null @@ -2874,14 +4772,10 @@ } }, "jsonExample": { - "createdBy": "createdBy", - "createdDateTime": "2024-01-15T09:30:00Z", - "modifiedBy": "modifiedBy", - "modifiedDateTime": "2024-01-15T09:30:00Z", - "id": "id", "name": "name", - "status": "active", - "executionContext": "prod" + "summary": "summary", + "id": "id", + "status": "active" } } } @@ -2894,138 +4788,37 @@ "autogeneratedExamples": [ { "example": { - "id": "4c8a497e", - "url": "/rules", + "id": "dc0a034f", + "url": "/entities", "name": null, "endpointHeaders": [], "endpointPathParameters": [], "queryParameters": [], - "servicePathParameters": [], - "serviceHeaders": [], - "rootPathParameters": [], - "request": { - "type": "inlinedRequestBody", - "properties": [ - { - "name": "name", - "originalTypeDeclaration": null, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - } - }, - { - "name": "executionContext", - "originalTypeDeclaration": null, - "value": { - "shape": { - "type": "named", - "shape": { - "type": "enum", - "value": "prod" - }, - "typeName": { - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext" - } - }, - "jsonExample": "prod" - } - } - ], - "extraProperties": null, - "jsonExample": { - "name": "name", - "executionContext": "prod" - } - }, - "response": { - "type": "ok", - "value": { - "type": "body", - "value": { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "propertyAccess": null - }, - { - "name": "name", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "propertyAccess": null - }, + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ { "name": "status", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { @@ -3035,14 +4828,14 @@ "value": "active" }, "typeName": { - "name": "RuleResponseStatus", + "name": "CombinedEntityStatus", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponseStatus" + "typeId": "type_:CombinedEntityStatus" } }, "jsonExample": "active" @@ -3050,72 +4843,42 @@ "propertyAccess": null }, { - "name": "executionContext", + "name": "id", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "named", - "shape": { - "type": "enum", - "value": "prod" - }, - "typeName": { - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext" - } - }, - "jsonExample": "prod" - }, - "valueType": { - "_type": "named", - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleExecutionContext", - "default": null, - "inline": null + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" } } }, - "jsonExample": "prod" + "jsonExample": "id" }, "propertyAccess": null }, { - "name": "createdBy", + "name": "name", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { @@ -3128,11 +4891,11 @@ "primitive": { "type": "string", "string": { - "original": "createdBy" + "original": "name" } } }, - "jsonExample": "createdBy" + "jsonExample": "name" }, "valueType": { "_type": "primitive", @@ -3147,62 +4910,21 @@ } } }, - "jsonExample": "createdBy" - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "createdDateTime", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" - } - }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "DATE_TIME", - "v2": null - } - } - } - }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "name" }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null }, { - "name": "modifiedBy", + "name": "summary", "originalTypeDeclaration": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" }, "value": { "shape": { @@ -3215,11 +4937,11 @@ "primitive": { "type": "string", "string": { - "original": "modifiedBy" + "original": "summary" } } }, - "jsonExample": "modifiedBy" + "jsonExample": "summary" }, "valueType": { "_type": "primitive", @@ -3234,74 +4956,29 @@ } } }, - "jsonExample": "modifiedBy" - }, - "propertyAccess": "READ_ONLY" - }, - { - "name": "modifiedDateTime", - "originalTypeDeclaration": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:RuleResponse" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "datetime", - "datetime": "2024-01-15T09:30:00.000Z", - "raw": "2024-01-15T09:30:00Z" - } - }, - "jsonExample": "2024-01-15T09:30:00Z" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "DATE_TIME", - "v2": null - } - } - } - }, - "jsonExample": "2024-01-15T09:30:00Z" + "jsonExample": "summary" }, - "propertyAccess": "READ_ONLY" + "propertyAccess": null } ], "extraProperties": null }, "typeName": { - "name": "RuleResponse", + "name": "CombinedEntity", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:RuleResponse" + "typeId": "type_:CombinedEntity" } }, "jsonExample": { + "status": "active", "id": "id", "name": "name", - "status": "active", - "executionContext": "prod", - "createdBy": "createdBy", - "createdDateTime": "2024-01-15T09:30:00Z", - "modifiedBy": "modifiedBy", - "modifiedDateTime": "2024-01-15T09:30:00Z" + "summary": "summary" } } } @@ -3322,9 +4999,9 @@ "docs": null }, { - "id": "endpoint_.listUsers", - "name": "listUsers", - "displayName": "List users with paginated results", + "id": "endpoint_.getOrganization", + "name": "getOrganization", + "displayName": "Get an organization with merged object-typed properties", "auth": false, "security": null, "idempotent": false, @@ -3333,11 +5010,11 @@ "method": "GET", "basePath": null, "path": { - "head": "/users", + "head": "/organizations", "parts": [] }, "fullPath": { - "head": "users", + "head": "organizations", "parts": [] }, "pathParameters": [], @@ -3354,33 +5031,33 @@ "type": "response", "responseBodyType": { "_type": "named", - "name": "UserSearchResponse", + "name": "Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:UserSearchResponse", + "typeId": "type_:Organization", "default": null, "inline": null }, - "docs": "Paginated list of users", + "docs": "An organization whose metadata is merged from two parents", "v2Examples": null } }, "status-code": 200, "isWildcardStatusCode": null, - "docs": "Paginated list of users" + "docs": "An organization whose metadata is merged from two parents" }, "v2Responses": null, "errors": [], "userSpecifiedExamples": [ { "example": { - "id": "55942cbc", + "id": "fd2e2e41", "name": null, - "url": "/users", + "url": "/organizations", "rootPathParameters": [], "endpointPathParameters": [], "servicePathParameters": [], @@ -3389,285 +5066,209 @@ "queryParameters": [], "request": null, "response": { - "type": "ok", - "value": { - "type": "body", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:UserSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "UserSearchResponse", - "displayName": null - }, - "shape": { - "type": "object", - "properties": [ - { - "name": "paging", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "PagingCursors", - "displayName": null - }, - "shape": { - "type": "object", - "properties": [ - { - "name": "next", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "next" - } - } - }, - "jsonExample": "next" - }, - "originalTypeDeclaration": { - "typeId": "type_:PagingCursors", + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "Organization", + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": "metadata", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:BaseOrgMetadata", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "PagingCursors", + "name": "BaseOrgMetadata", "displayName": null }, - "propertyAccess": null - }, - { - "name": "previous", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { + "shape": { + "type": "object", + "properties": [ + { + "name": "region", + "value": { "shape": { "type": "primitive", "primitive": { "type": "string", "string": { - "original": "previous" + "original": "region" } } }, - "jsonExample": "previous" + "jsonExample": "region" }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null + "originalTypeDeclaration": { + "typeId": "type_:BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "BaseOrgMetadata", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "tier", + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tier" + } + } + }, + "jsonExample": "tier" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } } - } - } + }, + "jsonExample": "tier" + }, + "originalTypeDeclaration": { + "typeId": "type_:BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "BaseOrgMetadata", + "displayName": null + }, + "propertyAccess": null } - }, - "jsonExample": "previous" - }, - "originalTypeDeclaration": { - "typeId": "type_:PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "PagingCursors", - "displayName": null - }, - "propertyAccess": null + ], + "extraProperties": null + } + }, + "jsonExample": { + "region": "region", + "tier": "tier" } - ], - "extraProperties": null + }, + "valueType": { + "_type": "named", + "name": "BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata", + "default": null, + "inline": null + } } }, "jsonExample": { - "next": "next", - "previous": "previous" + "region": "region", + "tier": "tier" } }, "originalTypeDeclaration": { - "typeId": "type_:UserSearchResponse", + "typeId": "type_:Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "UserSearchResponse", + "name": "Organization", "displayName": null }, "propertyAccess": null }, { - "name": "results", + "name": "id", "value": { "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "User", - "displayName": null - }, - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "originalTypeDeclaration": { - "typeId": "type_:User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "User", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "email", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "email" - } - } - }, - "jsonExample": "email" - }, - "originalTypeDeclaration": { - "typeId": "type_:User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "User", - "displayName": null - }, - "propertyAccess": null - } - ], - "extraProperties": null - } - }, - "jsonExample": { - "id": "id", - "email": "email" - } - } - ], - "itemType": { - "_type": "named", - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null - } - } - }, - "jsonExample": [ - { - "id": "id", - "email": "email" - } - ] - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null - } - } + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + }, + "originalTypeDeclaration": { + "typeId": "type_:Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": "Organization", + "displayName": null + }, + "propertyAccess": null + }, + { + "name": "name", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" } } }, - "jsonExample": [ - { - "id": "id", - "email": "email" - } - ] + "jsonExample": "name" }, "originalTypeDeclaration": { - "typeId": "type_:UserSearchResponse", + "typeId": "type_:Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, - "name": "UserSearchResponse", + "name": "Organization", "displayName": null }, "propertyAccess": null @@ -3677,16 +5278,12 @@ } }, "jsonExample": { - "paging": { - "next": "next", - "previous": "previous" + "metadata": { + "region": "region", + "tier": "tier" }, - "results": [ - { - "id": "id", - "email": "email" - } - ] + "id": "id", + "name": "name" } } } @@ -3699,8 +5296,8 @@ "autogeneratedExamples": [ { "example": { - "id": "53509ec7", - "url": "/users", + "id": "b2f0fc0e", + "url": "/organizations", "name": null, "endpointHeaders": [], "endpointPathParameters": [], @@ -3708,362 +5305,201 @@ "servicePathParameters": [], "serviceHeaders": [], "rootPathParameters": [], - "request": null, - "response": { - "type": "ok", - "value": { - "type": "body", - "value": { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "results", - "originalTypeDeclaration": { - "name": "UserSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:UserSearchResponse" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "propertyAccess": null - }, - { - "name": "email", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "email" - } - } - }, - "jsonExample": "email" - }, - "propertyAccess": null - } - ], - "extraProperties": null - }, - "typeName": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - } - }, - "jsonExample": { - "id": "id", - "email": "email" - } - }, - { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "propertyAccess": null - }, - { - "name": "email", - "originalTypeDeclaration": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "email" - } - } - }, - "jsonExample": "email" - }, - "propertyAccess": null - } - ], - "extraProperties": null - }, - "typeName": { - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User" - } - }, - "jsonExample": { - "id": "id", - "email": "email" - } - } - ], - "itemType": { - "_type": "named", - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null - } - } - }, - "jsonExample": [ - { - "id": "id", - "email": "email" - }, - { - "id": "id", - "email": "email" - } - ] - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": "User", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:User", - "default": null, - "inline": null - } - } + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "name", + "originalTypeDeclaration": { + "name": "Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Organization" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" } } }, - "jsonExample": [ - { - "id": "id", - "email": "email" - }, - { - "id": "id", - "email": "email" + "jsonExample": "name" + }, + "propertyAccess": null + }, + { + "name": "id", + "originalTypeDeclaration": { + "name": "Organization", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:Organization" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } } - ] + }, + "jsonExample": "id" }, "propertyAccess": null }, { - "name": "paging", + "name": "metadata", "originalTypeDeclaration": { - "name": "UserSearchResponse", + "name": "Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:UserSearchResponse" + "typeId": "type_:Organization" }, "value": { "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "next", - "originalTypeDeclaration": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:PagingCursors" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "next" - } - } - }, - "jsonExample": "next" - }, - "propertyAccess": null - }, - { - "name": "previous", - "originalTypeDeclaration": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:PagingCursors" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": "region", + "originalTypeDeclaration": { + "name": "BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata" + }, + "value": { "shape": { "type": "primitive", "primitive": { "type": "string", "string": { - "original": "previous" + "original": "region" } } }, - "jsonExample": "previous" + "jsonExample": "region" }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null + "propertyAccess": null + }, + { + "name": "tier", + "originalTypeDeclaration": { + "name": "BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tier" + } + } + }, + "jsonExample": "tier" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } } - } - } + }, + "jsonExample": "tier" + }, + "propertyAccess": null } - }, - "jsonExample": "previous" + ], + "extraProperties": null }, - "propertyAccess": null + "typeName": { + "name": "BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata" + } + }, + "jsonExample": { + "region": "region", + "tier": "tier" } - ], - "extraProperties": null - }, - "typeName": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null }, - "displayName": null, - "typeId": "type_:PagingCursors" + "valueType": { + "_type": "named", + "name": "BaseOrgMetadata", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:BaseOrgMetadata", + "default": null, + "inline": null + } } }, "jsonExample": { - "next": "next", - "previous": "previous" + "region": "region", + "tier": "tier" } }, "propertyAccess": null @@ -4072,601 +5508,707 @@ "extraProperties": null }, "typeName": { - "name": "UserSearchResponse", + "name": "Organization", "fernFilepath": { "allParts": [], "packagePath": [], "file": null }, "displayName": null, - "typeId": "type_:UserSearchResponse" + "typeId": "type_:Organization" } }, "jsonExample": { - "results": [ - { - "id": "id", - "email": "email" - }, - { - "id": "id", - "email": "email" - } - ], - "paging": { - "next": "next", - "previous": "previous" + "name": "name", + "id": "id", + "metadata": { + "region": "region", + "tier": "tier" } } } } }, - "docs": null + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "retries": null, + "apiPlayground": null, + "responseHeaders": [], + "availability": null, + "docs": null + } + ], + "audiences": null + } + }, + "constants": { + "errorInstanceIdKey": "errorInstanceId" + }, + "environments": { + "defaultEnvironment": "Default", + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Default", + "name": "Default", + "url": "https://api.example.com", + "audiences": null, + "defaultUrl": null, + "urlTemplate": null, + "urlVariables": null, + "docs": null + } + ] + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_": [ + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:AuditInfo", + "type_:RuleType", + "type_:RuleTypeSearchResponse", + "type_:User", + "type_:UserSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse", + "type_:CombinedEntityStatus", + "type_:CombinedEntity", + "type_:BaseOrgMetadata", + "type_:Organization" + ] + }, + "sharedTypes": [ + "type_:PaginatedResult", + "type_:Identifiable", + "type_:Describable", + "type_:BaseOrg", + "type_:DetailedOrgMetadata", + "type_:DetailedOrg" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "publishConfig": null, + "dynamic": { + "version": "1.0.0", + "types": { + "type_:PaginatedResult": { + "type": "object", + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "type": "named", + "value": "type_:PagingCursors" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "list", + "value": { + "type": "unknown" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:PagingCursors": { + "type": "object", + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleExecutionContext": { + "type": "enum", + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" } } - ], - "pagination": null, - "transport": null, - "v2Examples": null, - "source": null, - "audiences": null, - "retries": null, - "apiPlayground": null, - "responseHeaders": [], - "availability": null, - "docs": null + }, + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } + }, + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ] + }, + "type_:AuditInfo": { + "type": "object", + "declaration": { + "name": { + "originalName": "AuditInfo", + "camelCase": { + "unsafeName": "auditInfo", + "safeName": "auditInfo" + }, + "snakeCase": { + "unsafeName": "audit_info", + "safeName": "audit_info" + }, + "screamingSnakeCase": { + "unsafeName": "AUDIT_INFO", + "safeName": "AUDIT_INFO" + }, + "pascalCase": { + "unsafeName": "AuditInfo", + "safeName": "AuditInfo" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } }, - { - "id": "endpoint_.getEntity", - "name": "getEntity", - "displayName": "Get an entity that combines multiple parents", - "auth": false, - "security": null, - "idempotent": false, - "baseUrl": null, - "v2BaseUrls": null, - "method": "GET", - "basePath": null, - "path": { - "head": "/entities", - "parts": [] + "properties": [ + { + "name": { + "wireValue": "createdBy", + "name": { + "originalName": "createdBy", + "camelCase": { + "unsafeName": "createdBy", + "safeName": "createdBy" + }, + "snakeCase": { + "unsafeName": "created_by", + "safeName": "created_by" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_BY", + "safeName": "CREATED_BY" + }, + "pascalCase": { + "unsafeName": "CreatedBy", + "safeName": "CreatedBy" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null }, - "fullPath": { - "head": "entities", - "parts": [] + { + "name": { + "wireValue": "createdDateTime", + "name": { + "originalName": "createdDateTime", + "camelCase": { + "unsafeName": "createdDateTime", + "safeName": "createdDateTime" + }, + "snakeCase": { + "unsafeName": "created_date_time", + "safeName": "created_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "CREATED_DATE_TIME", + "safeName": "CREATED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "CreatedDateTime", + "safeName": "CreatedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [], - "headers": [], - "requestBody": null, - "v2RequestBodies": null, - "sdkRequest": null, - "response": { - "body": { - "type": "json", + { + "name": { + "wireValue": "modifiedBy", + "name": { + "originalName": "modifiedBy", + "camelCase": { + "unsafeName": "modifiedBy", + "safeName": "modifiedBy" + }, + "snakeCase": { + "unsafeName": "modified_by", + "safeName": "modified_by" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_BY", + "safeName": "MODIFIED_BY" + }, + "pascalCase": { + "unsafeName": "ModifiedBy", + "safeName": "ModifiedBy" + } + } + }, + "typeReference": { + "type": "optional", "value": { - "type": "response", - "responseBodyType": { - "_type": "named", - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity", - "default": null, - "inline": null + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": "READ_ONLY", + "variable": null + }, + { + "name": { + "wireValue": "modifiedDateTime", + "name": { + "originalName": "modifiedDateTime", + "camelCase": { + "unsafeName": "modifiedDateTime", + "safeName": "modifiedDateTime" }, - "docs": "An entity with properties from multiple parents", - "v2Examples": null + "snakeCase": { + "unsafeName": "modified_date_time", + "safeName": "modified_date_time" + }, + "screamingSnakeCase": { + "unsafeName": "MODIFIED_DATE_TIME", + "safeName": "MODIFIED_DATE_TIME" + }, + "pascalCase": { + "unsafeName": "ModifiedDateTime", + "safeName": "ModifiedDateTime" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "DATE_TIME" } }, - "status-code": 200, - "isWildcardStatusCode": null, - "docs": "An entity with properties from multiple parents" + "propertyAccess": "READ_ONLY", + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleType": { + "type": "object", + "declaration": { + "name": { + "originalName": "RuleType", + "camelCase": { + "unsafeName": "ruleType", + "safeName": "ruleType" + }, + "snakeCase": { + "unsafeName": "rule_type", + "safeName": "rule_type" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE", + "safeName": "RULE_TYPE" + }, + "pascalCase": { + "unsafeName": "RuleType", + "safeName": "RuleType" + } }, - "v2Responses": null, - "errors": [], - "userSpecifiedExamples": [ - { - "example": { - "id": "b2b07150", - "name": null, - "url": "/entities", - "rootPathParameters": [], - "endpointPathParameters": [], - "servicePathParameters": [], - "endpointHeaders": [], - "serviceHeaders": [], - "queryParameters": [], - "request": null, - "response": { - "type": "ok", - "value": { - "type": "body", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "shape": { - "type": "object", - "properties": [ - { - "name": "name", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "name" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "summary", - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "summary" - } - } - }, - "jsonExample": "summary" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "summary" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "id", - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - }, - { - "name": "status", - "value": { - "shape": { - "type": "named", - "typeName": { - "typeId": "type_:CombinedEntityStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntityStatus", - "displayName": null - }, - "shape": { - "type": "enum", - "value": "active" - } - }, - "jsonExample": "active" - }, - "originalTypeDeclaration": { - "typeId": "type_:CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "name": "CombinedEntity", - "displayName": null - }, - "propertyAccess": null - } - ], - "extraProperties": null - } - }, - "jsonExample": { - "name": "name", - "summary": "summary", - "id": "id", - "status": "active" - } - } - } + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" }, - "docs": null - }, - "codeSamples": null - } - ], - "autogeneratedExamples": [ - { - "example": { - "id": "dc0a034f", - "url": "/entities", - "name": null, - "endpointHeaders": [], - "endpointPathParameters": [], - "queryParameters": [], - "servicePathParameters": [], - "serviceHeaders": [], - "rootPathParameters": [], - "request": null, - "response": { - "type": "ok", - "value": { - "type": "body", - "value": { - "shape": { - "type": "named", - "shape": { - "type": "object", - "properties": [ - { - "name": "status", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "named", - "shape": { - "type": "enum", - "value": "active" - }, - "typeName": { - "name": "CombinedEntityStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntityStatus" - } - }, - "jsonExample": "active" - }, - "propertyAccess": null - }, - { - "name": "id", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "id" - } - } - }, - "jsonExample": "id" - }, - "propertyAccess": null - }, - { - "name": "name", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "name" - } - } - }, - "jsonExample": "name" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "name" - }, - "propertyAccess": null - }, - { - "name": "summary", - "originalTypeDeclaration": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "optional", - "optional": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "summary" - } - } - }, - "jsonExample": "summary" - }, - "valueType": { - "_type": "primitive", - "primitive": { - "v1": "STRING", - "v2": { - "type": "string", - "default": null, - "validation": null - } - } - } - } - }, - "jsonExample": "summary" - }, - "propertyAccess": null - } - ], - "extraProperties": null - }, - "typeName": { - "name": "CombinedEntity", - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "displayName": null, - "typeId": "type_:CombinedEntity" - } - }, - "jsonExample": { - "status": "active", - "id": "id", - "name": "name", - "summary": "summary" - } - } - } + "snakeCase": { + "unsafeName": "name", + "safeName": "name" }, - "docs": null + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } } - } - ], - "pagination": null, - "transport": null, - "v2Examples": null, - "source": null, - "audiences": null, - "retries": null, - "apiPlayground": null, - "responseHeaders": [], - "availability": null, - "docs": null - } - ], - "audiences": null - } - }, - "constants": { - "errorInstanceIdKey": "errorInstanceId" - }, - "environments": { - "defaultEnvironment": "Default", - "environments": { - "type": "singleBaseUrl", - "environments": [ - { - "id": "Default", - "name": "Default", - "url": "https://api.example.com", - "audiences": null, - "defaultUrl": null, - "urlTemplate": null, - "urlVariables": null, - "docs": null - } - ] - } - }, - "errorDiscriminationStrategy": { - "type": "statusCode" - }, - "basePath": null, - "pathParameters": [], - "variables": [], - "serviceTypeReferenceInfo": { - "typesReferencedOnlyByService": { - "service_": [ - "type_:PagingCursors", - "type_:RuleExecutionContext", - "type_:AuditInfo", - "type_:RuleType", - "type_:RuleTypeSearchResponse", - "type_:User", - "type_:UserSearchResponse", - "type_:RuleResponseStatus", - "type_:RuleResponse", - "type_:CombinedEntityStatus", - "type_:CombinedEntity" - ] - }, - "sharedTypes": [ - "type_:PaginatedResult", - "type_:Identifiable", - "type_:Describable" - ] - }, - "webhookGroups": {}, - "websocketChannels": {}, - "readmeConfig": null, - "sourceConfig": null, - "publishConfig": null, - "dynamic": { - "version": "1.0.0", - "types": { - "type_:PaginatedResult": { + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + }, + "propertyAccess": null, + "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:RuleTypeSearchResponse": { "type": "object", "declaration": { "name": { - "originalName": "PaginatedResult", + "originalName": "RuleTypeSearchResponse", "camelCase": { - "unsafeName": "paginatedResult", - "safeName": "paginatedResult" + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" }, "snakeCase": { - "unsafeName": "paginated_result", - "safeName": "paginated_result" + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" }, "screamingSnakeCase": { - "unsafeName": "PAGINATED_RESULT", - "safeName": "PAGINATED_RESULT" + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" }, "pascalCase": { - "unsafeName": "PaginatedResult", - "safeName": "PaginatedResult" + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" } }, "fernFilepath": { @@ -4676,6 +6218,42 @@ } }, "properties": [ + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:RuleType" + } + } + }, + "propertyAccess": null, + "variable": null + }, { "name": { "wireValue": "paging", @@ -4705,36 +6283,97 @@ }, "propertyAccess": null, "variable": null + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:User": { + "type": "object", + "declaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ { "name": { - "wireValue": "results", + "wireValue": "id", "name": { - "originalName": "results", + "originalName": "id", "camelCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "id", + "safeName": "id" }, "snakeCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "id", + "safeName": "id" }, "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" + "unsafeName": "ID", + "safeName": "ID" }, "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" + "unsafeName": "ID", + "safeName": "ID" } } }, "typeReference": { - "type": "list", - "value": { - "type": "unknown" + "type": "primitive", + "value": "STRING" + }, + "propertyAccess": null, + "variable": null + }, + { + "name": { + "wireValue": "email", + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } } }, + "typeReference": { + "type": "primitive", + "value": "STRING" + }, "propertyAccess": null, "variable": null } @@ -4742,26 +6381,26 @@ "extends": null, "additionalProperties": false }, - "type_:PagingCursors": { + "type_:UserSearchResponse": { "type": "object", "declaration": { "name": { - "originalName": "PagingCursors", + "originalName": "UserSearchResponse", "camelCase": { - "unsafeName": "pagingCursors", - "safeName": "pagingCursors" + "unsafeName": "userSearchResponse", + "safeName": "userSearchResponse" }, "snakeCase": { - "unsafeName": "paging_cursors", - "safeName": "paging_cursors" + "unsafeName": "user_search_response", + "safeName": "user_search_response" }, "screamingSnakeCase": { - "unsafeName": "PAGING_CURSORS", - "safeName": "PAGING_CURSORS" + "unsafeName": "USER_SEARCH_RESPONSE", + "safeName": "USER_SEARCH_RESPONSE" }, "pascalCase": { - "unsafeName": "PagingCursors", - "safeName": "PagingCursors" + "unsafeName": "UserSearchResponse", + "safeName": "UserSearchResponse" } }, "fernFilepath": { @@ -4773,63 +6412,66 @@ "properties": [ { "name": { - "wireValue": "next", + "wireValue": "results", "name": { - "originalName": "next", + "originalName": "results", "camelCase": { - "unsafeName": "next", - "safeName": "next" + "unsafeName": "results", + "safeName": "results" }, "snakeCase": { - "unsafeName": "next", - "safeName": "next" + "unsafeName": "results", + "safeName": "results" }, "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" + "unsafeName": "RESULTS", + "safeName": "RESULTS" }, "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" + "unsafeName": "Results", + "safeName": "Results" } } }, "typeReference": { - "type": "primitive", - "value": "STRING" + "type": "optional", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_:User" + } + } }, "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "previous", + "wireValue": "paging", "name": { - "originalName": "previous", + "originalName": "paging", "camelCase": { - "unsafeName": "previous", - "safeName": "previous" + "unsafeName": "paging", + "safeName": "paging" }, "snakeCase": { - "unsafeName": "previous", - "safeName": "previous" + "unsafeName": "paging", + "safeName": "paging" }, "screamingSnakeCase": { - "unsafeName": "PREVIOUS", - "safeName": "PREVIOUS" + "unsafeName": "PAGING", + "safeName": "PAGING" }, "pascalCase": { - "unsafeName": "Previous", - "safeName": "Previous" + "unsafeName": "Paging", + "safeName": "Paging" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } + "type": "named", + "value": "type_:PagingCursors" }, "propertyAccess": null, "variable": null @@ -4838,26 +6480,26 @@ "extends": null, "additionalProperties": false }, - "type_:RuleExecutionContext": { + "type_:RuleResponseStatus": { "type": "enum", "declaration": { "name": { - "originalName": "RuleExecutionContext", + "originalName": "RuleResponseStatus", "camelCase": { - "unsafeName": "ruleExecutionContext", - "safeName": "ruleExecutionContext" + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" }, "snakeCase": { - "unsafeName": "rule_execution_context", - "safeName": "rule_execution_context" + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" }, "screamingSnakeCase": { - "unsafeName": "RULE_EXECUTION_CONTEXT", - "safeName": "RULE_EXECUTION_CONTEXT" + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" }, "pascalCase": { - "unsafeName": "RuleExecutionContext", - "safeName": "RuleExecutionContext" + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" } }, "fernFilepath": { @@ -4868,93 +6510,93 @@ }, "values": [ { - "wireValue": "prod", + "wireValue": "active", "name": { - "originalName": "prod", + "originalName": "active", "camelCase": { - "unsafeName": "prod", - "safeName": "prod" + "unsafeName": "active", + "safeName": "active" }, "snakeCase": { - "unsafeName": "prod", - "safeName": "prod" + "unsafeName": "active", + "safeName": "active" }, "screamingSnakeCase": { - "unsafeName": "PROD", - "safeName": "PROD" + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" }, "pascalCase": { - "unsafeName": "Prod", - "safeName": "Prod" + "unsafeName": "Active", + "safeName": "Active" } } }, { - "wireValue": "staging", + "wireValue": "inactive", "name": { - "originalName": "staging", + "originalName": "inactive", "camelCase": { - "unsafeName": "staging", - "safeName": "staging" + "unsafeName": "inactive", + "safeName": "inactive" }, "snakeCase": { - "unsafeName": "staging", - "safeName": "staging" + "unsafeName": "inactive", + "safeName": "inactive" }, "screamingSnakeCase": { - "unsafeName": "STAGING", - "safeName": "STAGING" + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" }, "pascalCase": { - "unsafeName": "Staging", - "safeName": "Staging" + "unsafeName": "Inactive", + "safeName": "Inactive" } } }, { - "wireValue": "dev", + "wireValue": "draft", "name": { - "originalName": "dev", + "originalName": "draft", "camelCase": { - "unsafeName": "dev", - "safeName": "dev" + "unsafeName": "draft", + "safeName": "draft" }, "snakeCase": { - "unsafeName": "dev", - "safeName": "dev" + "unsafeName": "draft", + "safeName": "draft" }, "screamingSnakeCase": { - "unsafeName": "DEV", - "safeName": "DEV" + "unsafeName": "DRAFT", + "safeName": "DRAFT" }, "pascalCase": { - "unsafeName": "Dev", - "safeName": "Dev" + "unsafeName": "Draft", + "safeName": "Draft" } } } ] }, - "type_:AuditInfo": { + "type_:RuleResponse": { "type": "object", "declaration": { "name": { - "originalName": "AuditInfo", + "originalName": "RuleResponse", "camelCase": { - "unsafeName": "auditInfo", - "safeName": "auditInfo" + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" }, "snakeCase": { - "unsafeName": "audit_info", - "safeName": "audit_info" + "unsafeName": "rule_response", + "safeName": "rule_response" }, "screamingSnakeCase": { - "unsafeName": "AUDIT_INFO", - "safeName": "AUDIT_INFO" + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" }, "pascalCase": { - "unsafeName": "AuditInfo", - "safeName": "AuditInfo" + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" } }, "fernFilepath": { @@ -5095,40 +6737,7 @@ }, "propertyAccess": "READ_ONLY", "variable": null - } - ], - "extends": null, - "additionalProperties": false - }, - "type_:RuleType": { - "type": "object", - "declaration": { - "name": { - "originalName": "RuleType", - "camelCase": { - "unsafeName": "ruleType", - "safeName": "ruleType" - }, - "snakeCase": { - "unsafeName": "rule_type", - "safeName": "rule_type" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_TYPE", - "safeName": "RULE_TYPE" - }, - "pascalCase": { - "unsafeName": "RuleType", - "safeName": "RuleType" - } }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - } - }, - "properties": [ { "name": { "wireValue": "id", @@ -5170,181 +6779,114 @@ }, "snakeCase": { "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - } - }, - "typeReference": { - "type": "primitive", - "value": "STRING" - }, - "propertyAccess": null, - "variable": null - }, - { - "name": { - "wireValue": "description", - "name": { - "originalName": "description", - "camelCase": { - "unsafeName": "description", - "safeName": "description" - }, - "snakeCase": { - "unsafeName": "description", - "safeName": "description" - }, - "screamingSnakeCase": { - "unsafeName": "DESCRIPTION", - "safeName": "DESCRIPTION" - }, - "pascalCase": { - "unsafeName": "Description", - "safeName": "Description" - } - } - }, - "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } - }, - "propertyAccess": null, - "variable": null - } - ], - "extends": null, - "additionalProperties": false - }, - "type_:RuleTypeSearchResponse": { - "type": "object", - "declaration": { - "name": { - "originalName": "RuleTypeSearchResponse", - "camelCase": { - "unsafeName": "ruleTypeSearchResponse", - "safeName": "ruleTypeSearchResponse" - }, - "snakeCase": { - "unsafeName": "rule_type_search_response", - "safeName": "rule_type_search_response" + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } }, - "screamingSnakeCase": { - "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", - "safeName": "RULE_TYPE_SEARCH_RESPONSE" + "typeReference": { + "type": "primitive", + "value": "STRING" }, - "pascalCase": { - "unsafeName": "RuleTypeSearchResponse", - "safeName": "RuleTypeSearchResponse" - } + "propertyAccess": null, + "variable": null }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - } - }, - "properties": [ { "name": { - "wireValue": "results", + "wireValue": "status", "name": { - "originalName": "results", + "originalName": "status", "camelCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "status", + "safeName": "status" }, "snakeCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "status", + "safeName": "status" }, "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" + "unsafeName": "STATUS", + "safeName": "STATUS" }, "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" + "unsafeName": "Status", + "safeName": "Status" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "list", - "value": { - "type": "named", - "value": "type_:RuleType" - } - } + "type": "named", + "value": "type_:RuleResponseStatus" }, "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "paging", + "wireValue": "executionContext", "name": { - "originalName": "paging", + "originalName": "executionContext", "camelCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "executionContext", + "safeName": "executionContext" }, "snakeCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "execution_context", + "safeName": "execution_context" }, "screamingSnakeCase": { - "unsafeName": "PAGING", - "safeName": "PAGING" + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" }, "pascalCase": { - "unsafeName": "Paging", - "safeName": "Paging" + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" } } }, "typeReference": { - "type": "named", - "value": "type_:PagingCursors" + "type": "optional", + "value": { + "type": "named", + "value": "type_:RuleExecutionContext" + } }, "propertyAccess": null, "variable": null } ], - "extends": null, + "extends": [ + "type_:AuditInfo" + ], "additionalProperties": false }, - "type_:User": { + "type_:Identifiable": { "type": "object", "declaration": { "name": { - "originalName": "User", + "originalName": "Identifiable", "camelCase": { - "unsafeName": "user", - "safeName": "user" + "unsafeName": "identifiable", + "safeName": "identifiable" }, "snakeCase": { - "unsafeName": "user", - "safeName": "user" + "unsafeName": "identifiable", + "safeName": "identifiable" }, "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" + "unsafeName": "IDENTIFIABLE", + "safeName": "IDENTIFIABLE" }, "pascalCase": { - "unsafeName": "User", - "safeName": "User" + "unsafeName": "Identifiable", + "safeName": "Identifiable" } }, "fernFilepath": { @@ -5386,30 +6928,33 @@ }, { "name": { - "wireValue": "email", + "wireValue": "name", "name": { - "originalName": "email", + "originalName": "name", "camelCase": { - "unsafeName": "email", - "safeName": "email" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "email", - "safeName": "email" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "EMAIL", - "safeName": "EMAIL" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Email", - "safeName": "Email" + "unsafeName": "Name", + "safeName": "Name" } } }, "typeReference": { - "type": "primitive", - "value": "STRING" + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } }, "propertyAccess": null, "variable": null @@ -5418,26 +6963,26 @@ "extends": null, "additionalProperties": false }, - "type_:UserSearchResponse": { + "type_:Describable": { "type": "object", "declaration": { "name": { - "originalName": "UserSearchResponse", + "originalName": "Describable", "camelCase": { - "unsafeName": "userSearchResponse", - "safeName": "userSearchResponse" + "unsafeName": "describable", + "safeName": "describable" }, "snakeCase": { - "unsafeName": "user_search_response", - "safeName": "user_search_response" + "unsafeName": "describable", + "safeName": "describable" }, "screamingSnakeCase": { - "unsafeName": "USER_SEARCH_RESPONSE", - "safeName": "USER_SEARCH_RESPONSE" + "unsafeName": "DESCRIBABLE", + "safeName": "DESCRIBABLE" }, "pascalCase": { - "unsafeName": "UserSearchResponse", - "safeName": "UserSearchResponse" + "unsafeName": "Describable", + "safeName": "Describable" } }, "fernFilepath": { @@ -5449,35 +6994,32 @@ "properties": [ { "name": { - "wireValue": "results", + "wireValue": "name", "name": { - "originalName": "results", + "originalName": "name", "camelCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "results", - "safeName": "results" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" + "unsafeName": "Name", + "safeName": "Name" } } }, "typeReference": { "type": "optional", "value": { - "type": "list", - "value": { - "type": "named", - "value": "type_:User" - } + "type": "primitive", + "value": "STRING" } }, "propertyAccess": null, @@ -5485,30 +7027,33 @@ }, { "name": { - "wireValue": "paging", + "wireValue": "summary", "name": { - "originalName": "paging", + "originalName": "summary", "camelCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "summary", + "safeName": "summary" }, "snakeCase": { - "unsafeName": "paging", - "safeName": "paging" + "unsafeName": "summary", + "safeName": "summary" }, "screamingSnakeCase": { - "unsafeName": "PAGING", - "safeName": "PAGING" + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" }, "pascalCase": { - "unsafeName": "Paging", - "safeName": "Paging" + "unsafeName": "Summary", + "safeName": "Summary" } } }, "typeReference": { - "type": "named", - "value": "type_:PagingCursors" + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } }, "propertyAccess": null, "variable": null @@ -5517,26 +7062,26 @@ "extends": null, "additionalProperties": false }, - "type_:RuleResponseStatus": { + "type_:CombinedEntityStatus": { "type": "enum", "declaration": { "name": { - "originalName": "RuleResponseStatus", + "originalName": "CombinedEntityStatus", "camelCase": { - "unsafeName": "ruleResponseStatus", - "safeName": "ruleResponseStatus" + "unsafeName": "combinedEntityStatus", + "safeName": "combinedEntityStatus" }, "snakeCase": { - "unsafeName": "rule_response_status", - "safeName": "rule_response_status" + "unsafeName": "combined_entity_status", + "safeName": "combined_entity_status" }, "screamingSnakeCase": { - "unsafeName": "RULE_RESPONSE_STATUS", - "safeName": "RULE_RESPONSE_STATUS" + "unsafeName": "COMBINED_ENTITY_STATUS", + "safeName": "COMBINED_ENTITY_STATUS" }, "pascalCase": { - "unsafeName": "RuleResponseStatus", - "safeName": "RuleResponseStatus" + "unsafeName": "CombinedEntityStatus", + "safeName": "CombinedEntityStatus" } }, "fernFilepath": { @@ -5551,89 +7096,67 @@ "name": { "originalName": "active", "camelCase": { - "unsafeName": "active", - "safeName": "active" - }, - "snakeCase": { - "unsafeName": "active", - "safeName": "active" - }, - "screamingSnakeCase": { - "unsafeName": "ACTIVE", - "safeName": "ACTIVE" - }, - "pascalCase": { - "unsafeName": "Active", - "safeName": "Active" - } - } - }, - { - "wireValue": "inactive", - "name": { - "originalName": "inactive", - "camelCase": { - "unsafeName": "inactive", - "safeName": "inactive" + "unsafeName": "active", + "safeName": "active" }, "snakeCase": { - "unsafeName": "inactive", - "safeName": "inactive" + "unsafeName": "active", + "safeName": "active" }, "screamingSnakeCase": { - "unsafeName": "INACTIVE", - "safeName": "INACTIVE" + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" }, "pascalCase": { - "unsafeName": "Inactive", - "safeName": "Inactive" + "unsafeName": "Active", + "safeName": "Active" } } }, { - "wireValue": "draft", + "wireValue": "archived", "name": { - "originalName": "draft", + "originalName": "archived", "camelCase": { - "unsafeName": "draft", - "safeName": "draft" + "unsafeName": "archived", + "safeName": "archived" }, "snakeCase": { - "unsafeName": "draft", - "safeName": "draft" + "unsafeName": "archived", + "safeName": "archived" }, "screamingSnakeCase": { - "unsafeName": "DRAFT", - "safeName": "DRAFT" + "unsafeName": "ARCHIVED", + "safeName": "ARCHIVED" }, "pascalCase": { - "unsafeName": "Draft", - "safeName": "Draft" + "unsafeName": "Archived", + "safeName": "Archived" } } } ] }, - "type_:RuleResponse": { + "type_:CombinedEntity": { "type": "object", "declaration": { "name": { - "originalName": "RuleResponse", + "originalName": "CombinedEntity", "camelCase": { - "unsafeName": "ruleResponse", - "safeName": "ruleResponse" + "unsafeName": "combinedEntity", + "safeName": "combinedEntity" }, "snakeCase": { - "unsafeName": "rule_response", - "safeName": "rule_response" + "unsafeName": "combined_entity", + "safeName": "combined_entity" }, "screamingSnakeCase": { - "unsafeName": "RULE_RESPONSE", - "safeName": "RULE_RESPONSE" + "unsafeName": "COMBINED_ENTITY", + "safeName": "COMBINED_ENTITY" }, "pascalCase": { - "unsafeName": "RuleResponse", - "safeName": "RuleResponse" + "unsafeName": "CombinedEntity", + "safeName": "CombinedEntity" } }, "fernFilepath": { @@ -5645,90 +7168,84 @@ "properties": [ { "name": { - "wireValue": "createdBy", + "wireValue": "status", "name": { - "originalName": "createdBy", + "originalName": "status", "camelCase": { - "unsafeName": "createdBy", - "safeName": "createdBy" + "unsafeName": "status", + "safeName": "status" }, "snakeCase": { - "unsafeName": "created_by", - "safeName": "created_by" + "unsafeName": "status", + "safeName": "status" }, "screamingSnakeCase": { - "unsafeName": "CREATED_BY", - "safeName": "CREATED_BY" + "unsafeName": "STATUS", + "safeName": "STATUS" }, "pascalCase": { - "unsafeName": "CreatedBy", - "safeName": "CreatedBy" + "unsafeName": "Status", + "safeName": "Status" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } + "type": "named", + "value": "type_:CombinedEntityStatus" }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "createdDateTime", + "wireValue": "id", "name": { - "originalName": "createdDateTime", + "originalName": "id", "camelCase": { - "unsafeName": "createdDateTime", - "safeName": "createdDateTime" + "unsafeName": "id", + "safeName": "id" }, "snakeCase": { - "unsafeName": "created_date_time", - "safeName": "created_date_time" + "unsafeName": "id", + "safeName": "id" }, "screamingSnakeCase": { - "unsafeName": "CREATED_DATE_TIME", - "safeName": "CREATED_DATE_TIME" + "unsafeName": "ID", + "safeName": "ID" }, "pascalCase": { - "unsafeName": "CreatedDateTime", - "safeName": "CreatedDateTime" + "unsafeName": "ID", + "safeName": "ID" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "DATE_TIME" - } + "type": "primitive", + "value": "STRING" }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "modifiedBy", + "wireValue": "name", "name": { - "originalName": "modifiedBy", + "originalName": "name", "camelCase": { - "unsafeName": "modifiedBy", - "safeName": "modifiedBy" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "modified_by", - "safeName": "modified_by" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "MODIFIED_BY", - "safeName": "MODIFIED_BY" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "ModifiedBy", - "safeName": "ModifiedBy" + "unsafeName": "Name", + "safeName": "Name" } } }, @@ -5739,29 +7256,29 @@ "value": "STRING" } }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "modifiedDateTime", + "wireValue": "summary", "name": { - "originalName": "modifiedDateTime", + "originalName": "summary", "camelCase": { - "unsafeName": "modifiedDateTime", - "safeName": "modifiedDateTime" + "unsafeName": "summary", + "safeName": "summary" }, "snakeCase": { - "unsafeName": "modified_date_time", - "safeName": "modified_date_time" + "unsafeName": "summary", + "safeName": "summary" }, "screamingSnakeCase": { - "unsafeName": "MODIFIED_DATE_TIME", - "safeName": "MODIFIED_DATE_TIME" + "unsafeName": "SUMMARY", + "safeName": "SUMMARY" }, "pascalCase": { - "unsafeName": "ModifiedDateTime", - "safeName": "ModifiedDateTime" + "unsafeName": "Summary", + "safeName": "Summary" } } }, @@ -5769,62 +7286,65 @@ "type": "optional", "value": { "type": "primitive", - "value": "DATE_TIME" + "value": "STRING" } }, - "propertyAccess": "READ_ONLY", + "propertyAccess": null, "variable": null - }, - { - "name": { - "wireValue": "id", - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "ID", - "safeName": "ID" - } - } + } + ], + "extends": null, + "additionalProperties": false + }, + "type_:BaseOrgMetadata": { + "type": "object", + "declaration": { + "name": { + "originalName": "BaseOrgMetadata", + "camelCase": { + "unsafeName": "baseOrgMetadata", + "safeName": "baseOrgMetadata" }, - "typeReference": { - "type": "primitive", - "value": "STRING" + "snakeCase": { + "unsafeName": "base_org_metadata", + "safeName": "base_org_metadata" }, - "propertyAccess": null, - "variable": null + "screamingSnakeCase": { + "unsafeName": "BASE_ORG_METADATA", + "safeName": "BASE_ORG_METADATA" + }, + "pascalCase": { + "unsafeName": "BaseOrgMetadata", + "safeName": "BaseOrgMetadata" + } }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ { "name": { - "wireValue": "name", + "wireValue": "region", "name": { - "originalName": "name", + "originalName": "region", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "REGION", + "safeName": "REGION" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Region", + "safeName": "Region" } } }, @@ -5837,93 +7357,61 @@ }, { "name": { - "wireValue": "status", - "name": { - "originalName": "status", - "camelCase": { - "unsafeName": "status", - "safeName": "status" - }, - "snakeCase": { - "unsafeName": "status", - "safeName": "status" - }, - "screamingSnakeCase": { - "unsafeName": "STATUS", - "safeName": "STATUS" - }, - "pascalCase": { - "unsafeName": "Status", - "safeName": "Status" - } - } - }, - "typeReference": { - "type": "named", - "value": "type_:RuleResponseStatus" - }, - "propertyAccess": null, - "variable": null - }, - { - "name": { - "wireValue": "executionContext", + "wireValue": "tier", "name": { - "originalName": "executionContext", + "originalName": "tier", "camelCase": { - "unsafeName": "executionContext", - "safeName": "executionContext" + "unsafeName": "tier", + "safeName": "tier" }, "snakeCase": { - "unsafeName": "execution_context", - "safeName": "execution_context" + "unsafeName": "tier", + "safeName": "tier" }, - "screamingSnakeCase": { - "unsafeName": "EXECUTION_CONTEXT", - "safeName": "EXECUTION_CONTEXT" + "screamingSnakeCase": { + "unsafeName": "TIER", + "safeName": "TIER" }, "pascalCase": { - "unsafeName": "ExecutionContext", - "safeName": "ExecutionContext" + "unsafeName": "Tier", + "safeName": "Tier" } } }, "typeReference": { "type": "optional", "value": { - "type": "named", - "value": "type_:RuleExecutionContext" + "type": "primitive", + "value": "STRING" } }, "propertyAccess": null, "variable": null } ], - "extends": [ - "type_:AuditInfo" - ], + "extends": null, "additionalProperties": false }, - "type_:Identifiable": { + "type_:BaseOrg": { "type": "object", "declaration": { "name": { - "originalName": "Identifiable", + "originalName": "BaseOrg", "camelCase": { - "unsafeName": "identifiable", - "safeName": "identifiable" + "unsafeName": "baseOrg", + "safeName": "baseOrg" }, "snakeCase": { - "unsafeName": "identifiable", - "safeName": "identifiable" + "unsafeName": "base_org", + "safeName": "base_org" }, "screamingSnakeCase": { - "unsafeName": "IDENTIFIABLE", - "safeName": "IDENTIFIABLE" + "unsafeName": "BASE_ORG", + "safeName": "BASE_ORG" }, "pascalCase": { - "unsafeName": "Identifiable", - "safeName": "Identifiable" + "unsafeName": "BaseOrg", + "safeName": "BaseOrg" } }, "fernFilepath": { @@ -5965,32 +7453,32 @@ }, { "name": { - "wireValue": "name", + "wireValue": "metadata", "name": { - "originalName": "name", + "originalName": "metadata", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "metadata", + "safeName": "metadata" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "metadata", + "safeName": "metadata" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "METADATA", + "safeName": "METADATA" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Metadata", + "safeName": "Metadata" } } }, "typeReference": { "type": "optional", "value": { - "type": "primitive", - "value": "STRING" + "type": "named", + "value": "type_:BaseOrgMetadata" } }, "propertyAccess": null, @@ -6000,26 +7488,26 @@ "extends": null, "additionalProperties": false }, - "type_:Describable": { + "type_:DetailedOrgMetadata": { "type": "object", "declaration": { "name": { - "originalName": "Describable", + "originalName": "DetailedOrgMetadata", "camelCase": { - "unsafeName": "describable", - "safeName": "describable" + "unsafeName": "detailedOrgMetadata", + "safeName": "detailedOrgMetadata" }, "snakeCase": { - "unsafeName": "describable", - "safeName": "describable" + "unsafeName": "detailed_org_metadata", + "safeName": "detailed_org_metadata" }, "screamingSnakeCase": { - "unsafeName": "DESCRIBABLE", - "safeName": "DESCRIBABLE" + "unsafeName": "DETAILED_ORG_METADATA", + "safeName": "DETAILED_ORG_METADATA" }, "pascalCase": { - "unsafeName": "Describable", - "safeName": "Describable" + "unsafeName": "DetailedOrgMetadata", + "safeName": "DetailedOrgMetadata" } }, "fernFilepath": { @@ -6031,57 +7519,54 @@ "properties": [ { "name": { - "wireValue": "name", + "wireValue": "region", "name": { - "originalName": "name", + "originalName": "region", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "region", + "safeName": "region" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "REGION", + "safeName": "REGION" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Region", + "safeName": "Region" } } }, "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } + "type": "primitive", + "value": "STRING" }, "propertyAccess": null, "variable": null }, { "name": { - "wireValue": "summary", + "wireValue": "domain", "name": { - "originalName": "summary", + "originalName": "domain", "camelCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "domain", + "safeName": "domain" }, "snakeCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "domain", + "safeName": "domain" }, "screamingSnakeCase": { - "unsafeName": "SUMMARY", - "safeName": "SUMMARY" + "unsafeName": "DOMAIN", + "safeName": "DOMAIN" }, "pascalCase": { - "unsafeName": "Summary", - "safeName": "Summary" + "unsafeName": "Domain", + "safeName": "Domain" } } }, @@ -6099,26 +7584,26 @@ "extends": null, "additionalProperties": false }, - "type_:CombinedEntityStatus": { - "type": "enum", + "type_:DetailedOrg": { + "type": "object", "declaration": { "name": { - "originalName": "CombinedEntityStatus", + "originalName": "DetailedOrg", "camelCase": { - "unsafeName": "combinedEntityStatus", - "safeName": "combinedEntityStatus" + "unsafeName": "detailedOrg", + "safeName": "detailedOrg" }, "snakeCase": { - "unsafeName": "combined_entity_status", - "safeName": "combined_entity_status" + "unsafeName": "detailed_org", + "safeName": "detailed_org" }, "screamingSnakeCase": { - "unsafeName": "COMBINED_ENTITY_STATUS", - "safeName": "COMBINED_ENTITY_STATUS" + "unsafeName": "DETAILED_ORG", + "safeName": "DETAILED_ORG" }, "pascalCase": { - "unsafeName": "CombinedEntityStatus", - "safeName": "CombinedEntityStatus" + "unsafeName": "DetailedOrg", + "safeName": "DetailedOrg" } }, "fernFilepath": { @@ -6127,73 +7612,64 @@ "file": null } }, - "values": [ + "properties": [ { - "wireValue": "active", "name": { - "originalName": "active", - "camelCase": { - "unsafeName": "active", - "safeName": "active" - }, - "snakeCase": { - "unsafeName": "active", - "safeName": "active" - }, - "screamingSnakeCase": { - "unsafeName": "ACTIVE", - "safeName": "ACTIVE" - }, - "pascalCase": { - "unsafeName": "Active", - "safeName": "Active" + "wireValue": "metadata", + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } } - } - }, - { - "wireValue": "archived", - "name": { - "originalName": "archived", - "camelCase": { - "unsafeName": "archived", - "safeName": "archived" - }, - "snakeCase": { - "unsafeName": "archived", - "safeName": "archived" - }, - "screamingSnakeCase": { - "unsafeName": "ARCHIVED", - "safeName": "ARCHIVED" - }, - "pascalCase": { - "unsafeName": "Archived", - "safeName": "Archived" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:DetailedOrgMetadata" } - } + }, + "propertyAccess": null, + "variable": null } - ] + ], + "extends": null, + "additionalProperties": false }, - "type_:CombinedEntity": { + "type_:Organization": { "type": "object", "declaration": { "name": { - "originalName": "CombinedEntity", + "originalName": "Organization", "camelCase": { - "unsafeName": "combinedEntity", - "safeName": "combinedEntity" + "unsafeName": "organization", + "safeName": "organization" }, "snakeCase": { - "unsafeName": "combined_entity", - "safeName": "combined_entity" + "unsafeName": "organization", + "safeName": "organization" }, "screamingSnakeCase": { - "unsafeName": "COMBINED_ENTITY", - "safeName": "COMBINED_ENTITY" + "unsafeName": "ORGANIZATION", + "safeName": "ORGANIZATION" }, "pascalCase": { - "unsafeName": "CombinedEntity", - "safeName": "CombinedEntity" + "unsafeName": "Organization", + "safeName": "Organization" } }, "fernFilepath": { @@ -6205,30 +7681,30 @@ "properties": [ { "name": { - "wireValue": "status", + "wireValue": "name", "name": { - "originalName": "status", + "originalName": "name", "camelCase": { - "unsafeName": "status", - "safeName": "status" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "status", - "safeName": "status" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "STATUS", - "safeName": "STATUS" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Status", - "safeName": "Status" + "unsafeName": "Name", + "safeName": "Name" } } }, "typeReference": { - "type": "named", - "value": "type_:CombinedEntityStatus" + "type": "primitive", + "value": "STRING" }, "propertyAccess": null, "variable": null @@ -6265,65 +7741,32 @@ }, { "name": { - "wireValue": "name", - "name": { - "originalName": "name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - } - }, - "typeReference": { - "type": "optional", - "value": { - "type": "primitive", - "value": "STRING" - } - }, - "propertyAccess": null, - "variable": null - }, - { - "name": { - "wireValue": "summary", + "wireValue": "metadata", "name": { - "originalName": "summary", + "originalName": "metadata", "camelCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "metadata", + "safeName": "metadata" }, "snakeCase": { - "unsafeName": "summary", - "safeName": "summary" + "unsafeName": "metadata", + "safeName": "metadata" }, "screamingSnakeCase": { - "unsafeName": "SUMMARY", - "safeName": "SUMMARY" + "unsafeName": "METADATA", + "safeName": "METADATA" }, "pascalCase": { - "unsafeName": "Summary", - "safeName": "Summary" + "unsafeName": "Metadata", + "safeName": "Metadata" } } }, "typeReference": { "type": "optional", "value": { - "type": "primitive", - "value": "STRING" + "type": "named", + "value": "type_:BaseOrgMetadata" } }, "propertyAccess": null, @@ -6665,6 +8108,48 @@ "type": "json" }, "examples": null + }, + "endpoint_.getOrganization": { + "auth": null, + "declaration": { + "name": { + "originalName": "getOrganization", + "camelCase": { + "unsafeName": "getOrganization", + "safeName": "getOrganization" + }, + "snakeCase": { + "unsafeName": "get_organization", + "safeName": "get_organization" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION", + "safeName": "GET_ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "GetOrganization", + "safeName": "GetOrganization" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "GET", + "path": "/organizations" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": null + }, + "response": { + "type": "json" + }, + "examples": null } }, "pathParameters": [], @@ -6734,7 +8219,12 @@ "type_:Identifiable", "type_:Describable", "type_:CombinedEntityStatus", - "type_:CombinedEntity" + "type_:CombinedEntity", + "type_:BaseOrgMetadata", + "type_:BaseOrg", + "type_:DetailedOrgMetadata", + "type_:DetailedOrg", + "type_:Organization" ], "errors": [], "subpackages": [], From 0c05937e07028c506fed675ac8419cb5b833e8d7 Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 14:57:19 -0400 Subject: [PATCH 18/29] fix: deduplicate merged allOf refs and guard composition-keyword shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deduplicate $ref entries in the merged allOf array after mergeWith array-concatenation to prevent false-positive cycle detection in diamond inheritance patterns (A → B, C; B, C → D). Add allOf/oneOf/anyOf checks to the resolved-schema guard in maybeConvertSingularAllOfReferenceObject so the shortcut only fires for true scalar/enum primitives, not schemas defined via composition keywords whose inline constraints would be silently discarded. --- .../src/converters/schema/SchemaConverter.ts | 19 +++++++++++++++++++ .../schema/SchemaOrReferenceConverter.ts | 9 ++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index d369ca07e8d5..5dfbb8514f6b 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -290,6 +290,25 @@ export class SchemaConverter extends AbstractConverter(); + mergedSchema.allOf = ( + mergedSchema.allOf as (OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject)[] + ).filter((element) => { + if (this.context.isReferenceObject(element)) { + if (seenRefs.has(element.$ref)) { + return false; + } + seenRefs.add(element.$ref); + } + return true; + }); + } + const mergedConverter = new SchemaConverter({ context: this.context, breadcrumbs: this.breadcrumbs, diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts index beede8e0b4f6..ba1888204e73 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts @@ -122,7 +122,14 @@ export class SchemaOrReferenceConverter extends AbstractConverter< schemaOrReference: singleRef, breadcrumbs: this.breadcrumbs }); - if (resolved != null && resolved.type !== "object" && !resolved.properties) { + if ( + resolved != null && + resolved.type !== "object" && + !resolved.properties && + !resolved.allOf && + !resolved.oneOf && + !resolved.anyOf + ) { const response = this.context.convertReferenceToTypeReference({ reference: singleRef as OpenAPIV3_1.ReferenceObject, breadcrumbs: this.breadcrumbs From f12183171f4e9fa22b5e962ea651cac4199b1039 Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 16:47:53 -0400 Subject: [PATCH 19/29] fix: guard single-element allOf cycles and add versions.yml entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thread visitedRefs through the single-element allOf branch in SchemaConverter to detect self-referential and indirect cycles (e.g. A → B → A via single-element allOf chains), matching the protection already present in the multi-element shouldMergeAllOf path. Add the required versions.yml changelog entry for the allOf composition fixes in this PR. --- .../src/converters/schema/SchemaConverter.ts | 18 ++++++++++++++++-- packages/cli/cli/versions.yml | 12 ++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index 5dfbb8514f6b..22c865df0dbc 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -179,19 +179,33 @@ export class SchemaConverter extends AbstractConverter({ - schemaOrReference: this.schema.allOf[0], + schemaOrReference: allOfElement, breadcrumbs: this.breadcrumbs }); if (allOfSchema != null) { + const visitedRefsForChild = this.context.isReferenceObject(allOfElement) + ? new Set([...this.visitedRefs, allOfElement.$ref]) + : this.visitedRefs; + const allOfConverter = new SchemaConverter({ context: this.context, breadcrumbs: [...this.breadcrumbs, "allOf", "0"], schema: allOfSchema, id: this.id, inlined: true, - visitedRefs: this.visitedRefs + visitedRefs: visitedRefsForChild }); const allOfResult = allOfConverter.convert(); diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index b2dab69662b8..e344257db76f 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,16 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 4.65.4 + changelogEntry: + - summary: | + Fix allOf composition bugs in V3 OpenAPI importer: resolve $ref schemas + within multi-element allOf arrays instead of falling back to untyped objects, + deduplicate merged allOf refs for diamond-shaped inheritance, guard + composition-keyword shortcuts against oneOf/anyOf/allOf resolved schemas, + and detect cycles across recursive single-element allOf chains. + type: fix + createdAt: "2026-04-10" + irVersion: 66 + - version: 4.62.5 changelogEntry: - summary: | From 47d9f755e9c0198c8ec3be5e8b818fa91f073101 Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 18:02:40 -0400 Subject: [PATCH 20/29] nits --- .../{allof-spscommerce.test.ts => allof.test.ts} | 8 ++++---- .../{allof-spscommerce => allof}/fern/fern.config.json | 0 .../{allof-spscommerce => allof}/fern/generators.yml | 0 .../fixtures/{allof-spscommerce => allof}/openapi.yml | 10 ++++------ 4 files changed, 8 insertions(+), 10 deletions(-) rename packages/cli/api-importers/v3-importer-tests/src/__test__/{allof-spscommerce.test.ts => allof.test.ts} (97%) rename packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/{allof-spscommerce => allof}/fern/fern.config.json (100%) rename packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/{allof-spscommerce => allof}/fern/generators.yml (100%) rename packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/{allof-spscommerce => allof}/openapi.yml (93%) diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof.test.ts similarity index 97% rename from packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts rename to packages/cli/api-importers/v3-importer-tests/src/__test__/allof.test.ts index 4f0692b4a570..7d87f5ae7f31 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof-spscommerce.test.ts +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof.test.ts @@ -1,5 +1,5 @@ /** - * Assertion-based tests for allOf composition edge cases reported by SPS Commerce. + * Assertion-based tests for allOf composition edge cases. * These tests validate the IR output against expected behavior per the OpenAPI spec, * rather than snapshot-matching. They should FAIL until the allOf bugs are fixed. * @@ -13,7 +13,7 @@ import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; import { createMockTaskContext } from "@fern-api/task-context"; import { loadAPIWorkspace } from "@fern-api/workspace-loader"; -const FIXTURE_DIR = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures/allof-spscommerce/fern")); +const FIXTURE_DIR = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures/allof/fern")); interface IRProperty { name: string; @@ -66,7 +66,7 @@ function getContainerType(prop: IRProperty): string | undefined { return undefined; } -describe("allOf SPS Commerce edge cases", () => { +describe("allOf edge cases", () => { let ir: IR; beforeAll(async () => { @@ -75,7 +75,7 @@ describe("allOf SPS Commerce edge cases", () => { absolutePathToWorkspace: FIXTURE_DIR, context, cliVersion: "0.0.0", - workspaceName: "allof-spscommerce" + workspaceName: "allof" }); if (!workspace.didSucceed) { throw new Error(`Failed to load fixture: ${JSON.stringify(workspace.failures)}`); diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/fern.config.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof/fern/fern.config.json similarity index 100% rename from packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/fern.config.json rename to packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof/fern/fern.config.json diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/generators.yml b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof/fern/generators.yml similarity index 100% rename from packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/fern/generators.yml rename to packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof/fern/generators.yml diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/openapi.yml b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof/openapi.yml similarity index 93% rename from packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/openapi.yml rename to packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof/openapi.yml index 22ff87249f0d..e1df8f503799 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof-spscommerce/openapi.yml +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/fixtures/allof/openapi.yml @@ -1,14 +1,12 @@ openapi: 3.0.3 info: - title: allOf SPS Commerce Reproduction + title: allOf Reproduction version: 1.0.0 description: > - Reproduces the allOf edge cases reported by SPS Commerce (Pylon #18189). - These patterns are taken directly from the customer's Slack messages and - their simplified allof-troubleshooting.oas.yml test file. + Reproduces allOf edge cases servers: - - url: https://api.spscommerce.com + - url: https://api.allof.com paths: /v1/preview/1/rule-types: @@ -49,7 +47,7 @@ paths: components: schemas: # ----------------------------------------------------------------------- - # Shared base schemas (from SPS Commerce's actual spec) + # Shared base schemas # ----------------------------------------------------------------------- PagingCursors: type: object From ecb4507f4c12fe4732178a793683cd5316bd97d0 Mon Sep 17 00:00:00 2001 From: jsklan Date: Fri, 10 Apr 2026 18:14:31 -0400 Subject: [PATCH 21/29] fix(cli): address review feedback on allOf composition - Preserve outer `inlined` status through allOf merge path instead of hardcoding `inlined: true` - Guard variants-flattening against $ref-resolved schemas to prevent destructive flattening of named union types - Seed dedup seenRefs from resolvedRefs to prevent duplicate property declarations in diamond inheritance - Bail out of allOf shortcut when inline elements contain `type: "null"` to preserve nullable semantics in OpenAPI 3.1 patterns - Add optional guard for required executionContext field in Case B test - Remove debug script (scripts/debug-allof-pipeline.ts) - Remove orphaned allof-spscommerce snapshot files - Move changelog to changes/unreleased/ per release-versioning convention --- .../src/converters/schema/SchemaConverter.ts | 11 +- .../schema/SchemaOrReferenceConverter.ts | 3 +- .../baseline-sdks/allof-spscommerce.json | 1847 ----------------- .../v3-sdks/allof-spscommerce.json | 1781 ---------------- .../src/__test__/allof.test.ts | 5 + .../unreleased/fix-allof-composition.yml | 11 + packages/cli/cli/versions.yml | 11 - scripts/debug-allof-pipeline.ts | 80 - 8 files changed, 26 insertions(+), 3723 deletions(-) delete mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json delete mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json create mode 100644 packages/cli/cli/changes/unreleased/fix-allof-composition.yml delete mode 100644 scripts/debug-allof-pipeline.ts diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index 22c865df0dbc..34be2325b432 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -252,7 +252,12 @@ export class SchemaConverter extends AbstractConverter = {}; for (const variantSchemaOrRef of variants) { const variantSchema = this.context.isReferenceObject(variantSchemaOrRef) @@ -309,7 +314,7 @@ export class SchemaConverter extends AbstractConverter(); + const seenRefs = new Set(resolvedRefs); mergedSchema.allOf = ( mergedSchema.allOf as (OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject)[] ).filter((element) => { @@ -328,7 +333,7 @@ export class SchemaConverter extends AbstractConverter !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf) + inlineElements.every((s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf) && + !inlineElements.some((s) => s.type === "null") ) { const resolved = this.context.resolveMaybeReference({ schemaOrReference: singleRef, diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json deleted file mode 100644 index d1e60003cac1..000000000000 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof-spscommerce.json +++ /dev/null @@ -1,1847 +0,0 @@ -{ - "selfHosted": false, - "apiName": "api", - "apiDisplayName": "allOf SPS Commerce Reproduction", - "auth": { - "requirement": "ALL", - "schemes": [] - }, - "headers": [], - "idempotencyHeaders": [], - "types": { - "type_:PagingCursors": { - "name": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:PagingCursors" - }, - "shape": { - "extends": [], - "properties": [ - { - "name": "next", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "previous", - "valueType": { - "container": { - "optional": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - } - ], - "extraProperties": false, - "extendedProperties": [], - "type": "object" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - }, - "type_:PaginatedResult": { - "name": { - "name": "PaginatedResult", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:PaginatedResult" - }, - "shape": { - "extends": [], - "properties": [ - { - "name": "paging", - "valueType": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:PagingCursors", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "docs": "Current page of results from the requested resource.", - "name": "results", - "valueType": { - "container": { - "list": { - "type": "unknown" - }, - "type": "list" - }, - "type": "container" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - } - ], - "extraProperties": false, - "extendedProperties": [], - "type": "object" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - }, - "type_:RuleExecutionContext": { - "docs": "The execution environment for a rule.", - "name": { - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleExecutionContext" - }, - "shape": { - "values": [ - { - "name": "prod" - }, - { - "name": "staging" - }, - { - "name": "dev" - } - ], - "type": "enum" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - }, - "type_:RuleTypeResponseStatus": { - "inline": true, - "name": { - "name": "RuleTypeResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleTypeResponseStatus" - }, - "shape": { - "values": [ - { - "name": "active" - }, - { - "name": "inactive" - } - ], - "type": "enum" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - }, - "type_:RuleTypeResponse": { - "name": { - "name": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleTypeResponse" - }, - "shape": { - "extends": [], - "properties": [ - { - "name": "id", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "name", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "description", - "valueType": { - "container": { - "optional": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "status", - "valueType": { - "name": "RuleTypeResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleTypeResponseStatus", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - } - ], - "extraProperties": false, - "extendedProperties": [], - "type": "object" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - }, - "type_:RuleTypeSearchResponse": { - "name": { - "name": "RuleTypeSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleTypeSearchResponse" - }, - "shape": { - "extends": [], - "properties": [ - { - "docs": "Current page of results from the requested resource.", - "name": "results", - "valueType": { - "container": { - "optional": { - "container": { - "list": { - "name": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleTypeResponse", - "type": "named" - }, - "type": "list" - }, - "type": "container" - }, - "type": "optional" - }, - "type": "container" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "paging", - "valueType": { - "name": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:PagingCursors", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - } - ], - "extraProperties": false, - "extendedProperties": [], - "type": "object" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - }, - "type_:RuleResponseStatus": { - "inline": true, - "name": { - "name": "RuleResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleResponseStatus" - }, - "shape": { - "values": [ - { - "name": "active" - }, - { - "name": "inactive" - }, - { - "name": "draft" - } - ], - "type": "enum" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - }, - "type_:RuleResponse": { - "name": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleResponse" - }, - "shape": { - "extends": [], - "properties": [ - { - "name": "id", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "name", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "status", - "valueType": { - "name": "RuleResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleResponseStatus", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "executionContext", - "valueType": { - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleExecutionContext", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - } - ], - "extraProperties": false, - "extendedProperties": [], - "type": "object" - }, - "referencedTypes": {}, - "encoding": { - "json": {} - }, - "userProvidedExamples": [], - "autogeneratedExamples": [] - } - }, - "errors": {}, - "services": { - "service_": { - "name": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "basePath": { - "head": "", - "parts": [] - }, - "headers": [], - "pathParameters": [], - "encoding": { - "json": {} - }, - "transport": { - "type": "http" - }, - "endpoints": [ - { - "id": "endpoint_.searchRuleTypes", - "name": "searchRuleTypes", - "displayName": "Search for rule types", - "auth": false, - "idempotent": false, - "method": "GET", - "path": { - "head": "/v1/preview/1/rule-types", - "parts": [] - }, - "fullPath": { - "head": "v1/preview/1/rule-types", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": "query", - "valueType": { - "container": { - "optional": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - }, - "allowMultiple": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - } - ], - "headers": [], - "sdkRequest": { - "shape": { - "wrapperName": "SearchRuleTypesRequest", - "bodyKey": "body", - "includePathParameters": false, - "onlyPathParameters": false, - "type": "wrapper" - }, - "requestParameterName": "request" - }, - "response": { - "body": { - "value": { - "docs": "Paginated list of rule types", - "responseBodyType": { - "name": "RuleTypeSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleTypeSearchResponse", - "type": "named" - }, - "type": "response" - }, - "type": "json" - }, - "statusCode": 200, - "docs": "Paginated list of rule types" - }, - "errors": [], - "userSpecifiedExamples": [], - "autogeneratedExamples": [], - "responseHeaders": [] - }, - { - "id": "endpoint_.createRule", - "name": "createRule", - "displayName": "Create a rule", - "auth": false, - "idempotent": false, - "method": "POST", - "path": { - "head": "/v1/preview/1/rules", - "parts": [] - }, - "fullPath": { - "head": "v1/preview/1/rules", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [], - "headers": [], - "requestBody": { - "name": "RuleCreateRequest", - "extends": [], - "contentType": "application/json", - "properties": [ - { - "name": "name", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - }, - { - "name": "executionContext", - "valueType": { - "name": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleExecutionContext", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": {} - } - } - ], - "extraProperties": false, - "extendedProperties": [], - "type": "inlinedRequestBody" - }, - "sdkRequest": { - "shape": { - "wrapperName": "RuleCreateRequest", - "bodyKey": "body", - "includePathParameters": false, - "onlyPathParameters": false, - "type": "wrapper" - }, - "requestParameterName": "request" - }, - "response": { - "body": { - "value": { - "docs": "Created rule", - "responseBodyType": { - "name": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "typeId": "type_:RuleResponse", - "type": "named" - }, - "type": "response" - }, - "type": "json" - }, - "statusCode": 200, - "docs": "Created rule" - }, - "errors": [], - "userSpecifiedExamples": [], - "autogeneratedExamples": [], - "responseHeaders": [] - } - ] - } - }, - "constants": { - "errorInstanceIdKey": "errorInstanceId" - }, - "environments": { - "defaultEnvironment": "Default", - "environments": { - "environments": [ - { - "id": "Default", - "name": "Default", - "url": "https://api.spscommerce.com" - } - ], - "type": "singleBaseUrl" - } - }, - "errorDiscriminationStrategy": { - "type": "statusCode" - }, - "pathParameters": [], - "variables": [], - "serviceTypeReferenceInfo": { - "typesReferencedOnlyByService": { - "service_": [ - "type_:PagingCursors", - "type_:RuleExecutionContext", - "type_:RuleTypeResponseStatus", - "type_:RuleTypeResponse", - "type_:RuleTypeSearchResponse", - "type_:RuleResponseStatus", - "type_:RuleResponse" - ] - }, - "sharedTypes": [ - "type_:PaginatedResult" - ] - }, - "webhookGroups": {}, - "websocketChannels": {}, - "dynamic": { - "version": "1.0.0", - "types": { - "type_:PagingCursors": { - "declaration": { - "name": { - "originalName": "PagingCursors", - "camelCase": { - "unsafeName": "pagingCursors", - "safeName": "pagingCursors" - }, - "snakeCase": { - "unsafeName": "paging_cursors", - "safeName": "paging_cursors" - }, - "screamingSnakeCase": { - "unsafeName": "PAGING_CURSORS", - "safeName": "PAGING_CURSORS" - }, - "pascalCase": { - "unsafeName": "PagingCursors", - "safeName": "PagingCursors" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "properties": [ - { - "name": { - "wireValue": "next", - "name": { - "originalName": "next", - "camelCase": { - "unsafeName": "next", - "safeName": "next" - }, - "snakeCase": { - "unsafeName": "next", - "safeName": "next" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" - }, - "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" - } - } - }, - "typeReference": { - "value": "STRING", - "type": "primitive" - } - }, - { - "name": { - "wireValue": "previous", - "name": { - "originalName": "previous", - "camelCase": { - "unsafeName": "previous", - "safeName": "previous" - }, - "snakeCase": { - "unsafeName": "previous", - "safeName": "previous" - }, - "screamingSnakeCase": { - "unsafeName": "PREVIOUS", - "safeName": "PREVIOUS" - }, - "pascalCase": { - "unsafeName": "Previous", - "safeName": "Previous" - } - } - }, - "typeReference": { - "value": { - "value": "STRING", - "type": "primitive" - }, - "type": "optional" - } - } - ], - "additionalProperties": false, - "type": "object" - }, - "type_:PaginatedResult": { - "declaration": { - "name": { - "originalName": "PaginatedResult", - "camelCase": { - "unsafeName": "paginatedResult", - "safeName": "paginatedResult" - }, - "snakeCase": { - "unsafeName": "paginated_result", - "safeName": "paginated_result" - }, - "screamingSnakeCase": { - "unsafeName": "PAGINATED_RESULT", - "safeName": "PAGINATED_RESULT" - }, - "pascalCase": { - "unsafeName": "PaginatedResult", - "safeName": "PaginatedResult" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "properties": [ - { - "name": { - "wireValue": "paging", - "name": { - "originalName": "paging", - "camelCase": { - "unsafeName": "paging", - "safeName": "paging" - }, - "snakeCase": { - "unsafeName": "paging", - "safeName": "paging" - }, - "screamingSnakeCase": { - "unsafeName": "PAGING", - "safeName": "PAGING" - }, - "pascalCase": { - "unsafeName": "Paging", - "safeName": "Paging" - } - } - }, - "typeReference": { - "value": "type_:PagingCursors", - "type": "named" - } - }, - { - "name": { - "wireValue": "results", - "name": { - "originalName": "results", - "camelCase": { - "unsafeName": "results", - "safeName": "results" - }, - "snakeCase": { - "unsafeName": "results", - "safeName": "results" - }, - "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" - }, - "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" - } - } - }, - "typeReference": { - "value": { - "type": "unknown" - }, - "type": "list" - } - } - ], - "additionalProperties": false, - "type": "object" - }, - "type_:RuleExecutionContext": { - "declaration": { - "name": { - "originalName": "RuleExecutionContext", - "camelCase": { - "unsafeName": "ruleExecutionContext", - "safeName": "ruleExecutionContext" - }, - "snakeCase": { - "unsafeName": "rule_execution_context", - "safeName": "rule_execution_context" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_EXECUTION_CONTEXT", - "safeName": "RULE_EXECUTION_CONTEXT" - }, - "pascalCase": { - "unsafeName": "RuleExecutionContext", - "safeName": "RuleExecutionContext" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "values": [ - { - "wireValue": "prod", - "name": { - "originalName": "prod", - "camelCase": { - "unsafeName": "prod", - "safeName": "prod" - }, - "snakeCase": { - "unsafeName": "prod", - "safeName": "prod" - }, - "screamingSnakeCase": { - "unsafeName": "PROD", - "safeName": "PROD" - }, - "pascalCase": { - "unsafeName": "Prod", - "safeName": "Prod" - } - } - }, - { - "wireValue": "staging", - "name": { - "originalName": "staging", - "camelCase": { - "unsafeName": "staging", - "safeName": "staging" - }, - "snakeCase": { - "unsafeName": "staging", - "safeName": "staging" - }, - "screamingSnakeCase": { - "unsafeName": "STAGING", - "safeName": "STAGING" - }, - "pascalCase": { - "unsafeName": "Staging", - "safeName": "Staging" - } - } - }, - { - "wireValue": "dev", - "name": { - "originalName": "dev", - "camelCase": { - "unsafeName": "dev", - "safeName": "dev" - }, - "snakeCase": { - "unsafeName": "dev", - "safeName": "dev" - }, - "screamingSnakeCase": { - "unsafeName": "DEV", - "safeName": "DEV" - }, - "pascalCase": { - "unsafeName": "Dev", - "safeName": "Dev" - } - } - } - ], - "type": "enum" - }, - "type_:RuleTypeResponseStatus": { - "declaration": { - "name": { - "originalName": "RuleTypeResponseStatus", - "camelCase": { - "unsafeName": "ruleTypeResponseStatus", - "safeName": "ruleTypeResponseStatus" - }, - "snakeCase": { - "unsafeName": "rule_type_response_status", - "safeName": "rule_type_response_status" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_TYPE_RESPONSE_STATUS", - "safeName": "RULE_TYPE_RESPONSE_STATUS" - }, - "pascalCase": { - "unsafeName": "RuleTypeResponseStatus", - "safeName": "RuleTypeResponseStatus" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "values": [ - { - "wireValue": "active", - "name": { - "originalName": "active", - "camelCase": { - "unsafeName": "active", - "safeName": "active" - }, - "snakeCase": { - "unsafeName": "active", - "safeName": "active" - }, - "screamingSnakeCase": { - "unsafeName": "ACTIVE", - "safeName": "ACTIVE" - }, - "pascalCase": { - "unsafeName": "Active", - "safeName": "Active" - } - } - }, - { - "wireValue": "inactive", - "name": { - "originalName": "inactive", - "camelCase": { - "unsafeName": "inactive", - "safeName": "inactive" - }, - "snakeCase": { - "unsafeName": "inactive", - "safeName": "inactive" - }, - "screamingSnakeCase": { - "unsafeName": "INACTIVE", - "safeName": "INACTIVE" - }, - "pascalCase": { - "unsafeName": "Inactive", - "safeName": "Inactive" - } - } - } - ], - "type": "enum" - }, - "type_:RuleTypeResponse": { - "declaration": { - "name": { - "originalName": "RuleTypeResponse", - "camelCase": { - "unsafeName": "ruleTypeResponse", - "safeName": "ruleTypeResponse" - }, - "snakeCase": { - "unsafeName": "rule_type_response", - "safeName": "rule_type_response" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_TYPE_RESPONSE", - "safeName": "RULE_TYPE_RESPONSE" - }, - "pascalCase": { - "unsafeName": "RuleTypeResponse", - "safeName": "RuleTypeResponse" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "properties": [ - { - "name": { - "wireValue": "id", - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "ID", - "safeName": "ID" - } - } - }, - "typeReference": { - "value": "STRING", - "type": "primitive" - } - }, - { - "name": { - "wireValue": "name", - "name": { - "originalName": "name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - } - }, - "typeReference": { - "value": "STRING", - "type": "primitive" - } - }, - { - "name": { - "wireValue": "description", - "name": { - "originalName": "description", - "camelCase": { - "unsafeName": "description", - "safeName": "description" - }, - "snakeCase": { - "unsafeName": "description", - "safeName": "description" - }, - "screamingSnakeCase": { - "unsafeName": "DESCRIPTION", - "safeName": "DESCRIPTION" - }, - "pascalCase": { - "unsafeName": "Description", - "safeName": "Description" - } - } - }, - "typeReference": { - "value": { - "value": "STRING", - "type": "primitive" - }, - "type": "optional" - } - }, - { - "name": { - "wireValue": "status", - "name": { - "originalName": "status", - "camelCase": { - "unsafeName": "status", - "safeName": "status" - }, - "snakeCase": { - "unsafeName": "status", - "safeName": "status" - }, - "screamingSnakeCase": { - "unsafeName": "STATUS", - "safeName": "STATUS" - }, - "pascalCase": { - "unsafeName": "Status", - "safeName": "Status" - } - } - }, - "typeReference": { - "value": "type_:RuleTypeResponseStatus", - "type": "named" - } - } - ], - "additionalProperties": false, - "type": "object" - }, - "type_:RuleTypeSearchResponse": { - "declaration": { - "name": { - "originalName": "RuleTypeSearchResponse", - "camelCase": { - "unsafeName": "ruleTypeSearchResponse", - "safeName": "ruleTypeSearchResponse" - }, - "snakeCase": { - "unsafeName": "rule_type_search_response", - "safeName": "rule_type_search_response" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", - "safeName": "RULE_TYPE_SEARCH_RESPONSE" - }, - "pascalCase": { - "unsafeName": "RuleTypeSearchResponse", - "safeName": "RuleTypeSearchResponse" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "properties": [ - { - "name": { - "wireValue": "results", - "name": { - "originalName": "results", - "camelCase": { - "unsafeName": "results", - "safeName": "results" - }, - "snakeCase": { - "unsafeName": "results", - "safeName": "results" - }, - "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" - }, - "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" - } - } - }, - "typeReference": { - "value": { - "value": { - "value": "type_:RuleTypeResponse", - "type": "named" - }, - "type": "list" - }, - "type": "optional" - } - }, - { - "name": { - "wireValue": "paging", - "name": { - "originalName": "paging", - "camelCase": { - "unsafeName": "paging", - "safeName": "paging" - }, - "snakeCase": { - "unsafeName": "paging", - "safeName": "paging" - }, - "screamingSnakeCase": { - "unsafeName": "PAGING", - "safeName": "PAGING" - }, - "pascalCase": { - "unsafeName": "Paging", - "safeName": "Paging" - } - } - }, - "typeReference": { - "value": "type_:PagingCursors", - "type": "named" - } - } - ], - "additionalProperties": false, - "type": "object" - }, - "type_:RuleResponseStatus": { - "declaration": { - "name": { - "originalName": "RuleResponseStatus", - "camelCase": { - "unsafeName": "ruleResponseStatus", - "safeName": "ruleResponseStatus" - }, - "snakeCase": { - "unsafeName": "rule_response_status", - "safeName": "rule_response_status" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_RESPONSE_STATUS", - "safeName": "RULE_RESPONSE_STATUS" - }, - "pascalCase": { - "unsafeName": "RuleResponseStatus", - "safeName": "RuleResponseStatus" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "values": [ - { - "wireValue": "active", - "name": { - "originalName": "active", - "camelCase": { - "unsafeName": "active", - "safeName": "active" - }, - "snakeCase": { - "unsafeName": "active", - "safeName": "active" - }, - "screamingSnakeCase": { - "unsafeName": "ACTIVE", - "safeName": "ACTIVE" - }, - "pascalCase": { - "unsafeName": "Active", - "safeName": "Active" - } - } - }, - { - "wireValue": "inactive", - "name": { - "originalName": "inactive", - "camelCase": { - "unsafeName": "inactive", - "safeName": "inactive" - }, - "snakeCase": { - "unsafeName": "inactive", - "safeName": "inactive" - }, - "screamingSnakeCase": { - "unsafeName": "INACTIVE", - "safeName": "INACTIVE" - }, - "pascalCase": { - "unsafeName": "Inactive", - "safeName": "Inactive" - } - } - }, - { - "wireValue": "draft", - "name": { - "originalName": "draft", - "camelCase": { - "unsafeName": "draft", - "safeName": "draft" - }, - "snakeCase": { - "unsafeName": "draft", - "safeName": "draft" - }, - "screamingSnakeCase": { - "unsafeName": "DRAFT", - "safeName": "DRAFT" - }, - "pascalCase": { - "unsafeName": "Draft", - "safeName": "Draft" - } - } - } - ], - "type": "enum" - }, - "type_:RuleResponse": { - "declaration": { - "name": { - "originalName": "RuleResponse", - "camelCase": { - "unsafeName": "ruleResponse", - "safeName": "ruleResponse" - }, - "snakeCase": { - "unsafeName": "rule_response", - "safeName": "rule_response" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_RESPONSE", - "safeName": "RULE_RESPONSE" - }, - "pascalCase": { - "unsafeName": "RuleResponse", - "safeName": "RuleResponse" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "properties": [ - { - "name": { - "wireValue": "id", - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "ID", - "safeName": "ID" - } - } - }, - "typeReference": { - "value": "STRING", - "type": "primitive" - } - }, - { - "name": { - "wireValue": "name", - "name": { - "originalName": "name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - } - }, - "typeReference": { - "value": "STRING", - "type": "primitive" - } - }, - { - "name": { - "wireValue": "status", - "name": { - "originalName": "status", - "camelCase": { - "unsafeName": "status", - "safeName": "status" - }, - "snakeCase": { - "unsafeName": "status", - "safeName": "status" - }, - "screamingSnakeCase": { - "unsafeName": "STATUS", - "safeName": "STATUS" - }, - "pascalCase": { - "unsafeName": "Status", - "safeName": "Status" - } - } - }, - "typeReference": { - "value": "type_:RuleResponseStatus", - "type": "named" - } - }, - { - "name": { - "wireValue": "executionContext", - "name": { - "originalName": "executionContext", - "camelCase": { - "unsafeName": "executionContext", - "safeName": "executionContext" - }, - "snakeCase": { - "unsafeName": "execution_context", - "safeName": "execution_context" - }, - "screamingSnakeCase": { - "unsafeName": "EXECUTION_CONTEXT", - "safeName": "EXECUTION_CONTEXT" - }, - "pascalCase": { - "unsafeName": "ExecutionContext", - "safeName": "ExecutionContext" - } - } - }, - "typeReference": { - "value": "type_:RuleExecutionContext", - "type": "named" - } - } - ], - "additionalProperties": false, - "type": "object" - } - }, - "headers": [], - "endpoints": { - "endpoint_.searchRuleTypes": { - "declaration": { - "name": { - "originalName": "searchRuleTypes", - "camelCase": { - "unsafeName": "searchRuleTypes", - "safeName": "searchRuleTypes" - }, - "snakeCase": { - "unsafeName": "search_rule_types", - "safeName": "search_rule_types" - }, - "screamingSnakeCase": { - "unsafeName": "SEARCH_RULE_TYPES", - "safeName": "SEARCH_RULE_TYPES" - }, - "pascalCase": { - "unsafeName": "SearchRuleTypes", - "safeName": "SearchRuleTypes" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "location": { - "method": "GET", - "path": "/v1/preview/1/rule-types" - }, - "request": { - "declaration": { - "name": { - "originalName": "SearchRuleTypesRequest", - "camelCase": { - "unsafeName": "searchRuleTypesRequest", - "safeName": "searchRuleTypesRequest" - }, - "snakeCase": { - "unsafeName": "search_rule_types_request", - "safeName": "search_rule_types_request" - }, - "screamingSnakeCase": { - "unsafeName": "SEARCH_RULE_TYPES_REQUEST", - "safeName": "SEARCH_RULE_TYPES_REQUEST" - }, - "pascalCase": { - "unsafeName": "SearchRuleTypesRequest", - "safeName": "SearchRuleTypesRequest" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "pathParameters": [], - "queryParameters": [ - { - "name": { - "wireValue": "query", - "name": { - "originalName": "query", - "camelCase": { - "unsafeName": "query", - "safeName": "query" - }, - "snakeCase": { - "unsafeName": "query", - "safeName": "query" - }, - "screamingSnakeCase": { - "unsafeName": "QUERY", - "safeName": "QUERY" - }, - "pascalCase": { - "unsafeName": "Query", - "safeName": "Query" - } - } - }, - "typeReference": { - "value": { - "value": "STRING", - "type": "primitive" - }, - "type": "optional" - } - } - ], - "headers": [], - "metadata": { - "includePathParameters": false, - "onlyPathParameters": false - }, - "type": "inlined" - }, - "response": { - "type": "json" - }, - "examples": [] - }, - "endpoint_.createRule": { - "declaration": { - "name": { - "originalName": "createRule", - "camelCase": { - "unsafeName": "createRule", - "safeName": "createRule" - }, - "snakeCase": { - "unsafeName": "create_rule", - "safeName": "create_rule" - }, - "screamingSnakeCase": { - "unsafeName": "CREATE_RULE", - "safeName": "CREATE_RULE" - }, - "pascalCase": { - "unsafeName": "CreateRule", - "safeName": "CreateRule" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "location": { - "method": "POST", - "path": "/v1/preview/1/rules" - }, - "request": { - "declaration": { - "name": { - "originalName": "RuleCreateRequest", - "camelCase": { - "unsafeName": "ruleCreateRequest", - "safeName": "ruleCreateRequest" - }, - "snakeCase": { - "unsafeName": "rule_create_request", - "safeName": "rule_create_request" - }, - "screamingSnakeCase": { - "unsafeName": "RULE_CREATE_REQUEST", - "safeName": "RULE_CREATE_REQUEST" - }, - "pascalCase": { - "unsafeName": "RuleCreateRequest", - "safeName": "RuleCreateRequest" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "pathParameters": [], - "queryParameters": [], - "headers": [], - "body": { - "value": [ - { - "name": { - "wireValue": "name", - "name": { - "originalName": "name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - } - }, - "typeReference": { - "value": "STRING", - "type": "primitive" - } - }, - { - "name": { - "wireValue": "executionContext", - "name": { - "originalName": "executionContext", - "camelCase": { - "unsafeName": "executionContext", - "safeName": "executionContext" - }, - "snakeCase": { - "unsafeName": "execution_context", - "safeName": "execution_context" - }, - "screamingSnakeCase": { - "unsafeName": "EXECUTION_CONTEXT", - "safeName": "EXECUTION_CONTEXT" - }, - "pascalCase": { - "unsafeName": "ExecutionContext", - "safeName": "ExecutionContext" - } - } - }, - "typeReference": { - "value": "type_:RuleExecutionContext", - "type": "named" - } - } - ], - "type": "properties" - }, - "metadata": { - "includePathParameters": false, - "onlyPathParameters": false - }, - "type": "inlined" - }, - "response": { - "type": "json" - }, - "examples": [] - } - }, - "pathParameters": [], - "environments": { - "defaultEnvironment": "Default", - "environments": { - "environments": [ - { - "id": "Default", - "name": { - "originalName": "Default", - "camelCase": { - "unsafeName": "default", - "safeName": "default" - }, - "snakeCase": { - "unsafeName": "default", - "safeName": "default" - }, - "screamingSnakeCase": { - "unsafeName": "DEFAULT", - "safeName": "DEFAULT" - }, - "pascalCase": { - "unsafeName": "Default", - "safeName": "Default" - } - }, - "url": "https://api.spscommerce.com" - } - ], - "type": "singleBaseUrl" - } - } - }, - "apiPlayground": true, - "casingsConfig": { - "smartCasing": true - }, - "subpackages": {}, - "rootPackage": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "service": "service_", - "types": [ - "type_:PagingCursors", - "type_:PaginatedResult", - "type_:RuleExecutionContext", - "type_:RuleTypeResponseStatus", - "type_:RuleTypeResponse", - "type_:RuleTypeSearchResponse", - "type_:RuleResponseStatus", - "type_:RuleResponse" - ], - "errors": [], - "subpackages": [], - "hasEndpointsInTree": true - }, - "sdkConfig": { - "isAuthMandatory": false, - "hasStreamingEndpoints": false, - "hasPaginatedEndpoints": false, - "hasFileDownloadEndpoints": false, - "platformHeaders": { - "language": "X-Fern-Language", - "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" - } - } -} \ No newline at end of file diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json deleted file mode 100644 index 13a75af96e77..000000000000 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof-spscommerce.json +++ /dev/null @@ -1,1781 +0,0 @@ -{ - "auth": { - "requirement": "ALL", - "schemes": [] - }, - "selfHosted": false, - "types": { - "PagingCursors": { - "name": { - "typeId": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PagingCursors" - }, - "shape": { - "properties": [ - { - "name": "next", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "PagingCursorsNext_example_autogenerated": "string" - } - } - }, - { - "name": "previous", - "valueType": { - "container": { - "optional": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "PagingCursorsPrevious_example_autogenerated": "string" - } - } - } - ], - "extends": [], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "PagingCursors_example_autogenerated": { - "next": "string" - } - } - } - }, - "PaginatedResult": { - "name": { - "typeId": "PaginatedResult", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PaginatedResult" - }, - "shape": { - "properties": [ - { - "name": "paging", - "valueType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PagingCursors", - "typeId": "PagingCursors", - "inline": false, - "displayName": "PagingCursors", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "PaginatedResultPaging_example_autogenerated": { - "next": "string", - "previous": "string" - } - } - } - }, - { - "name": "results", - "valueType": { - "container": { - "list": { - "type": "unknown" - }, - "type": "list" - }, - "type": "container" - }, - "docs": "Current page of results from the requested resource.", - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "PaginatedResultResults_example_autogenerated": [ - null - ] - } - } - } - ], - "extends": [], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "PaginatedResult_example_autogenerated": { - "paging": { - "next": "string" - }, - "results": [ - null - ] - } - } - } - }, - "RuleExecutionContext": { - "name": { - "typeId": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleExecutionContext" - }, - "shape": { - "values": [ - { - "name": "prod" - }, - { - "name": "staging" - }, - { - "name": "dev" - } - ], - "type": "enum" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "docs": "The execution environment for a rule.", - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleExecutionContext_example_autogenerated": "prod" - } - } - }, - "RuleTypeResponseStatus": { - "name": { - "typeId": "RuleTypeResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponseStatus" - }, - "shape": { - "values": [ - { - "name": "active" - }, - { - "name": "inactive" - } - ], - "type": "enum" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeResponseStatus_example_autogenerated": "active" - } - } - }, - "RuleTypeResponse": { - "name": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "shape": { - "properties": [ - { - "name": "id", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeResponseId_example_autogenerated": "string" - } - } - }, - { - "name": "name", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeResponseName_example_autogenerated": "string" - } - } - }, - { - "name": "description", - "valueType": { - "container": { - "optional": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeResponseDescription_example_autogenerated": "string" - } - } - }, - { - "name": "status", - "valueType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponseStatus", - "typeId": "RuleTypeResponseStatus", - "inline": false, - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeResponseStatus_example_autogenerated": "active" - } - } - } - ], - "extends": [], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeResponse_example_autogenerated": { - "id": "string", - "name": "string", - "status": "active" - } - } - } - }, - "RuleTypeSearchResponse": { - "name": { - "typeId": "RuleTypeSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeSearchResponse" - }, - "shape": { - "properties": [ - { - "name": "paging", - "valueType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PagingCursors", - "typeId": "PagingCursors", - "inline": false, - "displayName": "PagingCursors", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeSearchResponsePaging_example_autogenerated": { - "next": "string", - "previous": "string" - } - } - } - }, - { - "name": "results", - "valueType": { - "container": { - "list": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse", - "typeId": "RuleTypeResponse", - "inline": false, - "displayName": "RuleTypeResponse", - "type": "named" - }, - "type": "list" - }, - "type": "container" - }, - "docs": "Current page of results from the requested resource.", - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeSearchResponseResults_example_autogenerated": [ - { - "id": "string", - "name": "string", - "status": "active" - } - ] - } - } - } - ], - "extends": [], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": true, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleTypeSearchResponse_example_autogenerated": { - "paging": { - "next": "string" - }, - "results": [ - { - "id": "string", - "name": "string", - "status": "active" - } - ] - } - } - } - }, - "RuleCreateRequest": { - "name": { - "typeId": "RuleCreateRequest", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleCreateRequest" - }, - "shape": { - "properties": [ - { - "name": "name", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleCreateRequestName_example_autogenerated": "string" - } - } - }, - { - "name": "executionContext", - "valueType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleExecutionContext", - "typeId": "RuleExecutionContext", - "inline": false, - "displayName": "RuleExecutionContext", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleCreateRequestExecutionContext_example_autogenerated": "prod" - } - } - } - ], - "extends": [], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleCreateRequest_example_autogenerated": { - "name": "string", - "executionContext": "prod" - } - } - } - }, - "RuleResponseStatus": { - "name": { - "typeId": "RuleResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponseStatus" - }, - "shape": { - "values": [ - { - "name": "active" - }, - { - "name": "inactive" - }, - { - "name": "draft" - } - ], - "type": "enum" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleResponseStatus_example_autogenerated": "active" - } - } - }, - "RuleResponse": { - "name": { - "typeId": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse" - }, - "shape": { - "properties": [ - { - "name": "id", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleResponseId_example_autogenerated": "string" - } - } - }, - { - "name": "name", - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleResponseName_example_autogenerated": "string" - } - } - }, - { - "name": "status", - "valueType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponseStatus", - "typeId": "RuleResponseStatus", - "inline": false, - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleResponseStatus_example_autogenerated": "active" - } - } - }, - { - "name": "executionContext", - "valueType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleExecutionContext", - "typeId": "RuleExecutionContext", - "inline": false, - "displayName": "RuleExecutionContext", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleResponseExecutionContext_example_autogenerated": "prod" - } - } - } - ], - "extends": [], - "extendedProperties": [], - "extraProperties": false, - "type": "object" - }, - "autogeneratedExamples": [], - "userProvidedExamples": [], - "referencedTypes": {}, - "inline": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "RuleResponse_example_autogenerated": { - "id": "string", - "name": "string", - "status": "active", - "executionContext": "prod" - } - } - } - } - }, - "services": { - "service_": { - "name": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - } - }, - "basePath": { - "head": "", - "parts": [] - }, - "headers": [], - "pathParameters": [], - "endpoints": [ - { - "displayName": "Search for rule types", - "method": "GET", - "baseUrl": "https://api.spscommerce.com", - "path": { - "head": "/v1/preview/1/rule-types", - "parts": [] - }, - "pathParameters": [], - "queryParameters": [ - { - "name": "query", - "valueType": { - "container": { - "optional": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - }, - "allowMultiple": false, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "query_example": "query" - } - } - } - ], - "headers": [], - "responseHeaders": [], - "errors": [], - "auth": false, - "userSpecifiedExamples": [], - "autogeneratedExamples": [ - { - "example": { - "id": "d622963", - "url": "/v1/preview/1/rule-types", - "endpointHeaders": [], - "endpointPathParameters": [], - "queryParameters": [], - "servicePathParameters": [], - "serviceHeaders": [], - "rootPathParameters": [], - "response": { - "value": { - "value": { - "jsonExample": { - "paging": { - "next": "next", - "previous": "previous" - }, - "results": [ - { - "id": "id", - "name": "name", - "description": "description", - "status": "active" - }, - { - "id": "id", - "name": "name", - "description": "description", - "status": "active" - } - ] - }, - "shape": { - "shape": { - "properties": [ - { - "name": "paging", - "originalTypeDeclaration": { - "typeId": "RuleTypeSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeSearchResponse" - }, - "value": { - "jsonExample": { - "next": "next", - "previous": "previous" - }, - "shape": { - "shape": { - "properties": [ - { - "name": "next", - "originalTypeDeclaration": { - "typeId": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PagingCursors" - }, - "value": { - "jsonExample": "next", - "shape": { - "primitive": { - "string": { - "original": "next" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "previous", - "originalTypeDeclaration": { - "typeId": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PagingCursors" - }, - "value": { - "jsonExample": "previous", - "shape": { - "container": { - "optional": { - "jsonExample": "previous", - "shape": { - "primitive": { - "string": { - "original": "previous" - }, - "type": "string" - }, - "type": "primitive" - } - }, - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - } - } - } - ], - "type": "object" - }, - "typeName": { - "typeId": "PagingCursors", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "PagingCursors" - }, - "type": "named" - } - } - }, - { - "name": "results", - "originalTypeDeclaration": { - "typeId": "RuleTypeSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeSearchResponse" - }, - "value": { - "jsonExample": [ - { - "id": "id", - "name": "name", - "description": "description", - "status": "active" - }, - { - "id": "id", - "name": "name", - "description": "description", - "status": "active" - } - ], - "shape": { - "container": { - "list": [ - { - "jsonExample": { - "id": "id", - "name": "name", - "description": "description", - "status": "active" - }, - "shape": { - "shape": { - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "id", - "shape": { - "primitive": { - "string": { - "original": "id" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "name", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "name", - "shape": { - "primitive": { - "string": { - "original": "name" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "description", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "description", - "shape": { - "container": { - "optional": { - "jsonExample": "description", - "shape": { - "primitive": { - "string": { - "original": "description" - }, - "type": "string" - }, - "type": "primitive" - } - }, - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - } - } - }, - { - "name": "status", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "active", - "shape": { - "shape": { - "value": "active", - "type": "enum" - }, - "typeName": { - "typeId": "RuleTypeResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponseStatus" - }, - "type": "named" - } - } - } - ], - "type": "object" - }, - "typeName": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "type": "named" - } - }, - { - "jsonExample": { - "id": "id", - "name": "name", - "description": "description", - "status": "active" - }, - "shape": { - "shape": { - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "id", - "shape": { - "primitive": { - "string": { - "original": "id" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "name", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "name", - "shape": { - "primitive": { - "string": { - "original": "name" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "description", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "description", - "shape": { - "container": { - "optional": { - "jsonExample": "description", - "shape": { - "primitive": { - "string": { - "original": "description" - }, - "type": "string" - }, - "type": "primitive" - } - }, - "valueType": { - "primitive": { - "v1": "STRING", - "v2": { - "validation": {}, - "type": "string" - } - }, - "type": "primitive" - }, - "type": "optional" - }, - "type": "container" - } - } - }, - { - "name": "status", - "originalTypeDeclaration": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "value": { - "jsonExample": "active", - "shape": { - "shape": { - "value": "active", - "type": "enum" - }, - "typeName": { - "typeId": "RuleTypeResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponseStatus" - }, - "type": "named" - } - } - } - ], - "type": "object" - }, - "typeName": { - "typeId": "RuleTypeResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse" - }, - "type": "named" - } - } - ], - "itemType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeResponse", - "typeId": "RuleTypeResponse", - "inline": false, - "displayName": "RuleTypeResponse", - "type": "named" - }, - "type": "list" - }, - "type": "container" - } - } - } - ], - "type": "object" - }, - "typeName": { - "typeId": "RuleTypeSearchResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeSearchResponse" - }, - "type": "named" - } - }, - "type": "body" - }, - "type": "ok" - } - } - } - ], - "idempotent": false, - "fullPath": { - "head": "/v1/preview/1/rule-types", - "parts": [] - }, - "allPathParameters": [], - "source": { - "type": "openapi" - }, - "audiences": [], - "id": "endpoint_.searchRuleTypes", - "name": "searchRuleTypes", - "v2RequestBodies": {}, - "response": { - "statusCode": 200, - "body": { - "value": { - "responseBodyType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeSearchResponse", - "typeId": "RuleTypeSearchResponse", - "inline": false, - "displayName": "RuleTypeSearchResponse", - "type": "named" - }, - "docs": "Paginated list of rule types", - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "searchRuleTypesExample": { - "paging": { - "next": "string", - "previous": "string" - }, - "results": null - } - } - }, - "type": "response" - }, - "type": "json" - }, - "docs": "Paginated list of rule types" - }, - "v2Examples": { - "autogeneratedExamples": { - "base_searchRuleTypesExample_200": { - "displayName": "searchRuleTypesExample", - "request": { - "endpoint": { - "method": "GET", - "path": "/v1/preview/1/rule-types" - }, - "environment": "https://api.spscommerce.com", - "pathParameters": {}, - "queryParameters": {}, - "headers": {} - }, - "response": { - "statusCode": 200, - "body": { - "value": { - "paging": { - "next": "string", - "previous": "string" - }, - "results": null - }, - "type": "json" - } - } - } - }, - "userSpecifiedExamples": {} - }, - "v2Responses": { - "responses": [ - { - "statusCode": 200, - "body": { - "value": { - "responseBodyType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleTypeSearchResponse", - "typeId": "RuleTypeSearchResponse", - "inline": false, - "displayName": "RuleTypeSearchResponse", - "type": "named" - }, - "docs": "Paginated list of rule types", - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "searchRuleTypesExample": { - "paging": { - "next": "string", - "previous": "string" - }, - "results": null - } - } - }, - "type": "response" - }, - "type": "json" - }, - "docs": "Paginated list of rule types" - } - ] - } - }, - { - "displayName": "Create a rule", - "method": "POST", - "baseUrl": "https://api.spscommerce.com", - "path": { - "head": "/v1/preview/1/rules", - "parts": [] - }, - "pathParameters": [], - "queryParameters": [], - "headers": [], - "responseHeaders": [], - "errors": [], - "auth": false, - "userSpecifiedExamples": [], - "autogeneratedExamples": [ - { - "example": { - "id": "32b0112f", - "url": "/v1/preview/1/rules", - "endpointHeaders": [], - "endpointPathParameters": [], - "queryParameters": [], - "servicePathParameters": [], - "serviceHeaders": [], - "rootPathParameters": [], - "request": { - "jsonExample": { - "name": "name", - "executionContext": "prod" - }, - "shape": { - "shape": { - "properties": [ - { - "name": "name", - "originalTypeDeclaration": { - "typeId": "RuleCreateRequest", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleCreateRequest" - }, - "value": { - "jsonExample": "name", - "shape": { - "primitive": { - "string": { - "original": "name" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "executionContext", - "originalTypeDeclaration": { - "typeId": "RuleCreateRequest", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleCreateRequest" - }, - "value": { - "jsonExample": "prod", - "shape": { - "shape": { - "value": "prod", - "type": "enum" - }, - "typeName": { - "typeId": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleExecutionContext" - }, - "type": "named" - } - } - } - ], - "type": "object" - }, - "typeName": { - "typeId": "RuleCreateRequest", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleCreateRequest" - }, - "type": "named" - }, - "type": "reference" - }, - "response": { - "value": { - "value": { - "jsonExample": { - "id": "id", - "name": "name", - "status": "active", - "executionContext": "prod" - }, - "shape": { - "shape": { - "properties": [ - { - "name": "id", - "originalTypeDeclaration": { - "typeId": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse" - }, - "value": { - "jsonExample": "id", - "shape": { - "primitive": { - "string": { - "original": "id" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "name", - "originalTypeDeclaration": { - "typeId": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse" - }, - "value": { - "jsonExample": "name", - "shape": { - "primitive": { - "string": { - "original": "name" - }, - "type": "string" - }, - "type": "primitive" - } - } - }, - { - "name": "status", - "originalTypeDeclaration": { - "typeId": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse" - }, - "value": { - "jsonExample": "active", - "shape": { - "shape": { - "value": "active", - "type": "enum" - }, - "typeName": { - "typeId": "RuleResponseStatus", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponseStatus" - }, - "type": "named" - } - } - }, - { - "name": "executionContext", - "originalTypeDeclaration": { - "typeId": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse" - }, - "value": { - "jsonExample": "prod", - "shape": { - "shape": { - "value": "prod", - "type": "enum" - }, - "typeName": { - "typeId": "RuleExecutionContext", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleExecutionContext" - }, - "type": "named" - } - } - } - ], - "type": "object" - }, - "typeName": { - "typeId": "RuleResponse", - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse" - }, - "type": "named" - } - }, - "type": "body" - }, - "type": "ok" - } - } - } - ], - "idempotent": false, - "fullPath": { - "head": "/v1/preview/1/rules", - "parts": [] - }, - "allPathParameters": [], - "source": { - "type": "openapi" - }, - "audiences": [], - "id": "endpoint_.createRule", - "name": "createRule", - "requestBody": { - "contentType": "application/json", - "requestBodyType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleCreateRequest", - "typeId": "RuleCreateRequest", - "inline": false, - "displayName": "RuleCreateRequest", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "createRuleExample": { - "name": "string", - "executionContext": "prod" - } - } - }, - "type": "reference" - }, - "v2RequestBodies": { - "requestBodies": [ - { - "contentType": "application/json", - "requestBodyType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleCreateRequest", - "typeId": "RuleCreateRequest", - "inline": false, - "displayName": "RuleCreateRequest", - "type": "named" - }, - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "createRuleExample": { - "name": "string", - "executionContext": "prod" - } - } - }, - "type": "reference" - } - ] - }, - "response": { - "statusCode": 200, - "body": { - "value": { - "responseBodyType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse", - "typeId": "RuleResponse", - "inline": false, - "displayName": "RuleResponse", - "type": "named" - }, - "docs": "Created rule", - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "createRuleExample": { - "id": "string", - "name": "string", - "status": "active", - "executionContext": "prod" - } - } - }, - "type": "response" - }, - "type": "json" - }, - "docs": "Created rule" - }, - "v2Examples": { - "autogeneratedExamples": { - "createRuleExample_200": { - "displayName": "createRuleExample", - "request": { - "endpoint": { - "method": "POST", - "path": "/v1/preview/1/rules" - }, - "environment": "https://api.spscommerce.com", - "pathParameters": {}, - "queryParameters": {}, - "headers": {}, - "requestBody": { - "name": "string", - "executionContext": "prod" - } - }, - "response": { - "statusCode": 200, - "body": { - "value": { - "id": "string", - "name": "string", - "status": "active", - "executionContext": "prod" - }, - "type": "json" - } - } - } - }, - "userSpecifiedExamples": {} - }, - "v2Responses": { - "responses": [ - { - "statusCode": 200, - "body": { - "value": { - "responseBodyType": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "name": "RuleResponse", - "typeId": "RuleResponse", - "inline": false, - "displayName": "RuleResponse", - "type": "named" - }, - "docs": "Created rule", - "v2Examples": { - "userSpecifiedExamples": {}, - "autogeneratedExamples": { - "createRuleExample": { - "id": "string", - "name": "string", - "status": "active", - "executionContext": "prod" - } - } - }, - "type": "response" - }, - "type": "json" - }, - "docs": "Created rule" - } - ] - } - } - ] - } - }, - "errors": {}, - "webhookGroups": {}, - "headers": [], - "idempotencyHeaders": [], - "apiDisplayName": "allOf SPS Commerce Reproduction", - "pathParameters": [], - "errorDiscriminationStrategy": { - "type": "statusCode" - }, - "variables": [], - "serviceTypeReferenceInfo": { - "sharedTypes": [], - "typesReferencedOnlyByService": {} - }, - "environments": { - "defaultEnvironment": "https://api.spscommerce.com", - "environments": { - "environments": [ - { - "id": "https://api.spscommerce.com", - "name": "https://api.spscommerce.com", - "url": "https://api.spscommerce.com" - } - ], - "type": "singleBaseUrl" - } - }, - "rootPackage": { - "fernFilepath": { - "allParts": [], - "packagePath": [] - }, - "service": "service_", - "types": [ - "PagingCursors", - "PaginatedResult", - "RuleExecutionContext", - "RuleTypeResponse", - "RuleTypeSearchResponse", - "RuleCreateRequest", - "RuleResponse" - ], - "errors": [], - "subpackages": [], - "hasEndpointsInTree": false - }, - "subpackages": {}, - "sdkConfig": { - "hasFileDownloadEndpoints": false, - "hasPaginatedEndpoints": false, - "hasStreamingEndpoints": false, - "isAuthMandatory": true, - "platformHeaders": { - "language": "", - "sdkName": "", - "sdkVersion": "" - } - }, - "apiName": "allOf SPS Commerce Reproduction", - "constants": { - "errorInstanceIdKey": "errorInstanceId" - } -} \ No newline at end of file diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof.test.ts b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof.test.ts index 7d87f5ae7f31..f0440f784e93 100644 --- a/packages/cli/api-importers/v3-importer-tests/src/__test__/allof.test.ts +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/allof.test.ts @@ -180,6 +180,11 @@ describe("allOf edge cases", () => { const prop = findProperty(type, "executionContext"); expect(prop).toBeDefined(); + // executionContext is required in the OpenAPI fixture, so it should NOT + // be wrapped in optional + // biome-ignore lint/style/noNonNullAssertion: verified by prior expect + expect(getContainerType(prop!)).not.toBe("optional"); + // The property should reference the RuleExecutionContext enum. // It should NOT be: unknown, an empty object, or a named reference // to a synthetic type with zero properties. diff --git a/packages/cli/cli/changes/unreleased/fix-allof-composition.yml b/packages/cli/cli/changes/unreleased/fix-allof-composition.yml new file mode 100644 index 000000000000..75671e9e094c --- /dev/null +++ b/packages/cli/cli/changes/unreleased/fix-allof-composition.yml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json + +- summary: | + Fix allOf composition bugs in V3 OpenAPI importer: resolve $ref schemas + within multi-element allOf arrays instead of falling back to untyped objects, + deduplicate merged allOf refs for diamond-shaped inheritance, guard + variants-flattening and composition-keyword shortcuts against $ref-resolved + schemas, preserve inline status through allOf merge path, handle nullable + enum patterns in allOf shortcuts, and detect cycles across recursive + single-element allOf chains. + type: fix diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index a81e99f0785f..bf9b30b2ddcc 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,15 +1,4 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json -- version: 4.67.2 - changelogEntry: - - summary: | - Fix allOf composition bugs in V3 OpenAPI importer: resolve $ref schemas - within multi-element allOf arrays instead of falling back to untyped objects, - deduplicate merged allOf refs for diamond-shaped inheritance, guard - composition-keyword shortcuts against oneOf/anyOf/allOf resolved schemas, - and detect cycles across recursive single-element allOf chains. - type: fix - createdAt: "2026-04-10" - irVersion: 66 - version: 4.67.1 changelogEntry: - summary: | diff --git a/scripts/debug-allof-pipeline.ts b/scripts/debug-allof-pipeline.ts deleted file mode 100644 index b55bbb51c017..000000000000 --- a/scripts/debug-allof-pipeline.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { execSync } from "child_process"; -import { cpSync, existsSync, mkdirSync } from "fs"; -import { join, resolve } from "path"; - -const ROOT = resolve(__dirname, ".."); -const TEST_DEFS = join(ROOT, "test-definitions"); -const CLI = join(ROOT, "packages/cli/cli/dist/prod/cli.cjs"); -const RESULTS_BASE = join(ROOT, ".local/results"); - -const APIS = ["allof", "allof-inline"]; - -function fern(args: string): void { - const cmd = `FERN_NO_VERSION_REDIRECTION=true node --enable-source-maps ${CLI} ${args}`; - execSync(cmd, { cwd: TEST_DEFS, stdio: "inherit" }); -} - -function main(): void { - if (!existsSync(CLI)) { - process.stderr.write(`CLI not built. Run: pnpm fern:build\n`); - process.exit(1); - } - - const allResults: Array<{ api: string; name: string; ok: boolean }> = []; - - for (const api of APIS) { - const results = join(RESULTS_BASE, api); - mkdirSync(results, { recursive: true }); - - const steps: Array<{ name: string; run: () => void }> = [ - { - name: "openapi-ir", - run: () => fern(`openapi-ir ${results}/openapi-ir.json --api ${api}`) - }, - { - name: "write-definition", - run: () => { - fern(`write-definition --api ${api}`); - const defSrc = join(TEST_DEFS, `fern/apis/${api}/.definition`); - const defDst = join(results, ".definition"); - if (existsSync(defSrc)) { - cpSync(defSrc, defDst, { recursive: true }); - } else { - process.stderr.write(`Warning: ${defSrc} not found after write-definition\n`); - } - } - }, - { - name: "ir", - run: () => fern(`ir ${results}/ir.json --api ${api}`) - } - ]; - - process.stdout.write(`\n=== ${api} ===\n`); - - for (const step of steps) { - process.stdout.write(`\n--- ${step.name} ---\n`); - try { - step.run(); - process.stdout.write(`OK: ${step.name}\n`); - allResults.push({ api, name: step.name, ok: true }); - } catch { - process.stderr.write(`FAIL: ${step.name}\n`); - allResults.push({ api, name: step.name, ok: false }); - } - } - } - - process.stdout.write(`\n--- summary ---\n`); - for (const r of allResults) { - process.stdout.write(` ${r.ok ? "OK" : "FAIL"}: ${r.api}/${r.name}\n`); - } - process.stdout.write(`Results in ${RESULTS_BASE}/\n`); - - const failed = allResults.filter((r) => !r.ok); - if (failed.length > 0) { - process.exit(1); - } -} - -main(); From 53001ff2f32ad2492f98756c7b5637d43491bffe Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:56:10 +0000 Subject: [PATCH 22/29] fix(cli): address remaining review comments on allOf composition - Fix inlined: true -> inlined: this.inlined in single-element allOf path - Propagate inlinedTypes in both allOf shortcuts in SchemaOrReferenceConverter - Update stale test comment and remove duplicate biome-ignore - Update v3-sdks snapshots Co-Authored-By: judah --- .../src/converters/schema/SchemaConverter.ts | 2 +- .../schema/SchemaOrReferenceConverter.ts | 4 +- .../v3-sdks/allof-array-items-narrowing.json | 4 +- .../__test__/__snapshots__/v3-sdks/allof.json | 1781 +++++++++++++++++ .../examples-endpoint-level-named.json | 2 +- .../v3-sdks/examples-endpoint-level.json | 2 +- .../__snapshots__/v3-sdks/url-reference.json | 2 +- .../src/__test__/allof.test.ts | 3 +- 8 files changed, 1790 insertions(+), 10 deletions(-) create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/v3-sdks/allof.json diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index 34be2325b432..9095434764cd 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -204,7 +204,7 @@ export class SchemaConverter extends AbstractConverter { // Could be optional since the enum // doesn't have a default — but the inner type must still reference it // biome-ignore lint/style/noNonNullAssertion: verified by prior expect - // biome-ignore lint/style/noNonNullAssertion: verified by prior expect const container = prop!.valueType.container!; if (container._type === "optional") { const inner = container.optional as Record; From dabfa9eae674372141cbde225c60d0a0b8d4fca2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 18:44:55 +0000 Subject: [PATCH 23/29] fix(cli): separate resolvedRefs concerns and add format keyword guard - SchemaConverter.ts: Use separate localResolvedRefs set for tracking refs within current allOf array. Check this.visitedRefs for ancestor cycles only. Same-array duplicate $refs now skip (continue) instead of triggering hasCycle=true. - SchemaOrReferenceConverter.ts: Add !s.format guard to inline elements check and !resolved.format guard to resolved schema check in maybeConvertSingularAllOfReferenceObject, preventing the shortcut from discarding inline format constraints like {format: "date-time"}. Co-Authored-By: bot_apk --- .../src/converters/schema/SchemaConverter.ts | 19 ++++++++++++++----- .../schema/SchemaOrReferenceConverter.ts | 7 +++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index 9095434764cd..c61ac93d41bb 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -223,18 +223,26 @@ export class SchemaConverter extends AbstractConverter = {}; - const resolvedRefs = new Set(this.visitedRefs); + // Track refs resolved in THIS allOf array (for dedup only). + // Ancestor cycle detection uses the separate `this.visitedRefs` set. + const localResolvedRefs = new Set(); let hasCycle = false; for (const allOfSchema of this.schema.allOf ?? []) { let schemaToMerge: OpenAPIV3_1.SchemaObject; if (this.context.isReferenceObject(allOfSchema)) { const refPath = allOfSchema.$ref; - if (resolvedRefs.has(refPath)) { + // Check ancestor set for true cross-schema cycles + if (this.visitedRefs.has(refPath)) { hasCycle = true; break; } - resolvedRefs.add(refPath); + // Skip same-array duplicates (e.g. allOf: [$ref:Base, $ref:Base]) + // without triggering the cycle breaker + if (localResolvedRefs.has(refPath)) { + continue; + } + localResolvedRefs.add(refPath); const resolved = this.context.resolveMaybeReference({ schemaOrReference: allOfSchema, @@ -313,8 +321,9 @@ export class SchemaConverter extends AbstractConverter([...this.visitedRefs, ...localResolvedRefs]); if (Array.isArray(mergedSchema.allOf)) { - const seenRefs = new Set(resolvedRefs); + const seenRefs = new Set(allResolvedRefs); mergedSchema.allOf = ( mergedSchema.allOf as (OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject)[] ).filter((element) => { @@ -334,7 +343,7 @@ export class SchemaConverter extends AbstractConverter !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf) && + inlineElements.every( + (s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf && !s.format + ) && !inlineElements.some((s) => s.type === "null") ) { const resolved = this.context.resolveMaybeReference({ @@ -129,7 +131,8 @@ export class SchemaOrReferenceConverter extends AbstractConverter< !resolved.properties && !resolved.allOf && !resolved.oneOf && - !resolved.anyOf + !resolved.anyOf && + !resolved.format ) { const response = this.context.convertReferenceToTypeReference({ reference: singleRef as OpenAPIV3_1.ReferenceObject, From f2668916ce6d1b64dbecb9b9405547d86be36875 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:23:18 +0000 Subject: [PATCH 24/29] style: fix biome formatting for inline format guard Co-Authored-By: bot_apk --- .../src/converters/schema/SchemaOrReferenceConverter.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts index c2f2822e383a..caf7657ba7d6 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts @@ -116,9 +116,7 @@ export class SchemaOrReferenceConverter extends AbstractConverter< if ( singleRef != null && - inlineElements.every( - (s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf && !s.format - ) && + inlineElements.every((s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf && !s.format) && !inlineElements.some((s) => s.type === "null") ) { const resolved = this.context.resolveMaybeReference({ From 386686ce5901574e848e8cb50e33d8e2e04e5475 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:45:57 +0000 Subject: [PATCH 25/29] fix(cli): preserve outer schema metadata in shouldMergeAllOf path Copy TYPE_INVARIANT_KEYS (description, deprecated, example, default, readOnly, writeOnly, etc.) from the outer this.schema into mergedSchema before constructing mergedConverter, so metadata fields are not silently dropped for schemas routed through the shouldMergeAllOf path. Co-Authored-By: bot_apk --- .../src/converters/schema/SchemaConverter.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index c61ac93d41bb..ed1b470ca86d 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -337,6 +337,14 @@ export class SchemaConverter extends AbstractConverter Date: Tue, 14 Apr 2026 16:33:03 -0400 Subject: [PATCH 26/29] refactor(cli): replace lodash.mergeWith with per-key allOf schema merge Replace the generic lodash.mergeWith deep merge in the shouldMergeAllOf path with a purpose-built mergeAllOfSchemas function that uses explicit per-key strategies for OpenAPI schema keywords: - required: set union - properties/items: recursive deep merge - example/default: deep merge objects - deprecated/readOnly/writeOnly: OR (true if any says true) - min/max constraints: most restrictive value - description/type/format/etc: outer schema wins - allOf from children: flattened before merge (prevents leakage) This eliminates the post-merge fixup code (allOf dedup block, TYPE_INVARIANT_KEYS loop) that was the source of cascading review issues. Also adds !s.items and array-form null-type guards to the allOf shortcut in SchemaOrReferenceConverter. --- .../src/converters/schema/SchemaConverter.ts | 52 +- .../schema/SchemaOrReferenceConverter.ts | 6 +- .../schema/__test__/mergeAllOfSchemas.test.ts | 148 ++ .../converters/schema/mergeAllOfSchemas.ts | 207 ++ .../__snapshots__/baseline-sdks/allof.json | 1847 +++++++++++++++++ 5 files changed, 2213 insertions(+), 47 deletions(-) create mode 100644 packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts create mode 100644 packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts create mode 100644 packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof.json diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index ed1b470ca86d..b688cdea689f 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -1,13 +1,12 @@ import * as FernIr from "@fern-api/ir-sdk"; -import { mergeWith } from "lodash-es"; import { OpenAPIV3_1 } from "openapi-types"; - import { AbstractConverter, AbstractConverterContext, Extensions } from "../../index.js"; import { createTypeReferenceFromFernType } from "../../utils/CreateTypeReferenceFromFernType.js"; import { ExampleConverter } from "../ExampleConverter.js"; import { ArraySchemaConverter } from "./ArraySchemaConverter.js"; import { EnumSchemaConverter } from "./EnumSchemaConverter.js"; import { MapSchemaConverter } from "./MapSchemaConverter.js"; +import { mergeAllOfSchemas } from "./mergeAllOfSchemas.js"; import { ObjectSchemaConverter } from "./ObjectSchemaConverter.js"; import { OneOfSchemaConverter } from "./OneOfSchemaConverter.js"; import { PrimitiveSchemaConverter } from "./PrimitiveSchemaConverter.js"; @@ -222,11 +221,10 @@ export class SchemaConverter extends AbstractConverter= 1; if (shouldMergeAllOf) { - let mergedSchema: Record = {}; - // Track refs resolved in THIS allOf array (for dedup only). - // Ancestor cycle detection uses the separate `this.visitedRefs` set. const localResolvedRefs = new Set(); + const resolvedElements: OpenAPIV3_1.SchemaObject[] = []; let hasCycle = false; + for (const allOfSchema of this.schema.allOf ?? []) { let schemaToMerge: OpenAPIV3_1.SchemaObject; @@ -293,23 +291,12 @@ export class SchemaConverter extends AbstractConverter 0) { - // Add flattened properties as optional on the merged schema - const existingProperties = (mergedSchema.properties as Record) ?? {}; - mergedSchema.properties = { ...existingProperties, ...flattenedProperties }; - // Do not add to required — variant properties are optional on the parent + resolvedElements.push({ properties: flattenedProperties } as OpenAPIV3_1.SchemaObject); } continue; } - mergedSchema = mergeWith(mergedSchema, schemaToMerge, (objValue, srcValue) => { - if (srcValue === schemaToMerge) { - return objValue; - } - if (Array.isArray(objValue) && Array.isArray(srcValue)) { - return [...objValue, ...srcValue]; - } - return undefined; - }); + resolvedElements.push(schemaToMerge); } // If a circular reference was detected, fall back to the ObjectSchemaConverter path @@ -317,34 +304,9 @@ export class SchemaConverter extends AbstractConverter([...this.visitedRefs, ...localResolvedRefs]); - if (Array.isArray(mergedSchema.allOf)) { - const seenRefs = new Set(allResolvedRefs); - mergedSchema.allOf = ( - mergedSchema.allOf as (OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject)[] - ).filter((element) => { - if (this.context.isReferenceObject(element)) { - if (seenRefs.has(element.$ref)) { - return false; - } - seenRefs.add(element.$ref); - } - return true; - }); - } - - // Preserve outer schema metadata on the merged result so that - // description, deprecated, example, default, etc. are not silently dropped. - for (const key of TYPE_INVARIANT_KEYS) { - if (this.schema[key as keyof typeof this.schema] != null && mergedSchema[key] == null) { - mergedSchema[key] = this.schema[key as keyof typeof this.schema]; - } - } + const mergedSchema = mergeAllOfSchemas(this.schema, resolvedElements); + const allResolvedRefs = new Set([...this.visitedRefs, ...localResolvedRefs]); const mergedConverter = new SchemaConverter({ context: this.context, breadcrumbs: this.breadcrumbs, diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts index caf7657ba7d6..91cde949eb81 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts @@ -116,8 +116,10 @@ export class SchemaOrReferenceConverter extends AbstractConverter< if ( singleRef != null && - inlineElements.every((s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf && !s.format) && - !inlineElements.some((s) => s.type === "null") + inlineElements.every( + (s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf && !s.format && !s.items + ) && + !inlineElements.some((s) => s.type === "null" || (Array.isArray(s.type) && s.type.includes("null"))) ) { const resolved = this.context.resolveMaybeReference({ schemaOrReference: singleRef, diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts new file mode 100644 index 000000000000..ad8656f7f60c --- /dev/null +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts @@ -0,0 +1,148 @@ +import { describe, expect, it } from "vitest"; +import { mergeAllOfSchemas } from "../mergeAllOfSchemas.js"; + +describe("mergeAllOfSchemas", () => { + describe("properties", () => { + it("unions property keys from multiple schemas", () => { + const result = mergeAllOfSchemas({} as any, [ + { properties: { a: { type: "string" } } } as any, + { properties: { b: { type: "number" } } } as any + ]); + expect(result.properties).toEqual({ + a: { type: "string" }, + b: { type: "number" } + }); + }); + + it("deep-merges same-named object properties (Case 6: metadata)", () => { + const result = mergeAllOfSchemas({} as any, [ + { + properties: { + metadata: { + type: "object", + properties: { region: { type: "string" }, tier: { type: "string" } } + } + } + } as any, + { + properties: { + metadata: { + type: "object", + properties: { region: { type: "string" }, domain: { type: "string" } } + } + } + } as any + ]); + const metadata = (result.properties as any).metadata; + expect(Object.keys(metadata.properties)).toEqual(expect.arrayContaining(["region", "tier", "domain"])); + }); + }); + + describe("required", () => { + it("set-unions required arrays", () => { + const result = mergeAllOfSchemas({} as any, [ + { required: ["a", "b"] } as any, + { required: ["b", "c"] } as any + ]); + expect(result.required).toEqual(expect.arrayContaining(["a", "b", "c"])); + expect(result.required).toHaveLength(3); + }); + }); + + describe("items narrowing", () => { + it("merges empty items with typed items (Case 1)", () => { + const result = mergeAllOfSchemas({} as any, [ + { + type: "object", + properties: { results: { type: "array", items: {} } } + } as any, + { + properties: { results: { items: { $ref: "#/components/schemas/RuleType" } } } + } as any + ]); + const results = (result.properties as any).results; + expect(results.type).toBe("array"); + expect(results.items).toEqual({ $ref: "#/components/schemas/RuleType" }); + }); + }); + + describe("outer schema precedence", () => { + it("outer description wins over child description", () => { + const result = mergeAllOfSchemas({ description: "Outer wins", allOf: [] } as any, [ + { description: "From child" } as any + ]); + expect(result.description).toBe("Outer wins"); + }); + + it("child description used when outer has none", () => { + const result = mergeAllOfSchemas({ allOf: [] } as any, [{ description: "From child" } as any]); + expect(result.description).toBe("From child"); + }); + + it("outer type wins over child type", () => { + const result = mergeAllOfSchemas({ type: "object", allOf: [] } as any, [{ type: "string" } as any]); + expect(result.type).toBe("object"); + }); + }); + + describe("OR keys", () => { + it("deprecated is true if any child says true", () => { + const result = mergeAllOfSchemas({} as any, [{ deprecated: false } as any, { deprecated: true } as any]); + expect(result.deprecated).toBe(true); + }); + + it("readOnly is true if any child says true", () => { + const result = mergeAllOfSchemas({} as any, [{} as any, { readOnly: true } as any]); + expect(result.readOnly).toBe(true); + }); + }); + + describe("constraint merging", () => { + it("takes the larger minimum (most restrictive)", () => { + const result = mergeAllOfSchemas({} as any, [{ minimum: 5 } as any, { minimum: 10 } as any]); + expect(result.minimum).toBe(10); + }); + + it("takes the smaller maximum (most restrictive)", () => { + const result = mergeAllOfSchemas({} as any, [{ maxLength: 100 } as any, { maxLength: 50 } as any]); + expect(result.maxLength).toBe(50); + }); + }); + + describe("nested allOf flattening", () => { + it("extracts nested allOf items and merges sibling keys", () => { + const result = mergeAllOfSchemas({} as any, [ + { + required: ["id"], + properties: { id: { type: "string" } }, + allOf: [{ required: ["name"], properties: { name: { type: "string" } } }] + } as any + ]); + expect(result.required).toEqual(expect.arrayContaining(["id", "name"])); + expect((result.properties as any).id).toBeDefined(); + expect((result.properties as any).name).toBeDefined(); + }); + + it("does not include allOf key on the result", () => { + const result = mergeAllOfSchemas({} as any, [ + { + properties: { a: { type: "string" } }, + allOf: [{ properties: { b: { type: "number" } } }] + } as any + ]); + expect(result.allOf).toBeUndefined(); + }); + }); + + describe("last-writer-wins fallback", () => { + it("last schema's pattern wins", () => { + const result = mergeAllOfSchemas({} as any, [{ pattern: "^a" } as any, { pattern: "^b" } as any]); + expect(result.pattern).toBe("^b"); + }); + + it("last schema's enum wins", () => { + const result = mergeAllOfSchemas({} as any, [{ enum: ["a", "b"] } as any, { enum: ["c", "d"] } as any]); + expect(result.enum).toEqual(["c", "d"]); + }); + }); +}); diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts new file mode 100644 index 000000000000..8b77e7bf074a --- /dev/null +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts @@ -0,0 +1,207 @@ +import { OpenAPIV3_1 } from "openapi-types"; + +const OUTER_WINS_KEYS = [ + "description", + "title", + "format", + "type", + "discriminator", + "xml", + "externalDocs", + "extensions" +]; + +/** + * Keys whose values should be deep-merged when both sides are objects + * (e.g., allOf composing examples from multiple parents), with the + * outer schema winning if present. + */ +const DEEP_MERGE_KEYS = ["example", "default"]; + +const OR_KEYS = ["deprecated", "readOnly", "writeOnly", "uniqueItems", "nullable"]; + +const MAX_OF_MINS_KEYS = ["minimum", "exclusiveMinimum", "minLength", "minItems", "minProperties"]; + +const MIN_OF_MAXS_KEYS = ["maximum", "exclusiveMaximum", "maxLength", "maxItems", "maxProperties"]; + +const SKIP_FROM_CHILDREN = ["allOf"]; + +export function mergeAllOfSchemas( + outerSchema: OpenAPIV3_1.SchemaObject, + elements: OpenAPIV3_1.SchemaObject[] +): OpenAPIV3_1.SchemaObject { + const flatElements = flattenNestedAllOf(elements); + + let result: Record = {}; + for (const element of flatElements) { + result = mergeSchemaElement(result, element); + } + + applyOuterSchema(result, outerSchema); + + return result as OpenAPIV3_1.SchemaObject; +} + +function flattenNestedAllOf(elements: OpenAPIV3_1.SchemaObject[]): OpenAPIV3_1.SchemaObject[] { + const flat: OpenAPIV3_1.SchemaObject[] = []; + for (const element of elements) { + if (Array.isArray(element.allOf) && element.allOf.length > 0) { + const { allOf, ...sibling } = element; + flat.push(...(allOf as OpenAPIV3_1.SchemaObject[])); + if (Object.keys(sibling).length > 0) { + flat.push(sibling as OpenAPIV3_1.SchemaObject); + } + } else { + flat.push(element); + } + } + return flat; +} + +function mergeSchemaElement( + target: Record, + source: OpenAPIV3_1.SchemaObject +): Record { + const result = { ...target }; + + for (const [key, sourceValue] of Object.entries(source)) { + if (sourceValue === undefined) { + continue; + } + + if (SKIP_FROM_CHILDREN.includes(key)) { + continue; + } + + const targetValue = result[key]; + + if (key === "required") { + const targetArr = Array.isArray(targetValue) ? (targetValue as string[]) : []; + const sourceArr = Array.isArray(sourceValue) ? (sourceValue as string[]) : []; + result[key] = [...new Set([...targetArr, ...sourceArr])]; + continue; + } + + if (key === "properties") { + result[key] = mergeProperties( + (targetValue as Record) ?? {}, + sourceValue as Record + ); + continue; + } + + if (key === "items") { + if (isPlainObject(targetValue) && isPlainObject(sourceValue)) { + result[key] = mergeSchemaElement( + targetValue as Record, + sourceValue as OpenAPIV3_1.SchemaObject + ); + } else { + result[key] = sourceValue; + } + continue; + } + + if (DEEP_MERGE_KEYS.includes(key)) { + // Deep-merge when both sides are objects (e.g., examples from + // multiple allOf parents). Otherwise last writer wins. + if (isPlainObject(targetValue) && isPlainObject(sourceValue)) { + result[key] = deepMergeObjects( + targetValue as Record, + sourceValue as Record + ); + } else { + result[key] = sourceValue; + } + continue; + } + + if (OR_KEYS.includes(key)) { + result[key] = Boolean(targetValue) || Boolean(sourceValue); + continue; + } + + if (MAX_OF_MINS_KEYS.includes(key)) { + if (typeof targetValue === "number" && typeof sourceValue === "number") { + result[key] = Math.max(targetValue, sourceValue); + } else { + result[key] = sourceValue; + } + continue; + } + + if (MIN_OF_MAXS_KEYS.includes(key)) { + if (typeof targetValue === "number" && typeof sourceValue === "number") { + result[key] = Math.min(targetValue, sourceValue); + } else { + result[key] = sourceValue; + } + continue; + } + + result[key] = sourceValue; + } + + return result; +} + +function mergeProperties(target: Record, source: Record): Record { + const result = { ...target }; + for (const [key, sourceValue] of Object.entries(source)) { + const targetValue = result[key]; + if (isPlainObject(targetValue) && isPlainObject(sourceValue)) { + result[key] = mergeSchemaElement( + targetValue as Record, + sourceValue as OpenAPIV3_1.SchemaObject + ); + } else { + result[key] = sourceValue; + } + } + return result; +} + +/** + * Deep-merges two plain objects. For same-named keys, if both are objects, + * recurses; otherwise source wins. Used for `example` and `default`. + */ +function deepMergeObjects(target: Record, source: Record): Record { + const result = { ...target }; + for (const [key, sourceValue] of Object.entries(source)) { + const targetValue = result[key]; + if (isPlainObject(targetValue) && isPlainObject(sourceValue)) { + result[key] = deepMergeObjects( + targetValue as Record, + sourceValue as Record + ); + } else { + result[key] = sourceValue; + } + } + return result; +} + +function applyOuterSchema(result: Record, outerSchema: OpenAPIV3_1.SchemaObject): void { + for (const key of OUTER_WINS_KEYS) { + const outerValue = outerSchema[key as keyof typeof outerSchema]; + if (outerValue != null) { + result[key] = outerValue; + } + } + for (const key of DEEP_MERGE_KEYS) { + const outerValue = outerSchema[key as keyof typeof outerSchema]; + if (outerValue != null) { + result[key] = outerValue; + } + } + for (const key of OR_KEYS) { + const outerValue = outerSchema[key as keyof typeof outerSchema]; + if (outerValue === true) { + result[key] = true; + } + } +} + +function isPlainObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} diff --git a/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof.json b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof.json new file mode 100644 index 000000000000..f7d519efd6e6 --- /dev/null +++ b/packages/cli/api-importers/v3-importer-tests/src/__test__/__snapshots__/baseline-sdks/allof.json @@ -0,0 +1,1847 @@ +{ + "selfHosted": false, + "apiName": "api", + "apiDisplayName": "allOf Reproduction", + "auth": { + "requirement": "ALL", + "schemes": [] + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_:PagingCursors": { + "name": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PagingCursors" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "next", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "previous", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:PaginatedResult": { + "name": { + "name": "PaginatedResult", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PaginatedResult" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "paging", + "valueType": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PagingCursors", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "docs": "Current page of results from the requested resource.", + "name": "results", + "valueType": { + "container": { + "list": { + "type": "unknown" + }, + "type": "list" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleExecutionContext": { + "docs": "The execution environment for a rule.", + "name": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleExecutionContext" + }, + "shape": { + "values": [ + { + "name": "prod" + }, + { + "name": "staging" + }, + { + "name": "dev" + } + ], + "type": "enum" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleTypeResponseStatus": { + "inline": true, + "name": { + "name": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponseStatus" + }, + "shape": { + "values": [ + { + "name": "active" + }, + { + "name": "inactive" + } + ], + "type": "enum" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleTypeResponse": { + "name": { + "name": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponse" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "description", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "status", + "valueType": { + "name": "RuleTypeResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponseStatus", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleTypeSearchResponse": { + "name": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeSearchResponse" + }, + "shape": { + "extends": [], + "properties": [ + { + "docs": "Current page of results from the requested resource.", + "name": "results", + "valueType": { + "container": { + "optional": { + "container": { + "list": { + "name": "RuleTypeResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeResponse", + "type": "named" + }, + "type": "list" + }, + "type": "container" + }, + "type": "optional" + }, + "type": "container" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "paging", + "valueType": { + "name": "PagingCursors", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:PagingCursors", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleResponseStatus": { + "inline": true, + "name": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponseStatus" + }, + "shape": { + "values": [ + { + "name": "active" + }, + { + "name": "inactive" + }, + { + "name": "draft" + } + ], + "type": "enum" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + }, + "type_:RuleResponse": { + "name": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponse" + }, + "shape": { + "extends": [], + "properties": [ + { + "name": "id", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "status", + "valueType": { + "name": "RuleResponseStatus", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponseStatus", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "executionContext", + "valueType": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleExecutionContext", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "object" + }, + "referencedTypes": {}, + "encoding": { + "json": {} + }, + "userProvidedExamples": [], + "autogeneratedExamples": [] + } + }, + "errors": {}, + "services": { + "service_": { + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {} + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_.searchRuleTypes", + "name": "searchRuleTypes", + "displayName": "Search for rule types", + "auth": false, + "idempotent": false, + "method": "GET", + "path": { + "head": "/v1/preview/1/rule-types", + "parts": [] + }, + "fullPath": { + "head": "v1/preview/1/rule-types", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [ + { + "name": "query", + "valueType": { + "container": { + "optional": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "type": "optional" + }, + "type": "container" + }, + "allowMultiple": false, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "headers": [], + "sdkRequest": { + "shape": { + "wrapperName": "SearchRuleTypesRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false, + "type": "wrapper" + }, + "requestParameterName": "request" + }, + "response": { + "body": { + "value": { + "docs": "Paginated list of rule types", + "responseBodyType": { + "name": "RuleTypeSearchResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleTypeSearchResponse", + "type": "named" + }, + "type": "response" + }, + "type": "json" + }, + "statusCode": 200, + "docs": "Paginated list of rule types" + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [], + "responseHeaders": [] + }, + { + "id": "endpoint_.createRule", + "name": "createRule", + "displayName": "Create a rule", + "auth": false, + "idempotent": false, + "method": "POST", + "path": { + "head": "/v1/preview/1/rules", + "parts": [] + }, + "fullPath": { + "head": "v1/preview/1/rules", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "name": "RuleCreateRequest", + "extends": [], + "contentType": "application/json", + "properties": [ + { + "name": "name", + "valueType": { + "primitive": { + "v1": "STRING", + "v2": { + "type": "string" + } + }, + "type": "primitive" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + }, + { + "name": "executionContext", + "valueType": { + "name": "RuleExecutionContext", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleExecutionContext", + "type": "named" + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + } + } + ], + "extraProperties": false, + "extendedProperties": [], + "type": "inlinedRequestBody" + }, + "sdkRequest": { + "shape": { + "wrapperName": "RuleCreateRequest", + "bodyKey": "body", + "includePathParameters": false, + "onlyPathParameters": false, + "type": "wrapper" + }, + "requestParameterName": "request" + }, + "response": { + "body": { + "value": { + "docs": "Created rule", + "responseBodyType": { + "name": "RuleResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "typeId": "type_:RuleResponse", + "type": "named" + }, + "type": "response" + }, + "type": "json" + }, + "statusCode": 200, + "docs": "Created rule" + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [], + "responseHeaders": [] + } + ] + } + }, + "constants": { + "errorInstanceIdKey": "errorInstanceId" + }, + "environments": { + "defaultEnvironment": "Default", + "environments": { + "environments": [ + { + "id": "Default", + "name": "Default", + "url": "https://api.allof.com" + } + ], + "type": "singleBaseUrl" + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_": [ + "type_:PagingCursors", + "type_:RuleExecutionContext", + "type_:RuleTypeResponseStatus", + "type_:RuleTypeResponse", + "type_:RuleTypeSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse" + ] + }, + "sharedTypes": [ + "type_:PaginatedResult" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "dynamic": { + "version": "1.0.0", + "types": { + "type_:PagingCursors": { + "declaration": { + "name": { + "originalName": "PagingCursors", + "camelCase": { + "unsafeName": "pagingCursors", + "safeName": "pagingCursors" + }, + "snakeCase": { + "unsafeName": "paging_cursors", + "safeName": "paging_cursors" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING_CURSORS", + "safeName": "PAGING_CURSORS" + }, + "pascalCase": { + "unsafeName": "PagingCursors", + "safeName": "PagingCursors" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "next", + "name": { + "originalName": "next", + "camelCase": { + "unsafeName": "next", + "safeName": "next" + }, + "snakeCase": { + "unsafeName": "next", + "safeName": "next" + }, + "screamingSnakeCase": { + "unsafeName": "NEXT", + "safeName": "NEXT" + }, + "pascalCase": { + "unsafeName": "Next", + "safeName": "Next" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "previous", + "name": { + "originalName": "previous", + "camelCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "snakeCase": { + "unsafeName": "previous", + "safeName": "previous" + }, + "screamingSnakeCase": { + "unsafeName": "PREVIOUS", + "safeName": "PREVIOUS" + }, + "pascalCase": { + "unsafeName": "Previous", + "safeName": "Previous" + } + } + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:PaginatedResult": { + "declaration": { + "name": { + "originalName": "PaginatedResult", + "camelCase": { + "unsafeName": "paginatedResult", + "safeName": "paginatedResult" + }, + "snakeCase": { + "unsafeName": "paginated_result", + "safeName": "paginated_result" + }, + "screamingSnakeCase": { + "unsafeName": "PAGINATED_RESULT", + "safeName": "PAGINATED_RESULT" + }, + "pascalCase": { + "unsafeName": "PaginatedResult", + "safeName": "PaginatedResult" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "value": "type_:PagingCursors", + "type": "named" + } + }, + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "value": { + "type": "unknown" + }, + "type": "list" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:RuleExecutionContext": { + "declaration": { + "name": { + "originalName": "RuleExecutionContext", + "camelCase": { + "unsafeName": "ruleExecutionContext", + "safeName": "ruleExecutionContext" + }, + "snakeCase": { + "unsafeName": "rule_execution_context", + "safeName": "rule_execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_EXECUTION_CONTEXT", + "safeName": "RULE_EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "RuleExecutionContext", + "safeName": "RuleExecutionContext" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "values": [ + { + "wireValue": "prod", + "name": { + "originalName": "prod", + "camelCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "snakeCase": { + "unsafeName": "prod", + "safeName": "prod" + }, + "screamingSnakeCase": { + "unsafeName": "PROD", + "safeName": "PROD" + }, + "pascalCase": { + "unsafeName": "Prod", + "safeName": "Prod" + } + } + }, + { + "wireValue": "staging", + "name": { + "originalName": "staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + } + }, + { + "wireValue": "dev", + "name": { + "originalName": "dev", + "camelCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "snakeCase": { + "unsafeName": "dev", + "safeName": "dev" + }, + "screamingSnakeCase": { + "unsafeName": "DEV", + "safeName": "DEV" + }, + "pascalCase": { + "unsafeName": "Dev", + "safeName": "Dev" + } + } + } + ], + "type": "enum" + }, + "type_:RuleTypeResponseStatus": { + "declaration": { + "name": { + "originalName": "RuleTypeResponseStatus", + "camelCase": { + "unsafeName": "ruleTypeResponseStatus", + "safeName": "ruleTypeResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_type_response_status", + "safeName": "rule_type_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_RESPONSE_STATUS", + "safeName": "RULE_TYPE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleTypeResponseStatus", + "safeName": "RuleTypeResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + } + ], + "type": "enum" + }, + "type_:RuleTypeResponse": { + "declaration": { + "name": { + "originalName": "RuleTypeResponse", + "camelCase": { + "unsafeName": "ruleTypeResponse", + "safeName": "ruleTypeResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_response", + "safeName": "rule_type_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_RESPONSE", + "safeName": "RULE_TYPE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeResponse", + "safeName": "RuleTypeResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "description", + "name": { + "originalName": "description", + "camelCase": { + "unsafeName": "description", + "safeName": "description" + }, + "snakeCase": { + "unsafeName": "description", + "safeName": "description" + }, + "screamingSnakeCase": { + "unsafeName": "DESCRIPTION", + "safeName": "DESCRIPTION" + }, + "pascalCase": { + "unsafeName": "Description", + "safeName": "Description" + } + } + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "value": "type_:RuleTypeResponseStatus", + "type": "named" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:RuleTypeSearchResponse": { + "declaration": { + "name": { + "originalName": "RuleTypeSearchResponse", + "camelCase": { + "unsafeName": "ruleTypeSearchResponse", + "safeName": "ruleTypeSearchResponse" + }, + "snakeCase": { + "unsafeName": "rule_type_search_response", + "safeName": "rule_type_search_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_TYPE_SEARCH_RESPONSE", + "safeName": "RULE_TYPE_SEARCH_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleTypeSearchResponse", + "safeName": "RuleTypeSearchResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "results", + "name": { + "originalName": "results", + "camelCase": { + "unsafeName": "results", + "safeName": "results" + }, + "snakeCase": { + "unsafeName": "results", + "safeName": "results" + }, + "screamingSnakeCase": { + "unsafeName": "RESULTS", + "safeName": "RESULTS" + }, + "pascalCase": { + "unsafeName": "Results", + "safeName": "Results" + } + } + }, + "typeReference": { + "value": { + "value": { + "value": "type_:RuleTypeResponse", + "type": "named" + }, + "type": "list" + }, + "type": "optional" + } + }, + { + "name": { + "wireValue": "paging", + "name": { + "originalName": "paging", + "camelCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "snakeCase": { + "unsafeName": "paging", + "safeName": "paging" + }, + "screamingSnakeCase": { + "unsafeName": "PAGING", + "safeName": "PAGING" + }, + "pascalCase": { + "unsafeName": "Paging", + "safeName": "Paging" + } + } + }, + "typeReference": { + "value": "type_:PagingCursors", + "type": "named" + } + } + ], + "additionalProperties": false, + "type": "object" + }, + "type_:RuleResponseStatus": { + "declaration": { + "name": { + "originalName": "RuleResponseStatus", + "camelCase": { + "unsafeName": "ruleResponseStatus", + "safeName": "ruleResponseStatus" + }, + "snakeCase": { + "unsafeName": "rule_response_status", + "safeName": "rule_response_status" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE_STATUS", + "safeName": "RULE_RESPONSE_STATUS" + }, + "pascalCase": { + "unsafeName": "RuleResponseStatus", + "safeName": "RuleResponseStatus" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "values": [ + { + "wireValue": "active", + "name": { + "originalName": "active", + "camelCase": { + "unsafeName": "active", + "safeName": "active" + }, + "snakeCase": { + "unsafeName": "active", + "safeName": "active" + }, + "screamingSnakeCase": { + "unsafeName": "ACTIVE", + "safeName": "ACTIVE" + }, + "pascalCase": { + "unsafeName": "Active", + "safeName": "Active" + } + } + }, + { + "wireValue": "inactive", + "name": { + "originalName": "inactive", + "camelCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "snakeCase": { + "unsafeName": "inactive", + "safeName": "inactive" + }, + "screamingSnakeCase": { + "unsafeName": "INACTIVE", + "safeName": "INACTIVE" + }, + "pascalCase": { + "unsafeName": "Inactive", + "safeName": "Inactive" + } + } + }, + { + "wireValue": "draft", + "name": { + "originalName": "draft", + "camelCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "snakeCase": { + "unsafeName": "draft", + "safeName": "draft" + }, + "screamingSnakeCase": { + "unsafeName": "DRAFT", + "safeName": "DRAFT" + }, + "pascalCase": { + "unsafeName": "Draft", + "safeName": "Draft" + } + } + } + ], + "type": "enum" + }, + "type_:RuleResponse": { + "declaration": { + "name": { + "originalName": "RuleResponse", + "camelCase": { + "unsafeName": "ruleResponse", + "safeName": "ruleResponse" + }, + "snakeCase": { + "unsafeName": "rule_response", + "safeName": "rule_response" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_RESPONSE", + "safeName": "RULE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "RuleResponse", + "safeName": "RuleResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "properties": [ + { + "name": { + "wireValue": "id", + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "status", + "name": { + "originalName": "status", + "camelCase": { + "unsafeName": "status", + "safeName": "status" + }, + "snakeCase": { + "unsafeName": "status", + "safeName": "status" + }, + "screamingSnakeCase": { + "unsafeName": "STATUS", + "safeName": "STATUS" + }, + "pascalCase": { + "unsafeName": "Status", + "safeName": "Status" + } + } + }, + "typeReference": { + "value": "type_:RuleResponseStatus", + "type": "named" + } + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "value": "type_:RuleExecutionContext", + "type": "named" + } + } + ], + "additionalProperties": false, + "type": "object" + } + }, + "headers": [], + "endpoints": { + "endpoint_.searchRuleTypes": { + "declaration": { + "name": { + "originalName": "searchRuleTypes", + "camelCase": { + "unsafeName": "searchRuleTypes", + "safeName": "searchRuleTypes" + }, + "snakeCase": { + "unsafeName": "search_rule_types", + "safeName": "search_rule_types" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES", + "safeName": "SEARCH_RULE_TYPES" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypes", + "safeName": "SearchRuleTypes" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "location": { + "method": "GET", + "path": "/v1/preview/1/rule-types" + }, + "request": { + "declaration": { + "name": { + "originalName": "SearchRuleTypesRequest", + "camelCase": { + "unsafeName": "searchRuleTypesRequest", + "safeName": "searchRuleTypesRequest" + }, + "snakeCase": { + "unsafeName": "search_rule_types_request", + "safeName": "search_rule_types_request" + }, + "screamingSnakeCase": { + "unsafeName": "SEARCH_RULE_TYPES_REQUEST", + "safeName": "SEARCH_RULE_TYPES_REQUEST" + }, + "pascalCase": { + "unsafeName": "SearchRuleTypesRequest", + "safeName": "SearchRuleTypesRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "pathParameters": [], + "queryParameters": [ + { + "name": { + "wireValue": "query", + "name": { + "originalName": "query", + "camelCase": { + "unsafeName": "query", + "safeName": "query" + }, + "snakeCase": { + "unsafeName": "query", + "safeName": "query" + }, + "screamingSnakeCase": { + "unsafeName": "QUERY", + "safeName": "QUERY" + }, + "pascalCase": { + "unsafeName": "Query", + "safeName": "Query" + } + } + }, + "typeReference": { + "value": { + "value": "STRING", + "type": "primitive" + }, + "type": "optional" + } + } + ], + "headers": [], + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + }, + "type": "inlined" + }, + "response": { + "type": "json" + }, + "examples": [] + }, + "endpoint_.createRule": { + "declaration": { + "name": { + "originalName": "createRule", + "camelCase": { + "unsafeName": "createRule", + "safeName": "createRule" + }, + "snakeCase": { + "unsafeName": "create_rule", + "safeName": "create_rule" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RULE", + "safeName": "CREATE_RULE" + }, + "pascalCase": { + "unsafeName": "CreateRule", + "safeName": "CreateRule" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "location": { + "method": "POST", + "path": "/v1/preview/1/rules" + }, + "request": { + "declaration": { + "name": { + "originalName": "RuleCreateRequest", + "camelCase": { + "unsafeName": "ruleCreateRequest", + "safeName": "ruleCreateRequest" + }, + "snakeCase": { + "unsafeName": "rule_create_request", + "safeName": "rule_create_request" + }, + "screamingSnakeCase": { + "unsafeName": "RULE_CREATE_REQUEST", + "safeName": "RULE_CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "RuleCreateRequest", + "safeName": "RuleCreateRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [] + } + }, + "pathParameters": [], + "queryParameters": [], + "headers": [], + "body": { + "value": [ + { + "name": { + "wireValue": "name", + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + } + }, + "typeReference": { + "value": "STRING", + "type": "primitive" + } + }, + { + "name": { + "wireValue": "executionContext", + "name": { + "originalName": "executionContext", + "camelCase": { + "unsafeName": "executionContext", + "safeName": "executionContext" + }, + "snakeCase": { + "unsafeName": "execution_context", + "safeName": "execution_context" + }, + "screamingSnakeCase": { + "unsafeName": "EXECUTION_CONTEXT", + "safeName": "EXECUTION_CONTEXT" + }, + "pascalCase": { + "unsafeName": "ExecutionContext", + "safeName": "ExecutionContext" + } + } + }, + "typeReference": { + "value": "type_:RuleExecutionContext", + "type": "named" + } + } + ], + "type": "properties" + }, + "metadata": { + "includePathParameters": false, + "onlyPathParameters": false + }, + "type": "inlined" + }, + "response": { + "type": "json" + }, + "examples": [] + } + }, + "pathParameters": [], + "environments": { + "defaultEnvironment": "Default", + "environments": { + "environments": [ + { + "id": "Default", + "name": { + "originalName": "Default", + "camelCase": { + "unsafeName": "default", + "safeName": "default" + }, + "snakeCase": { + "unsafeName": "default", + "safeName": "default" + }, + "screamingSnakeCase": { + "unsafeName": "DEFAULT", + "safeName": "DEFAULT" + }, + "pascalCase": { + "unsafeName": "Default", + "safeName": "Default" + } + }, + "url": "https://api.allof.com" + } + ], + "type": "singleBaseUrl" + } + } + }, + "apiPlayground": true, + "casingsConfig": { + "smartCasing": true + }, + "subpackages": {}, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [] + }, + "service": "service_", + "types": [ + "type_:PagingCursors", + "type_:PaginatedResult", + "type_:RuleExecutionContext", + "type_:RuleTypeResponseStatus", + "type_:RuleTypeResponse", + "type_:RuleTypeSearchResponse", + "type_:RuleResponseStatus", + "type_:RuleResponse" + ], + "errors": [], + "subpackages": [], + "hasEndpointsInTree": true + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version" + } + } +} \ No newline at end of file From 4bec8ca212c642e146582317f7211336cd1370c8 Mon Sep 17 00:00:00 2001 From: jsklan Date: Wed, 15 Apr 2026 10:34:37 -0400 Subject: [PATCH 27/29] fix(cli): fix type errors in allOf schema merge and eliminate `as any` in tests Use `"items" in s` instead of `s.items` to avoid TS error on NonArraySchemaObject, and replace all `as any` casts in mergeAllOfSchemas tests with proper OpenAPI types. --- .../schema/SchemaOrReferenceConverter.ts | 2 +- .../schema/__test__/mergeAllOfSchemas.test.ts | 77 +++++++++++-------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts index 91cde949eb81..8d346e7d0ccd 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaOrReferenceConverter.ts @@ -117,7 +117,7 @@ export class SchemaOrReferenceConverter extends AbstractConverter< if ( singleRef != null && inlineElements.every( - (s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf && !s.format && !s.items + (s) => !s.properties && !s.enum && !s.oneOf && !s.anyOf && !s.allOf && !s.format && !("items" in s) ) && !inlineElements.some((s) => s.type === "null" || (Array.isArray(s.type) && s.type.includes("null"))) ) { diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts index ad8656f7f60c..00c5bd2649d7 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/__test__/mergeAllOfSchemas.test.ts @@ -1,12 +1,15 @@ +import { OpenAPIV3_1 } from "openapi-types"; import { describe, expect, it } from "vitest"; import { mergeAllOfSchemas } from "../mergeAllOfSchemas.js"; +type Schema = OpenAPIV3_1.SchemaObject; + describe("mergeAllOfSchemas", () => { describe("properties", () => { it("unions property keys from multiple schemas", () => { - const result = mergeAllOfSchemas({} as any, [ - { properties: { a: { type: "string" } } } as any, - { properties: { b: { type: "number" } } } as any + const result = mergeAllOfSchemas({} as Schema, [ + { properties: { a: { type: "string" } } } as Schema, + { properties: { b: { type: "number" } } } as Schema ]); expect(result.properties).toEqual({ a: { type: "string" }, @@ -15,7 +18,7 @@ describe("mergeAllOfSchemas", () => { }); it("deep-merges same-named object properties (Case 6: metadata)", () => { - const result = mergeAllOfSchemas({} as any, [ + const result = mergeAllOfSchemas({} as Schema, [ { properties: { metadata: { @@ -23,7 +26,7 @@ describe("mergeAllOfSchemas", () => { properties: { region: { type: "string" }, tier: { type: "string" } } } } - } as any, + } as Schema, { properties: { metadata: { @@ -31,18 +34,20 @@ describe("mergeAllOfSchemas", () => { properties: { region: { type: "string" }, domain: { type: "string" } } } } - } as any + } as Schema ]); - const metadata = (result.properties as any).metadata; - expect(Object.keys(metadata.properties)).toEqual(expect.arrayContaining(["region", "tier", "domain"])); + const metadata = result.properties?.["metadata"] as Schema; + expect(Object.keys(metadata.properties ?? {})).toEqual( + expect.arrayContaining(["region", "tier", "domain"]) + ); }); }); describe("required", () => { it("set-unions required arrays", () => { - const result = mergeAllOfSchemas({} as any, [ - { required: ["a", "b"] } as any, - { required: ["b", "c"] } as any + const result = mergeAllOfSchemas({} as Schema, [ + { required: ["a", "b"] } as Schema, + { required: ["b", "c"] } as Schema ]); expect(result.required).toEqual(expect.arrayContaining(["a", "b", "c"])); expect(result.required).toHaveLength(3); @@ -51,84 +56,89 @@ describe("mergeAllOfSchemas", () => { describe("items narrowing", () => { it("merges empty items with typed items (Case 1)", () => { - const result = mergeAllOfSchemas({} as any, [ + const result = mergeAllOfSchemas({} as Schema, [ { type: "object", properties: { results: { type: "array", items: {} } } - } as any, + } as Schema, { properties: { results: { items: { $ref: "#/components/schemas/RuleType" } } } - } as any + } as Schema ]); - const results = (result.properties as any).results; + const results = result.properties?.["results"] as Schema; expect(results.type).toBe("array"); - expect(results.items).toEqual({ $ref: "#/components/schemas/RuleType" }); + expect((results as OpenAPIV3_1.ArraySchemaObject).items).toEqual({ + $ref: "#/components/schemas/RuleType" + }); }); }); describe("outer schema precedence", () => { it("outer description wins over child description", () => { - const result = mergeAllOfSchemas({ description: "Outer wins", allOf: [] } as any, [ - { description: "From child" } as any + const result = mergeAllOfSchemas({ description: "Outer wins", allOf: [] } as Schema, [ + { description: "From child" } as Schema ]); expect(result.description).toBe("Outer wins"); }); it("child description used when outer has none", () => { - const result = mergeAllOfSchemas({ allOf: [] } as any, [{ description: "From child" } as any]); + const result = mergeAllOfSchemas({ allOf: [] } as Schema, [{ description: "From child" } as Schema]); expect(result.description).toBe("From child"); }); it("outer type wins over child type", () => { - const result = mergeAllOfSchemas({ type: "object", allOf: [] } as any, [{ type: "string" } as any]); + const result = mergeAllOfSchemas({ type: "object", allOf: [] } as Schema, [{ type: "string" } as Schema]); expect(result.type).toBe("object"); }); }); describe("OR keys", () => { it("deprecated is true if any child says true", () => { - const result = mergeAllOfSchemas({} as any, [{ deprecated: false } as any, { deprecated: true } as any]); + const result = mergeAllOfSchemas({} as Schema, [ + { deprecated: false } as Schema, + { deprecated: true } as Schema + ]); expect(result.deprecated).toBe(true); }); it("readOnly is true if any child says true", () => { - const result = mergeAllOfSchemas({} as any, [{} as any, { readOnly: true } as any]); + const result = mergeAllOfSchemas({} as Schema, [{} as Schema, { readOnly: true } as Schema]); expect(result.readOnly).toBe(true); }); }); describe("constraint merging", () => { it("takes the larger minimum (most restrictive)", () => { - const result = mergeAllOfSchemas({} as any, [{ minimum: 5 } as any, { minimum: 10 } as any]); + const result = mergeAllOfSchemas({} as Schema, [{ minimum: 5 } as Schema, { minimum: 10 } as Schema]); expect(result.minimum).toBe(10); }); it("takes the smaller maximum (most restrictive)", () => { - const result = mergeAllOfSchemas({} as any, [{ maxLength: 100 } as any, { maxLength: 50 } as any]); + const result = mergeAllOfSchemas({} as Schema, [{ maxLength: 100 } as Schema, { maxLength: 50 } as Schema]); expect(result.maxLength).toBe(50); }); }); describe("nested allOf flattening", () => { it("extracts nested allOf items and merges sibling keys", () => { - const result = mergeAllOfSchemas({} as any, [ + const result = mergeAllOfSchemas({} as Schema, [ { required: ["id"], properties: { id: { type: "string" } }, allOf: [{ required: ["name"], properties: { name: { type: "string" } } }] - } as any + } as Schema ]); expect(result.required).toEqual(expect.arrayContaining(["id", "name"])); - expect((result.properties as any).id).toBeDefined(); - expect((result.properties as any).name).toBeDefined(); + expect(result.properties?.["id"]).toBeDefined(); + expect(result.properties?.["name"]).toBeDefined(); }); it("does not include allOf key on the result", () => { - const result = mergeAllOfSchemas({} as any, [ + const result = mergeAllOfSchemas({} as Schema, [ { properties: { a: { type: "string" } }, allOf: [{ properties: { b: { type: "number" } } }] - } as any + } as Schema ]); expect(result.allOf).toBeUndefined(); }); @@ -136,12 +146,15 @@ describe("mergeAllOfSchemas", () => { describe("last-writer-wins fallback", () => { it("last schema's pattern wins", () => { - const result = mergeAllOfSchemas({} as any, [{ pattern: "^a" } as any, { pattern: "^b" } as any]); + const result = mergeAllOfSchemas({} as Schema, [{ pattern: "^a" } as Schema, { pattern: "^b" } as Schema]); expect(result.pattern).toBe("^b"); }); it("last schema's enum wins", () => { - const result = mergeAllOfSchemas({} as any, [{ enum: ["a", "b"] } as any, { enum: ["c", "d"] } as any]); + const result = mergeAllOfSchemas({} as Schema, [ + { enum: ["a", "b"] } as Schema, + { enum: ["c", "d"] } as Schema + ]); expect(result.enum).toEqual(["c", "d"]); }); }); From b67826e7bfac670acbe7fd6ae87b9f89d1352df4 Mon Sep 17 00:00:00 2001 From: jsklan Date: Wed, 15 Apr 2026 10:42:20 -0400 Subject: [PATCH 28/29] fix(cli): harden allOf merge pipeline against $ref leaks, nested flattening, and metadata loss Filter unresolved $ref objects from nested allOf flattening to prevent spurious top-level $ref keys corrupting merged schemas. Make flattenNestedAllOf recursive so deeply-nested allOf structures are fully expanded. Add oneOf/anyOf to SKIP_FROM_CHILDREN so composition keywords from resolved child schemas don't leak into the merged result and erroneously trigger union conversion. Propagate outer schema metadata (description, deprecated) through the single-element allOf path for non-object types. --- .../src/converters/schema/SchemaConverter.ts | 15 +++++ .../schema/__test__/mergeAllOfSchemas.test.ts | 59 +++++++++++++++++++ .../converters/schema/mergeAllOfSchemas.ts | 9 ++- .../unreleased/fix-allof-composition.yml | 7 ++- 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts index b688cdea689f..3dae42f80521 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/SchemaConverter.ts @@ -210,6 +210,21 @@ export class SchemaConverter extends AbstractConverter { ]); expect(result.allOf).toBeUndefined(); }); + + it("filters out $ref objects from nested allOf arrays", () => { + const result = mergeAllOfSchemas({} as Schema, [ + { + required: ["id"], + properties: { id: { type: "string" } }, + allOf: [ + { $ref: "#/components/schemas/Base" } as unknown as Schema, + { required: ["name"], properties: { name: { type: "string" } } } + ] + } as Schema + ]); + // $ref should be filtered out, inline schema should be merged + expect(result.properties?.["id"]).toBeDefined(); + expect(result.properties?.["name"]).toBeDefined(); + expect(result.required).toEqual(expect.arrayContaining(["id", "name"])); + // No $ref key should leak onto the result + expect("$ref" in result).toBe(false); + }); + + it("recursively flattens doubly-nested allOf", () => { + const result = mergeAllOfSchemas({} as Schema, [ + { + allOf: [ + { + properties: { a: { type: "string" } }, + allOf: [{ properties: { b: { type: "number" } } }] + } + ] + } as Schema + ]); + expect(result.properties?.["a"]).toBeDefined(); + expect(result.properties?.["b"]).toBeDefined(); + expect(result.allOf).toBeUndefined(); + }); + }); + + describe("composition keyword stripping", () => { + it("strips oneOf from child schemas so it does not leak into merged result", () => { + const result = mergeAllOfSchemas({} as Schema, [ + { + properties: { id: { type: "string" } }, + oneOf: [{ type: "string" }, { type: "integer" }] + } as Schema + ]); + expect(result.properties?.["id"]).toBeDefined(); + expect(result.oneOf).toBeUndefined(); + }); + + it("strips anyOf from child schemas", () => { + const result = mergeAllOfSchemas({} as Schema, [ + { + properties: { name: { type: "string" } }, + anyOf: [{ type: "string" }, { type: "null" }] + } as Schema + ]); + expect(result.properties?.["name"]).toBeDefined(); + expect(result.anyOf).toBeUndefined(); + }); }); describe("last-writer-wins fallback", () => { diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts index 8b77e7bf074a..50ca28fd40af 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/schema/mergeAllOfSchemas.ts @@ -24,7 +24,7 @@ const MAX_OF_MINS_KEYS = ["minimum", "exclusiveMinimum", "minLength", "minItems" const MIN_OF_MAXS_KEYS = ["maximum", "exclusiveMaximum", "maxLength", "maxItems", "maxProperties"]; -const SKIP_FROM_CHILDREN = ["allOf"]; +const SKIP_FROM_CHILDREN = ["allOf", "oneOf", "anyOf"]; export function mergeAllOfSchemas( outerSchema: OpenAPIV3_1.SchemaObject, @@ -47,7 +47,12 @@ function flattenNestedAllOf(elements: OpenAPIV3_1.SchemaObject[]): OpenAPIV3_1.S for (const element of elements) { if (Array.isArray(element.allOf) && element.allOf.length > 0) { const { allOf, ...sibling } = element; - flat.push(...(allOf as OpenAPIV3_1.SchemaObject[])); + // Filter out ReferenceObjects — only include resolved SchemaObjects. + // Unresolved $ref entries would corrupt the merged result with a + // spurious top-level $ref key. + const schemaChildren = allOf.filter((child): child is OpenAPIV3_1.SchemaObject => !("$ref" in child)); + // Recursively flatten in case extracted children also contain allOf + flat.push(...flattenNestedAllOf(schemaChildren)); if (Object.keys(sibling).length > 0) { flat.push(sibling as OpenAPIV3_1.SchemaObject); } diff --git a/packages/cli/cli/changes/unreleased/fix-allof-composition.yml b/packages/cli/cli/changes/unreleased/fix-allof-composition.yml index 75671e9e094c..fac18d6d97d5 100644 --- a/packages/cli/cli/changes/unreleased/fix-allof-composition.yml +++ b/packages/cli/cli/changes/unreleased/fix-allof-composition.yml @@ -6,6 +6,9 @@ deduplicate merged allOf refs for diamond-shaped inheritance, guard variants-flattening and composition-keyword shortcuts against $ref-resolved schemas, preserve inline status through allOf merge path, handle nullable - enum patterns in allOf shortcuts, and detect cycles across recursive - single-element allOf chains. + enum patterns in allOf shortcuts, detect cycles across recursive + single-element allOf chains, filter unresolved $ref objects from nested allOf + flattening, recursively flatten deeply-nested allOf arrays, strip oneOf/anyOf + from child schemas during allOf merging, and propagate outer schema metadata + through single-element allOf paths. type: fix From fc0680b56b56a3988c900588c694a3170e1692d5 Mon Sep 17 00:00:00 2001 From: jsklan Date: Wed, 15 Apr 2026 10:45:17 -0400 Subject: [PATCH 29/29] Revert seed output changes to reduce diff --- seed/csharp-model/allof-inline/.editorconfig | 35 - .../allof-inline/.fern/metadata.json | 7 - .../allof-inline/.github/workflows/ci.yml | 52 - seed/csharp-model/allof-inline/.gitignore | 484 -- seed/csharp-model/allof-inline/SeedApi.slnx | 4 - seed/csharp-model/allof-inline/snippet.json | 0 .../Core/Json/AdditionalPropertiesTests.cs | 365 -- .../Core/Json/DateOnlyJsonTests.cs | 100 - .../Core/Json/DateTimeJsonTests.cs | 134 - .../Core/Json/JsonAccessAttributeTests.cs | 160 - .../Core/Json/OneOfSerializerTests.cs | 314 -- .../SeedApi.Test/SeedApi.Test.Custom.props | 6 - .../src/SeedApi.Test/SeedApi.Test.csproj | 36 - .../Utils/AdditionalPropertiesComparer.cs | 219 - .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 - .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 - .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 - .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 - .../SeedApi.Test/Utils/OptionalComparer.cs | 104 - .../Utils/ReadOnlyMemoryComparer.cs | 87 - .../allof-inline/src/SeedApi/AuditInfo.cs | 56 - .../allof-inline/src/SeedApi/BaseOrg.cs | 31 - .../src/SeedApi/BaseOrgMetadata.cs | 37 - .../src/SeedApi/CombinedEntity.cs | 46 - .../src/SeedApi/CombinedEntityStatus.cs | 115 - .../SeedApi/Core/CollectionItemSerializer.cs | 91 - .../src/SeedApi/Core/Constants.cs | 7 - .../src/SeedApi/Core/DateOnlyConverter.cs | 747 --- .../src/SeedApi/Core/DateTimeSerializer.cs | 40 - .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 - .../src/SeedApi/Core/JsonConfiguration.cs | 275 - .../src/SeedApi/Core/NullableAttribute.cs | 18 - .../src/SeedApi/Core/OneOfSerializer.cs | 145 - .../allof-inline/src/SeedApi/Core/Optional.cs | 474 -- .../src/SeedApi/Core/OptionalAttribute.cs | 17 - .../Core/Public/AdditionalProperties.cs | 353 -- .../src/SeedApi/Core/Public/FileParameter.cs | 63 - .../src/SeedApi/Core/Public/Version.cs | 7 - .../src/SeedApi/Core/StringEnum.cs | 6 - .../src/SeedApi/Core/StringEnumExtensions.cs | 6 - .../allof-inline/src/SeedApi/Describable.cs | 37 - .../allof-inline/src/SeedApi/DetailedOrg.cs | 28 - .../src/SeedApi/DetailedOrgMetadata.cs | 37 - .../allof-inline/src/SeedApi/Identifiable.cs | 37 - .../allof-inline/src/SeedApi/Organization.cs | 34 - .../src/SeedApi/OrganizationMetadata.cs | 37 - .../src/SeedApi/PaginatedResult.cs | 34 - .../allof-inline/src/SeedApi/PagingCursors.cs | 37 - .../src/SeedApi/RuleExecutionContext.cs | 119 - .../allof-inline/src/SeedApi/RuleResponse.cs | 65 - .../src/SeedApi/RuleResponseStatus.cs | 119 - .../allof-inline/src/SeedApi/RuleType.cs | 34 - .../src/SeedApi/RuleTypeSearchResponse.cs | 34 - .../src/SeedApi/SeedApi.Custom.props | 20 - .../allof-inline/src/SeedApi/SeedApi.csproj | 60 - .../allof-inline/src/SeedApi/User.cs | 31 - .../src/SeedApi/UserSearchResponse.cs | 34 - seed/csharp-model/allof/.editorconfig | 35 - seed/csharp-model/allof/.fern/metadata.json | 7 - .../allof/.github/workflows/ci.yml | 52 - seed/csharp-model/allof/.gitignore | 484 -- seed/csharp-model/allof/SeedApi.slnx | 4 - seed/csharp-model/allof/snippet.json | 0 .../Core/Json/AdditionalPropertiesTests.cs | 365 -- .../Core/Json/DateOnlyJsonTests.cs | 100 - .../Core/Json/DateTimeJsonTests.cs | 134 - .../Core/Json/JsonAccessAttributeTests.cs | 160 - .../Core/Json/OneOfSerializerTests.cs | 314 -- .../SeedApi.Test/SeedApi.Test.Custom.props | 6 - .../src/SeedApi.Test/SeedApi.Test.csproj | 36 - .../Utils/AdditionalPropertiesComparer.cs | 219 - .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 - .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 - .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 - .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 - .../SeedApi.Test/Utils/OptionalComparer.cs | 104 - .../Utils/ReadOnlyMemoryComparer.cs | 87 - .../allof/src/SeedApi/AuditInfo.cs | 56 - .../csharp-model/allof/src/SeedApi/BaseOrg.cs | 31 - .../allof/src/SeedApi/BaseOrgMetadata.cs | 37 - .../allof/src/SeedApi/CombinedEntity.cs | 46 - .../allof/src/SeedApi/CombinedEntityStatus.cs | 115 - .../SeedApi/Core/CollectionItemSerializer.cs | 91 - .../allof/src/SeedApi/Core/Constants.cs | 7 - .../src/SeedApi/Core/DateOnlyConverter.cs | 747 --- .../src/SeedApi/Core/DateTimeSerializer.cs | 40 - .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 - .../src/SeedApi/Core/JsonConfiguration.cs | 275 - .../src/SeedApi/Core/NullableAttribute.cs | 18 - .../allof/src/SeedApi/Core/OneOfSerializer.cs | 145 - .../allof/src/SeedApi/Core/Optional.cs | 474 -- .../src/SeedApi/Core/OptionalAttribute.cs | 17 - .../Core/Public/AdditionalProperties.cs | 353 -- .../src/SeedApi/Core/Public/FileParameter.cs | 63 - .../allof/src/SeedApi/Core/Public/Version.cs | 7 - .../allof/src/SeedApi/Core/StringEnum.cs | 6 - .../src/SeedApi/Core/StringEnumExtensions.cs | 6 - .../allof/src/SeedApi/Describable.cs | 37 - .../allof/src/SeedApi/DetailedOrg.cs | 28 - .../allof/src/SeedApi/DetailedOrgMetadata.cs | 37 - .../allof/src/SeedApi/Identifiable.cs | 37 - .../allof/src/SeedApi/Organization.cs | 34 - .../allof/src/SeedApi/PaginatedResult.cs | 34 - .../allof/src/SeedApi/PagingCursors.cs | 37 - .../allof/src/SeedApi/RuleExecutionContext.cs | 119 - .../allof/src/SeedApi/RuleResponse.cs | 65 - .../allof/src/SeedApi/RuleResponseStatus.cs | 119 - .../allof/src/SeedApi/RuleType.cs | 34 - .../src/SeedApi/RuleTypeSearchResponse.cs | 34 - .../allof/src/SeedApi/SeedApi.Custom.props | 20 - .../allof/src/SeedApi/SeedApi.csproj | 60 - seed/csharp-model/allof/src/SeedApi/User.cs | 31 - .../allof/src/SeedApi/UserSearchResponse.cs | 34 - seed/csharp-sdk/allof-inline/.editorconfig | 35 - .../allof-inline/.fern/metadata.json | 8 - .../allof-inline/.github/workflows/ci.yml | 52 - seed/csharp-sdk/allof-inline/.gitignore | 484 -- seed/csharp-sdk/allof-inline/README.md | 216 - seed/csharp-sdk/allof-inline/SeedApi.slnx | 4 - seed/csharp-sdk/allof-inline/reference.md | 158 - seed/csharp-sdk/allof-inline/snippet.json | 65 - .../src/SeedApi.DynamicSnippets/Example0.cs | 19 - .../src/SeedApi.DynamicSnippets/Example1.cs | 21 - .../src/SeedApi.DynamicSnippets/Example2.cs | 22 - .../src/SeedApi.DynamicSnippets/Example3.cs | 22 - .../src/SeedApi.DynamicSnippets/Example4.cs | 17 - .../src/SeedApi.DynamicSnippets/Example5.cs | 17 - .../src/SeedApi.DynamicSnippets/Example6.cs | 17 - .../src/SeedApi.DynamicSnippets/Example7.cs | 17 - .../src/SeedApi.DynamicSnippets/Example8.cs | 17 - .../src/SeedApi.DynamicSnippets/Example9.cs | 17 - .../SeedApi.DynamicSnippets.csproj | 13 - .../SeedApi.Test/Core/HeadersBuilderTests.cs | 326 -- .../Core/Json/AdditionalPropertiesTests.cs | 365 -- .../Core/Json/DateOnlyJsonTests.cs | 100 - .../Core/Json/DateTimeJsonTests.cs | 134 - .../Core/Json/JsonAccessAttributeTests.cs | 160 - .../Core/QueryStringBuilderTests.cs | 658 --- .../Core/QueryStringConverterTests.cs | 158 - .../Core/RawClientTests/MultipartFormTests.cs | 1121 ---- .../RawClientTests/QueryParameterTests.cs | 108 - .../Core/RawClientTests/RetriesTests.cs | 406 -- .../SeedApi.Test/Core/WithRawResponseTests.cs | 269 - .../SeedApi.Test/SeedApi.Test.Custom.props | 6 - .../src/SeedApi.Test/SeedApi.Test.csproj | 36 - .../src/SeedApi.Test/TestClient.cs | 6 - .../Unit/MockServer/BaseMockServerTest.cs | 37 - .../Unit/MockServer/CreateRuleTest.cs | 92 - .../Unit/MockServer/GetEntityTest.cs | 59 - .../Unit/MockServer/GetOrganizationTest.cs | 63 - .../Unit/MockServer/ListUsersTest.cs | 75 - .../Unit/MockServer/SearchRuleTypesTest.cs | 87 - .../Utils/AdditionalPropertiesComparer.cs | 219 - .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 - .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 - .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 - .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 - .../SeedApi.Test/Utils/OptionalComparer.cs | 104 - .../Utils/ReadOnlyMemoryComparer.cs | 87 - .../src/SeedApi/Core/ApiResponse.cs | 13 - .../src/SeedApi/Core/BaseRequest.cs | 67 - .../SeedApi/Core/CollectionItemSerializer.cs | 91 - .../src/SeedApi/Core/Constants.cs | 7 - .../src/SeedApi/Core/DateOnlyConverter.cs | 747 --- .../src/SeedApi/Core/DateTimeSerializer.cs | 40 - .../src/SeedApi/Core/EmptyRequest.cs | 11 - .../src/SeedApi/Core/EncodingCache.cs | 11 - .../src/SeedApi/Core/Extensions.cs | 55 - .../src/SeedApi/Core/FormUrlEncoder.cs | 33 - .../src/SeedApi/Core/HeaderValue.cs | 52 - .../allof-inline/src/SeedApi/Core/Headers.cs | 28 - .../src/SeedApi/Core/HeadersBuilder.cs | 197 - .../src/SeedApi/Core/HttpContentExtensions.cs | 20 - .../src/SeedApi/Core/HttpMethodExtensions.cs | 8 - .../src/SeedApi/Core/IIsRetryableContent.cs | 6 - .../src/SeedApi/Core/IRequestOptions.cs | 83 - .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 - .../src/SeedApi/Core/JsonConfiguration.cs | 275 - .../src/SeedApi/Core/JsonRequest.cs | 36 - .../src/SeedApi/Core/MultipartFormRequest.cs | 294 -- .../src/SeedApi/Core/NullableAttribute.cs | 18 - .../src/SeedApi/Core/OneOfSerializer.cs | 145 - .../allof-inline/src/SeedApi/Core/Optional.cs | 474 -- .../src/SeedApi/Core/OptionalAttribute.cs | 17 - .../Core/Public/AdditionalProperties.cs | 353 -- .../src/SeedApi/Core/Public/ClientOptions.cs | 84 - .../src/SeedApi/Core/Public/FileParameter.cs | 63 - .../src/SeedApi/Core/Public/RawResponse.cs | 24 - .../src/SeedApi/Core/Public/RequestOptions.cs | 86 - .../Core/Public/SeedApiApiException.cs | 22 - .../SeedApi/Core/Public/SeedApiEnvironment.cs | 7 - .../SeedApi/Core/Public/SeedApiException.cs | 7 - .../src/SeedApi/Core/Public/Version.cs | 7 - .../SeedApi/Core/Public/WithRawResponse.cs | 18 - .../Core/Public/WithRawResponseTask.cs | 144 - .../src/SeedApi/Core/QueryStringBuilder.cs | 654 --- .../src/SeedApi/Core/QueryStringConverter.cs | 259 - .../src/SeedApi/Core/RawClient.cs | 344 -- .../src/SeedApi/Core/RawResponse.cs | 24 - .../src/SeedApi/Core/ResponseHeaders.cs | 108 - .../src/SeedApi/Core/StreamRequest.cs | 29 - .../src/SeedApi/Core/StringEnum.cs | 6 - .../src/SeedApi/Core/StringEnumExtensions.cs | 6 - .../src/SeedApi/Core/ValueConvert.cs | 115 - .../src/SeedApi/ISeedApiClient.cs | 31 - .../src/SeedApi/Requests/RuleCreateRequest.cs | 20 - .../Requests/SearchRuleTypesRequest.cs | 17 - .../src/SeedApi/SeedApi.Custom.props | 20 - .../allof-inline/src/SeedApi/SeedApi.csproj | 60 - .../allof-inline/src/SeedApi/SeedApiClient.cs | 429 -- .../src/SeedApi/Types/AuditInfo.cs | 56 - .../allof-inline/src/SeedApi/Types/BaseOrg.cs | 31 - .../src/SeedApi/Types/BaseOrgMetadata.cs | 37 - .../src/SeedApi/Types/CombinedEntity.cs | 46 - .../src/SeedApi/Types/CombinedEntityStatus.cs | 115 - .../src/SeedApi/Types/Describable.cs | 37 - .../src/SeedApi/Types/DetailedOrg.cs | 28 - .../src/SeedApi/Types/DetailedOrgMetadata.cs | 37 - .../src/SeedApi/Types/Identifiable.cs | 37 - .../src/SeedApi/Types/Organization.cs | 34 - .../src/SeedApi/Types/OrganizationMetadata.cs | 37 - .../src/SeedApi/Types/PaginatedResult.cs | 34 - .../src/SeedApi/Types/PagingCursors.cs | 37 - .../src/SeedApi/Types/RuleExecutionContext.cs | 119 - .../src/SeedApi/Types/RuleResponse.cs | 65 - .../src/SeedApi/Types/RuleResponseStatus.cs | 119 - .../src/SeedApi/Types/RuleType.cs | 34 - .../SeedApi/Types/RuleTypeSearchResponse.cs | 34 - .../allof-inline/src/SeedApi/Types/User.cs | 31 - .../src/SeedApi/Types/UserSearchResponse.cs | 34 - seed/csharp-sdk/allof/.editorconfig | 35 - seed/csharp-sdk/allof/.fern/metadata.json | 8 - .../csharp-sdk/allof/.github/workflows/ci.yml | 52 - seed/csharp-sdk/allof/.gitignore | 484 -- seed/csharp-sdk/allof/README.md | 216 - seed/csharp-sdk/allof/SeedApi.slnx | 4 - seed/csharp-sdk/allof/reference.md | 158 - seed/csharp-sdk/allof/snippet.json | 65 - .../src/SeedApi.DynamicSnippets/Example0.cs | 19 - .../src/SeedApi.DynamicSnippets/Example1.cs | 21 - .../src/SeedApi.DynamicSnippets/Example2.cs | 22 - .../src/SeedApi.DynamicSnippets/Example3.cs | 22 - .../src/SeedApi.DynamicSnippets/Example4.cs | 17 - .../src/SeedApi.DynamicSnippets/Example5.cs | 17 - .../src/SeedApi.DynamicSnippets/Example6.cs | 17 - .../src/SeedApi.DynamicSnippets/Example7.cs | 17 - .../src/SeedApi.DynamicSnippets/Example8.cs | 17 - .../src/SeedApi.DynamicSnippets/Example9.cs | 17 - .../SeedApi.DynamicSnippets.csproj | 13 - .../SeedApi.Test/Core/HeadersBuilderTests.cs | 326 -- .../Core/Json/AdditionalPropertiesTests.cs | 365 -- .../Core/Json/DateOnlyJsonTests.cs | 100 - .../Core/Json/DateTimeJsonTests.cs | 134 - .../Core/Json/JsonAccessAttributeTests.cs | 160 - .../Core/QueryStringBuilderTests.cs | 658 --- .../Core/QueryStringConverterTests.cs | 158 - .../Core/RawClientTests/MultipartFormTests.cs | 1120 ---- .../RawClientTests/QueryParameterTests.cs | 108 - .../Core/RawClientTests/RetriesTests.cs | 406 -- .../SeedApi.Test/Core/WithRawResponseTests.cs | 269 - .../SeedApi.Test/SeedApi.Test.Custom.props | 6 - .../src/SeedApi.Test/SeedApi.Test.csproj | 33 - .../allof/src/SeedApi.Test/TestClient.cs | 6 - .../Unit/MockServer/BaseMockServerTest.cs | 37 - .../Unit/MockServer/CreateRuleTest.cs | 92 - .../Unit/MockServer/GetEntityTest.cs | 59 - .../Unit/MockServer/GetOrganizationTest.cs | 63 - .../Unit/MockServer/ListUsersTest.cs | 75 - .../Unit/MockServer/SearchRuleTypesTest.cs | 87 - .../Utils/AdditionalPropertiesComparer.cs | 219 - .../src/SeedApi.Test/Utils/JsonAssert.cs | 29 - .../SeedApi.Test/Utils/JsonElementComparer.cs | 236 - .../src/SeedApi.Test/Utils/NUnitExtensions.cs | 32 - .../src/SeedApi.Test/Utils/OneOfComparer.cs | 86 - .../SeedApi.Test/Utils/OptionalComparer.cs | 104 - .../Utils/ReadOnlyMemoryComparer.cs | 87 - .../allof/src/SeedApi/Core/ApiResponse.cs | 13 - .../allof/src/SeedApi/Core/BaseRequest.cs | 67 - .../SeedApi/Core/CollectionItemSerializer.cs | 91 - .../allof/src/SeedApi/Core/Constants.cs | 7 - .../src/SeedApi/Core/DateOnlyConverter.cs | 747 --- .../src/SeedApi/Core/DateTimeSerializer.cs | 40 - .../allof/src/SeedApi/Core/EmptyRequest.cs | 11 - .../allof/src/SeedApi/Core/EncodingCache.cs | 11 - .../allof/src/SeedApi/Core/Extensions.cs | 55 - .../allof/src/SeedApi/Core/FormUrlEncoder.cs | 33 - .../allof/src/SeedApi/Core/HeaderValue.cs | 52 - .../allof/src/SeedApi/Core/Headers.cs | 28 - .../allof/src/SeedApi/Core/HeadersBuilder.cs | 197 - .../src/SeedApi/Core/HttpContentExtensions.cs | 20 - .../src/SeedApi/Core/HttpMethodExtensions.cs | 8 - .../src/SeedApi/Core/IIsRetryableContent.cs | 6 - .../allof/src/SeedApi/Core/IRequestOptions.cs | 83 - .../src/SeedApi/Core/JsonAccessAttribute.cs | 15 - .../src/SeedApi/Core/JsonConfiguration.cs | 275 - .../allof/src/SeedApi/Core/JsonRequest.cs | 36 - .../src/SeedApi/Core/MultipartFormRequest.cs | 294 -- .../src/SeedApi/Core/NullableAttribute.cs | 18 - .../allof/src/SeedApi/Core/OneOfSerializer.cs | 145 - .../allof/src/SeedApi/Core/Optional.cs | 474 -- .../src/SeedApi/Core/OptionalAttribute.cs | 17 - .../Core/Public/AdditionalProperties.cs | 353 -- .../src/SeedApi/Core/Public/ClientOptions.cs | 84 - .../src/SeedApi/Core/Public/FileParameter.cs | 63 - .../src/SeedApi/Core/Public/RawResponse.cs | 24 - .../src/SeedApi/Core/Public/RequestOptions.cs | 86 - .../Core/Public/SeedApiApiException.cs | 22 - .../SeedApi/Core/Public/SeedApiEnvironment.cs | 7 - .../SeedApi/Core/Public/SeedApiException.cs | 7 - .../allof/src/SeedApi/Core/Public/Version.cs | 7 - .../SeedApi/Core/Public/WithRawResponse.cs | 18 - .../Core/Public/WithRawResponseTask.cs | 144 - .../src/SeedApi/Core/QueryStringBuilder.cs | 654 --- .../src/SeedApi/Core/QueryStringConverter.cs | 259 - .../allof/src/SeedApi/Core/RawClient.cs | 343 -- .../allof/src/SeedApi/Core/RawResponse.cs | 24 - .../allof/src/SeedApi/Core/ResponseHeaders.cs | 108 - .../allof/src/SeedApi/Core/StreamRequest.cs | 29 - .../allof/src/SeedApi/Core/StringEnum.cs | 6 - .../src/SeedApi/Core/StringEnumExtensions.cs | 6 - .../allof/src/SeedApi/Core/ValueConvert.cs | 115 - .../allof/src/SeedApi/ISeedApiClient.cs | 31 - .../src/SeedApi/Requests/RuleCreateRequest.cs | 20 - .../Requests/SearchRuleTypesRequest.cs | 17 - .../allof/src/SeedApi/SeedApi.Custom.props | 20 - .../allof/src/SeedApi/SeedApi.csproj | 56 - .../allof/src/SeedApi/SeedApiClient.cs | 429 -- .../allof/src/SeedApi/Types/AuditInfo.cs | 56 - .../allof/src/SeedApi/Types/BaseOrg.cs | 31 - .../src/SeedApi/Types/BaseOrgMetadata.cs | 37 - .../allof/src/SeedApi/Types/CombinedEntity.cs | 46 - .../src/SeedApi/Types/CombinedEntityStatus.cs | 115 - .../allof/src/SeedApi/Types/Describable.cs | 37 - .../allof/src/SeedApi/Types/DetailedOrg.cs | 28 - .../src/SeedApi/Types/DetailedOrgMetadata.cs | 37 - .../allof/src/SeedApi/Types/Identifiable.cs | 37 - .../allof/src/SeedApi/Types/Organization.cs | 34 - .../src/SeedApi/Types/PaginatedResult.cs | 34 - .../allof/src/SeedApi/Types/PagingCursors.cs | 37 - .../src/SeedApi/Types/RuleExecutionContext.cs | 119 - .../allof/src/SeedApi/Types/RuleResponse.cs | 65 - .../src/SeedApi/Types/RuleResponseStatus.cs | 119 - .../allof/src/SeedApi/Types/RuleType.cs | 34 - .../SeedApi/Types/RuleTypeSearchResponse.cs | 34 - .../allof/src/SeedApi/Types/User.cs | 31 - .../src/SeedApi/Types/UserSearchResponse.cs | 34 - .../go-model/allof-inline/.fern/metadata.json | 7 - seed/go-model/allof-inline/doc.go | 1 - seed/go-model/allof-inline/go.mod | 14 - seed/go-model/allof-inline/go.sum | 10 - .../allof-inline/internal/extra_properties.go | 141 - .../internal/extra_properties_test.go | 228 - .../allof-inline/internal/stringer.go | 13 - seed/go-model/allof-inline/internal/time.go | 165 - seed/go-model/allof-inline/snippet.json | 0 seed/go-model/allof-inline/types.go | 1246 ----- seed/go-model/allof/.fern/metadata.json | 7 - seed/go-model/allof/doc.go | 1 - seed/go-model/allof/go.mod | 14 - seed/go-model/allof/go.sum | 10 - .../allof/internal/extra_properties.go | 141 - .../allof/internal/extra_properties_test.go | 228 - seed/go-model/allof/internal/stringer.go | 13 - seed/go-model/allof/internal/time.go | 165 - seed/go-model/allof/snippet.json | 0 seed/go-model/allof/types.go | 1185 ----- seed/go-sdk/allof-inline/.fern/metadata.json | 10 - .../allof-inline/.github/workflows/ci.yml | 62 - seed/go-sdk/allof-inline/README.md | 195 - seed/go-sdk/allof-inline/client/client.go | 109 - .../go-sdk/allof-inline/client/client_test.go | 45 - seed/go-sdk/allof-inline/client/raw_client.go | 238 - seed/go-sdk/allof-inline/core/api_error.go | 47 - seed/go-sdk/allof-inline/core/http.go | 15 - .../allof-inline/core/request_option.go | 119 - .../dynamic-snippets/example0/snippet.go | 22 - .../dynamic-snippets/example1/snippet.go | 26 - .../dynamic-snippets/example2/snippet.go | 25 - .../dynamic-snippets/example3/snippet.go | 25 - .../dynamic-snippets/example4/snippet.go | 19 - .../dynamic-snippets/example5/snippet.go | 19 - .../dynamic-snippets/example6/snippet.go | 19 - .../dynamic-snippets/example7/snippet.go | 19 - .../dynamic-snippets/example8/snippet.go | 19 - .../dynamic-snippets/example9/snippet.go | 19 - seed/go-sdk/allof-inline/environments.go | 13 - seed/go-sdk/allof-inline/error_codes.go | 9 - seed/go-sdk/allof-inline/file_param.go | 41 - seed/go-sdk/allof-inline/go.mod | 16 - seed/go-sdk/allof-inline/go.sum | 12 - seed/go-sdk/allof-inline/internal/caller.go | 311 -- .../allof-inline/internal/caller_test.go | 705 --- .../allof-inline/internal/error_decoder.go | 64 - .../internal/error_decoder_test.go | 59 - .../allof-inline/internal/explicit_fields.go | 116 - .../internal/explicit_fields_test.go | 645 --- .../allof-inline/internal/extra_properties.go | 141 - .../internal/extra_properties_test.go | 228 - seed/go-sdk/allof-inline/internal/http.go | 71 - seed/go-sdk/allof-inline/internal/query.go | 358 -- .../allof-inline/internal/query_test.go | 395 -- seed/go-sdk/allof-inline/internal/retrier.go | 239 - .../allof-inline/internal/retrier_test.go | 352 -- seed/go-sdk/allof-inline/internal/stringer.go | 13 - seed/go-sdk/allof-inline/internal/time.go | 165 - .../allof-inline/option/request_option.go | 73 - seed/go-sdk/allof-inline/pointer.go | 137 - seed/go-sdk/allof-inline/pointer_test.go | 211 - seed/go-sdk/allof-inline/reference.md | 186 - seed/go-sdk/allof-inline/snippet.json | 59 - seed/go-sdk/allof-inline/types.go | 2091 -------- seed/go-sdk/allof-inline/types_test.go | 4688 ----------------- seed/go-sdk/allof/.fern/metadata.json | 10 - seed/go-sdk/allof/.github/workflows/ci.yml | 62 - seed/go-sdk/allof/README.md | 195 - seed/go-sdk/allof/client/client.go | 109 - seed/go-sdk/allof/client/client_test.go | 45 - seed/go-sdk/allof/client/raw_client.go | 238 - seed/go-sdk/allof/core/api_error.go | 47 - seed/go-sdk/allof/core/http.go | 15 - seed/go-sdk/allof/core/request_option.go | 119 - .../dynamic-snippets/example0/snippet.go | 22 - .../dynamic-snippets/example1/snippet.go | 26 - .../dynamic-snippets/example2/snippet.go | 25 - .../dynamic-snippets/example3/snippet.go | 25 - .../dynamic-snippets/example4/snippet.go | 19 - .../dynamic-snippets/example5/snippet.go | 19 - .../dynamic-snippets/example6/snippet.go | 19 - .../dynamic-snippets/example7/snippet.go | 19 - .../dynamic-snippets/example8/snippet.go | 19 - .../dynamic-snippets/example9/snippet.go | 19 - seed/go-sdk/allof/environments.go | 13 - seed/go-sdk/allof/error_codes.go | 9 - seed/go-sdk/allof/file_param.go | 41 - seed/go-sdk/allof/go.mod | 16 - seed/go-sdk/allof/go.sum | 12 - seed/go-sdk/allof/internal/caller.go | 311 -- seed/go-sdk/allof/internal/caller_test.go | 705 --- seed/go-sdk/allof/internal/error_decoder.go | 64 - .../allof/internal/error_decoder_test.go | 59 - seed/go-sdk/allof/internal/explicit_fields.go | 116 - .../allof/internal/explicit_fields_test.go | 645 --- .../go-sdk/allof/internal/extra_properties.go | 141 - .../allof/internal/extra_properties_test.go | 228 - seed/go-sdk/allof/internal/http.go | 71 - seed/go-sdk/allof/internal/query.go | 358 -- seed/go-sdk/allof/internal/query_test.go | 395 -- seed/go-sdk/allof/internal/retrier.go | 239 - seed/go-sdk/allof/internal/retrier_test.go | 352 -- seed/go-sdk/allof/internal/stringer.go | 13 - seed/go-sdk/allof/internal/time.go | 165 - seed/go-sdk/allof/option/request_option.go | 73 - seed/go-sdk/allof/pointer.go | 137 - seed/go-sdk/allof/pointer_test.go | 211 - seed/go-sdk/allof/reference.md | 186 - seed/go-sdk/allof/snippet.json | 59 - seed/go-sdk/allof/types.go | 1989 ------- seed/go-sdk/allof/types_test.go | 4473 ---------------- .../allof-inline/.github/workflows/ci.yml | 65 - seed/java-model/allof-inline/.gitignore | 24 - seed/java-model/allof-inline/build.gradle | 98 - seed/java-model/allof-inline/settings.gradle | 2 - seed/java-model/allof-inline/snippet.json | 0 .../seed/api/core/DateTimeDeserializer.java | 56 - .../com/seed/api/core/DoubleSerializer.java | 44 - .../java/com/seed/api/core/ObjectMappers.java | 52 - .../api/core/Rfc2822DateTimeDeserializer.java | 26 - .../java/com/seed/api/model/AuditInfo.java | 192 - .../main/java/com/seed/api/model/BaseOrg.java | 127 - .../com/seed/api/model/BaseOrgMetadata.java | 151 - .../com/seed/api/model/CombinedEntity.java | 218 - .../seed/api/model/CombinedEntityStatus.java | 26 - .../java/com/seed/api/model/Describable.java | 128 - .../java/com/seed/api/model/DetailedOrg.java | 91 - .../seed/api/model/DetailedOrgMetadata.java | 151 - .../java/com/seed/api/model/Identifiable.java | 151 - .../java/com/seed/api/model/Organization.java | 149 - .../seed/api/model/OrganizationMetadata.java | 151 - .../com/seed/api/model/PaginatedResult.java | 158 - .../com/seed/api/model/PagingCursors.java | 151 - .../seed/api/model/RuleExecutionContext.java | 28 - .../java/com/seed/api/model/RuleResponse.java | 350 -- .../seed/api/model/RuleResponseStatus.java | 28 - .../java/com/seed/api/model/RuleType.java | 149 - .../api/model/RuleTypeSearchResponse.java | 141 - .../main/java/com/seed/api/model/User.java | 116 - .../seed/api/model/UserSearchResponse.java | 141 - .../java-model/allof/.github/workflows/ci.yml | 65 - seed/java-model/allof/.gitignore | 24 - seed/java-model/allof/build.gradle | 98 - seed/java-model/allof/settings.gradle | 2 - seed/java-model/allof/snippet.json | 0 .../seed/api/core/DateTimeDeserializer.java | 56 - .../com/seed/api/core/DoubleSerializer.java | 44 - .../java/com/seed/api/core/ObjectMappers.java | 52 - .../api/core/Rfc2822DateTimeDeserializer.java | 26 - .../java/com/seed/api/model/AuditInfo.java | 196 - .../main/java/com/seed/api/model/BaseOrg.java | 127 - .../com/seed/api/model/BaseOrgMetadata.java | 151 - .../com/seed/api/model/CombinedEntity.java | 218 - .../seed/api/model/CombinedEntityStatus.java | 26 - .../java/com/seed/api/model/Describable.java | 128 - .../java/com/seed/api/model/DetailedOrg.java | 91 - .../seed/api/model/DetailedOrgMetadata.java | 151 - .../java/com/seed/api/model/IAuditInfo.java | 19 - .../java/com/seed/api/model/Identifiable.java | 151 - .../java/com/seed/api/model/Organization.java | 149 - .../com/seed/api/model/PaginatedResult.java | 158 - .../com/seed/api/model/PagingCursors.java | 151 - .../seed/api/model/RuleExecutionContext.java | 28 - .../java/com/seed/api/model/RuleResponse.java | 354 -- .../seed/api/model/RuleResponseStatus.java | 28 - .../java/com/seed/api/model/RuleType.java | 149 - .../api/model/RuleTypeSearchResponse.java | 141 - .../main/java/com/seed/api/model/User.java | 116 - .../seed/api/model/UserSearchResponse.java | 141 - .../java-sdk/allof-inline/.fern/metadata.json | 7 - .../allof-inline/.github/workflows/ci.yml | 65 - seed/java-sdk/allof-inline/.gitignore | 24 - seed/java-sdk/allof-inline/README.md | 235 - seed/java-sdk/allof-inline/build.gradle | 102 - seed/java-sdk/allof-inline/reference.md | 174 - .../allof-inline/sample-app/build.gradle | 19 - .../sample-app/src/main/java/sample/App.java | 13 - seed/java-sdk/allof-inline/settings.gradle | 3 - seed/java-sdk/allof-inline/snippet.json | 135 - .../com/seed/api/AsyncRawSeedApiClient.java | 323 -- .../java/com/seed/api/AsyncSeedApiClient.java | 86 - .../seed/api/AsyncSeedApiClientBuilder.java | 194 - .../java/com/seed/api/RawSeedApiClient.java | 249 - .../main/java/com/seed/api/SeedApiClient.java | 84 - .../com/seed/api/SeedApiClientBuilder.java | 194 - .../java/com/seed/api/core/ClientOptions.java | 221 - .../java/com/seed/api/core/ConsoleLogger.java | 51 - .../seed/api/core/DateTimeDeserializer.java | 55 - .../com/seed/api/core/DoubleSerializer.java | 43 - .../java/com/seed/api/core/Environment.java | 22 - .../java/com/seed/api/core/FileStream.java | 60 - .../main/java/com/seed/api/core/ILogger.java | 38 - .../seed/api/core/InputStreamRequestBody.java | 74 - .../java/com/seed/api/core/LogConfig.java | 98 - .../main/java/com/seed/api/core/LogLevel.java | 36 - .../main/java/com/seed/api/core/Logger.java | 97 - .../com/seed/api/core/LoggingInterceptor.java | 104 - .../java/com/seed/api/core/MediaTypes.java | 13 - .../main/java/com/seed/api/core/Nullable.java | 140 - .../seed/api/core/NullableNonemptyFilter.java | 22 - .../java/com/seed/api/core/ObjectMappers.java | 46 - .../com/seed/api/core/QueryStringMapper.java | 142 - .../com/seed/api/core/RequestOptions.java | 118 - .../api/core/ResponseBodyInputStream.java | 45 - .../com/seed/api/core/ResponseBodyReader.java | 44 - .../com/seed/api/core/RetryInterceptor.java | 181 - .../api/core/Rfc2822DateTimeDeserializer.java | 25 - .../seed/api/core/SeedApiApiException.java | 73 - .../com/seed/api/core/SeedApiException.java | 17 - .../seed/api/core/SeedApiHttpResponse.java | 37 - .../main/java/com/seed/api/core/SseEvent.java | 114 - .../com/seed/api/core/SseEventParser.java | 228 - .../main/java/com/seed/api/core/Stream.java | 513 -- .../java/com/seed/api/core/Suppliers.java | 23 - .../seed/api/requests/RuleCreateRequest.java | 142 - .../api/requests/SearchRuleTypesRequest.java | 105 - .../java/com/seed/api/types/AuditInfo.java | 204 - .../main/java/com/seed/api/types/BaseOrg.java | 148 - .../com/seed/api/types/BaseOrgMetadata.java | 172 - .../com/seed/api/types/CombinedEntity.java | 243 - .../seed/api/types/CombinedEntityStatus.java | 83 - .../java/com/seed/api/types/Describable.java | 139 - .../java/com/seed/api/types/DetailedOrg.java | 105 - .../seed/api/types/DetailedOrgMetadata.java | 172 - .../java/com/seed/api/types/Identifiable.java | 172 - .../java/com/seed/api/types/Organization.java | 171 - .../seed/api/types/OrganizationMetadata.java | 172 - .../com/seed/api/types/PaginatedResult.java | 179 - .../com/seed/api/types/PagingCursors.java | 172 - .../seed/api/types/RuleExecutionContext.java | 93 - .../java/com/seed/api/types/RuleResponse.java | 390 -- .../seed/api/types/RuleResponseStatus.java | 93 - .../java/com/seed/api/types/RuleType.java | 170 - .../api/types/RuleTypeSearchResponse.java | 163 - .../main/java/com/seed/api/types/User.java | 140 - .../seed/api/types/UserSearchResponse.java | 163 - .../src/main/java/com/snippets/Example0.java | 13 - .../src/main/java/com/snippets/Example1.java | 13 - .../src/main/java/com/snippets/Example2.java | 17 - .../src/main/java/com/snippets/Example3.java | 17 - .../src/main/java/com/snippets/Example4.java | 12 - .../src/main/java/com/snippets/Example5.java | 12 - .../src/main/java/com/snippets/Example6.java | 12 - .../src/main/java/com/snippets/Example7.java | 12 - .../src/main/java/com/snippets/Example8.java | 12 - .../src/main/java/com/snippets/Example9.java | 12 - .../test/java/com/seed/api/StreamTest.java | 120 - .../test/java/com/seed/api/TestClient.java | 11 - .../seed/api/core/QueryStringMapperTest.java | 339 -- seed/java-sdk/allof/.fern/metadata.json | 7 - seed/java-sdk/allof/.github/workflows/ci.yml | 65 - seed/java-sdk/allof/.gitignore | 24 - seed/java-sdk/allof/README.md | 235 - seed/java-sdk/allof/build.gradle | 102 - seed/java-sdk/allof/reference.md | 174 - seed/java-sdk/allof/sample-app/build.gradle | 19 - .../sample-app/src/main/java/sample/App.java | 13 - seed/java-sdk/allof/settings.gradle | 3 - seed/java-sdk/allof/snippet.json | 135 - .../com/seed/api/AsyncRawSeedApiClient.java | 323 -- .../java/com/seed/api/AsyncSeedApiClient.java | 86 - .../seed/api/AsyncSeedApiClientBuilder.java | 194 - .../java/com/seed/api/RawSeedApiClient.java | 249 - .../main/java/com/seed/api/SeedApiClient.java | 84 - .../com/seed/api/SeedApiClientBuilder.java | 194 - .../java/com/seed/api/core/ClientOptions.java | 221 - .../java/com/seed/api/core/ConsoleLogger.java | 51 - .../seed/api/core/DateTimeDeserializer.java | 55 - .../com/seed/api/core/DoubleSerializer.java | 43 - .../java/com/seed/api/core/Environment.java | 22 - .../java/com/seed/api/core/FileStream.java | 60 - .../main/java/com/seed/api/core/ILogger.java | 38 - .../seed/api/core/InputStreamRequestBody.java | 74 - .../java/com/seed/api/core/LogConfig.java | 98 - .../main/java/com/seed/api/core/LogLevel.java | 36 - .../main/java/com/seed/api/core/Logger.java | 97 - .../com/seed/api/core/LoggingInterceptor.java | 104 - .../java/com/seed/api/core/MediaTypes.java | 13 - .../main/java/com/seed/api/core/Nullable.java | 140 - .../seed/api/core/NullableNonemptyFilter.java | 22 - .../java/com/seed/api/core/ObjectMappers.java | 46 - .../com/seed/api/core/QueryStringMapper.java | 142 - .../com/seed/api/core/RequestOptions.java | 118 - .../api/core/ResponseBodyInputStream.java | 45 - .../com/seed/api/core/ResponseBodyReader.java | 44 - .../com/seed/api/core/RetryInterceptor.java | 181 - .../api/core/Rfc2822DateTimeDeserializer.java | 25 - .../seed/api/core/SeedApiApiException.java | 73 - .../com/seed/api/core/SeedApiException.java | 17 - .../seed/api/core/SeedApiHttpResponse.java | 37 - .../main/java/com/seed/api/core/SseEvent.java | 114 - .../com/seed/api/core/SseEventParser.java | 228 - .../main/java/com/seed/api/core/Stream.java | 513 -- .../java/com/seed/api/core/Suppliers.java | 23 - .../seed/api/requests/RuleCreateRequest.java | 142 - .../api/requests/SearchRuleTypesRequest.java | 105 - .../java/com/seed/api/types/AuditInfo.java | 208 - .../main/java/com/seed/api/types/BaseOrg.java | 148 - .../com/seed/api/types/BaseOrgMetadata.java | 172 - .../com/seed/api/types/CombinedEntity.java | 243 - .../seed/api/types/CombinedEntityStatus.java | 83 - .../java/com/seed/api/types/Describable.java | 139 - .../java/com/seed/api/types/DetailedOrg.java | 105 - .../seed/api/types/DetailedOrgMetadata.java | 172 - .../java/com/seed/api/types/IAuditInfo.java | 17 - .../java/com/seed/api/types/Identifiable.java | 172 - .../java/com/seed/api/types/Organization.java | 171 - .../com/seed/api/types/PaginatedResult.java | 179 - .../com/seed/api/types/PagingCursors.java | 172 - .../seed/api/types/RuleExecutionContext.java | 93 - .../java/com/seed/api/types/RuleResponse.java | 394 -- .../seed/api/types/RuleResponseStatus.java | 93 - .../java/com/seed/api/types/RuleType.java | 170 - .../api/types/RuleTypeSearchResponse.java | 163 - .../main/java/com/seed/api/types/User.java | 140 - .../seed/api/types/UserSearchResponse.java | 163 - .../src/main/java/com/snippets/Example0.java | 13 - .../src/main/java/com/snippets/Example1.java | 13 - .../src/main/java/com/snippets/Example2.java | 17 - .../src/main/java/com/snippets/Example3.java | 17 - .../src/main/java/com/snippets/Example4.java | 12 - .../src/main/java/com/snippets/Example5.java | 12 - .../src/main/java/com/snippets/Example6.java | 12 - .../src/main/java/com/snippets/Example7.java | 12 - .../src/main/java/com/snippets/Example8.java | 12 - .../src/main/java/com/snippets/Example9.java | 12 - .../test/java/com/seed/api/StreamTest.java | 120 - .../test/java/com/seed/api/TestClient.java | 11 - .../seed/api/core/QueryStringMapperTest.java | 339 -- seed/openapi/allof-inline/openapi.yml | 432 -- seed/openapi/allof-inline/snippet.json | 0 seed/openapi/allof/openapi.yml | 403 -- seed/openapi/allof/snippet.json | 0 .../allof-inline/.fern/metadata.json | 7 - .../allof-inline/.github/workflows/ci.yml | 52 - seed/php-model/allof-inline/.gitignore | 5 - seed/php-model/allof-inline/composer.json | 46 - seed/php-model/allof-inline/phpstan.neon | 6 - seed/php-model/allof-inline/phpunit.xml | 7 - seed/php-model/allof-inline/snippet.json | 0 seed/php-model/allof-inline/src/AuditInfo.php | 63 - seed/php-model/allof-inline/src/BaseOrg.php | 42 - .../allof-inline/src/BaseOrgMetadata.php | 42 - .../allof-inline/src/CombinedEntity.php | 58 - .../allof-inline/src/CombinedEntityStatus.php | 9 - .../src/Core/Json/JsonDecoder.php | 161 - .../src/Core/Json/JsonDeserializer.php | 218 - .../src/Core/Json/JsonEncoder.php | 20 - .../src/Core/Json/JsonProperty.php | 13 - .../src/Core/Json/JsonSerializableType.php | 225 - .../src/Core/Json/JsonSerializer.php | 205 - .../allof-inline/src/Core/Json/Utils.php | 62 - .../allof-inline/src/Core/Types/ArrayType.php | 16 - .../allof-inline/src/Core/Types/Constant.php | 12 - .../allof-inline/src/Core/Types/Date.php | 16 - .../allof-inline/src/Core/Types/Union.php | 62 - .../allof-inline/src/Describable.php | 42 - .../allof-inline/src/DetailedOrg.php | 34 - .../allof-inline/src/DetailedOrgMetadata.php | 42 - .../allof-inline/src/Identifiable.php | 42 - .../allof-inline/src/Organization.php | 50 - .../allof-inline/src/OrganizationMetadata.php | 42 - .../allof-inline/src/PaginatedResult.php | 43 - .../allof-inline/src/PagingCursors.php | 42 - .../allof-inline/src/RuleExecutionContext.php | 10 - .../allof-inline/src/RuleResponse.php | 92 - .../allof-inline/src/RuleResponseStatus.php | 10 - seed/php-model/allof-inline/src/RuleType.php | 50 - .../src/RuleTypeSearchResponse.php | 43 - seed/php-model/allof-inline/src/User.php | 42 - .../allof-inline/src/UserSearchResponse.php | 43 - .../Core/Json/AdditionalPropertiesTest.php | 76 - .../tests/Core/Json/DateArrayTest.php | 54 - .../tests/Core/Json/EmptyArrayTest.php | 71 - .../allof-inline/tests/Core/Json/EnumTest.php | 77 - .../tests/Core/Json/ExhaustiveTest.php | 197 - .../tests/Core/Json/InvalidTest.php | 42 - .../tests/Core/Json/NestedUnionArrayTest.php | 89 - .../tests/Core/Json/NullPropertyTest.php | 53 - .../tests/Core/Json/NullableArrayTest.php | 49 - .../tests/Core/Json/ScalarTest.php | 116 - .../tests/Core/Json/TraitTest.php | 60 - .../tests/Core/Json/UnionArrayTest.php | 57 - .../tests/Core/Json/UnionPropertyTest.php | 111 - seed/php-model/allof/.fern/metadata.json | 7 - seed/php-model/allof/.github/workflows/ci.yml | 52 - seed/php-model/allof/.gitignore | 5 - seed/php-model/allof/composer.json | 46 - seed/php-model/allof/phpstan.neon | 6 - seed/php-model/allof/phpunit.xml | 7 - seed/php-model/allof/snippet.json | 0 seed/php-model/allof/src/AuditInfo.php | 63 - seed/php-model/allof/src/BaseOrg.php | 42 - seed/php-model/allof/src/BaseOrgMetadata.php | 42 - seed/php-model/allof/src/CombinedEntity.php | 58 - .../allof/src/CombinedEntityStatus.php | 9 - .../allof/src/Core/Json/JsonDecoder.php | 161 - .../allof/src/Core/Json/JsonDeserializer.php | 218 - .../allof/src/Core/Json/JsonEncoder.php | 20 - .../allof/src/Core/Json/JsonProperty.php | 13 - .../src/Core/Json/JsonSerializableType.php | 225 - .../allof/src/Core/Json/JsonSerializer.php | 205 - seed/php-model/allof/src/Core/Json/Utils.php | 62 - .../allof/src/Core/Types/ArrayType.php | 16 - .../allof/src/Core/Types/Constant.php | 12 - seed/php-model/allof/src/Core/Types/Date.php | 16 - seed/php-model/allof/src/Core/Types/Union.php | 62 - seed/php-model/allof/src/Describable.php | 42 - seed/php-model/allof/src/DetailedOrg.php | 34 - .../allof/src/DetailedOrgMetadata.php | 42 - seed/php-model/allof/src/Identifiable.php | 42 - seed/php-model/allof/src/Organization.php | 50 - seed/php-model/allof/src/PaginatedResult.php | 43 - seed/php-model/allof/src/PagingCursors.php | 42 - .../allof/src/RuleExecutionContext.php | 10 - seed/php-model/allof/src/RuleResponse.php | 70 - .../allof/src/RuleResponseStatus.php | 10 - seed/php-model/allof/src/RuleType.php | 50 - .../allof/src/RuleTypeSearchResponse.php | 43 - seed/php-model/allof/src/Traits/AuditInfo.php | 42 - seed/php-model/allof/src/User.php | 42 - .../allof/src/UserSearchResponse.php | 43 - .../Core/Json/AdditionalPropertiesTest.php | 76 - .../allof/tests/Core/Json/DateArrayTest.php | 54 - .../allof/tests/Core/Json/EmptyArrayTest.php | 71 - .../allof/tests/Core/Json/EnumTest.php | 77 - .../allof/tests/Core/Json/ExhaustiveTest.php | 197 - .../allof/tests/Core/Json/InvalidTest.php | 42 - .../tests/Core/Json/NestedUnionArrayTest.php | 89 - .../tests/Core/Json/NullPropertyTest.php | 53 - .../tests/Core/Json/NullableArrayTest.php | 49 - .../allof/tests/Core/Json/ScalarTest.php | 116 - .../allof/tests/Core/Json/TraitTest.php | 60 - .../allof/tests/Core/Json/UnionArrayTest.php | 57 - .../tests/Core/Json/UnionPropertyTest.php | 111 - seed/php-sdk/allof-inline/.fern/metadata.json | 7 - .../allof-inline/.github/workflows/ci.yml | 52 - seed/php-sdk/allof-inline/.gitignore | 5 - seed/php-sdk/allof-inline/README.md | 169 - seed/php-sdk/allof-inline/composer.json | 46 - seed/php-sdk/allof-inline/phpstan.neon | 6 - seed/php-sdk/allof-inline/phpunit.xml | 7 - seed/php-sdk/allof-inline/reference.md | 171 - seed/php-sdk/allof-inline/snippet.json | 0 .../src/Core/Client/BaseApiRequest.php | 22 - .../src/Core/Client/HttpClientBuilder.php | 56 - .../src/Core/Client/HttpMethod.php | 12 - .../src/Core/Client/MockHttpClient.php | 75 - .../src/Core/Client/RawClient.php | 310 -- .../src/Core/Client/RetryDecoratingClient.php | 241 - .../src/Core/Json/JsonApiRequest.php | 28 - .../src/Core/Json/JsonDecoder.php | 161 - .../src/Core/Json/JsonDeserializer.php | 218 - .../src/Core/Json/JsonEncoder.php | 20 - .../src/Core/Json/JsonProperty.php | 13 - .../src/Core/Json/JsonSerializableType.php | 225 - .../src/Core/Json/JsonSerializer.php | 205 - .../allof-inline/src/Core/Json/Utils.php | 62 - .../Core/Multipart/MultipartApiRequest.php | 28 - .../src/Core/Multipart/MultipartFormData.php | 58 - .../Core/Multipart/MultipartFormDataPart.php | 62 - .../allof-inline/src/Core/Types/ArrayType.php | 16 - .../allof-inline/src/Core/Types/Constant.php | 12 - .../allof-inline/src/Core/Types/Date.php | 16 - .../allof-inline/src/Core/Types/Union.php | 62 - .../php-sdk/allof-inline/src/Environments.php | 8 - .../src/Exceptions/SeedApiException.php | 53 - .../src/Exceptions/SeedException.php | 12 - .../src/Requests/RuleCreateRequest.php | 35 - .../src/Requests/SearchRuleTypesRequest.php | 24 - seed/php-sdk/allof-inline/src/SeedClient.php | 302 -- .../allof-inline/src/Types/AuditInfo.php | 63 - .../allof-inline/src/Types/BaseOrg.php | 42 - .../src/Types/BaseOrgMetadata.php | 42 - .../allof-inline/src/Types/CombinedEntity.php | 58 - .../src/Types/CombinedEntityStatus.php | 9 - .../allof-inline/src/Types/Describable.php | 42 - .../allof-inline/src/Types/DetailedOrg.php | 34 - .../src/Types/DetailedOrgMetadata.php | 42 - .../allof-inline/src/Types/Identifiable.php | 42 - .../allof-inline/src/Types/Organization.php | 50 - .../src/Types/OrganizationMetadata.php | 42 - .../src/Types/PaginatedResult.php | 43 - .../allof-inline/src/Types/PagingCursors.php | 42 - .../src/Types/RuleExecutionContext.php | 10 - .../allof-inline/src/Types/RuleResponse.php | 92 - .../src/Types/RuleResponseStatus.php | 10 - .../allof-inline/src/Types/RuleType.php | 50 - .../src/Types/RuleTypeSearchResponse.php | 43 - seed/php-sdk/allof-inline/src/Types/User.php | 42 - .../src/Types/UserSearchResponse.php | 43 - seed/php-sdk/allof-inline/src/Utils/File.php | 129 - .../src/dynamic-snippets/example0/snippet.php | 15 - .../src/dynamic-snippets/example1/snippet.php | 17 - .../src/dynamic-snippets/example2/snippet.php | 19 - .../src/dynamic-snippets/example3/snippet.php | 19 - .../src/dynamic-snippets/example4/snippet.php | 12 - .../src/dynamic-snippets/example5/snippet.php | 12 - .../src/dynamic-snippets/example6/snippet.php | 12 - .../src/dynamic-snippets/example7/snippet.php | 12 - .../src/dynamic-snippets/example8/snippet.php | 12 - .../src/dynamic-snippets/example9/snippet.php | 12 - .../tests/Core/Client/RawClientTest.php | 1074 ---- .../Core/Json/AdditionalPropertiesTest.php | 76 - .../tests/Core/Json/DateArrayTest.php | 54 - .../tests/Core/Json/EmptyArrayTest.php | 71 - .../allof-inline/tests/Core/Json/EnumTest.php | 77 - .../tests/Core/Json/ExhaustiveTest.php | 197 - .../tests/Core/Json/InvalidTest.php | 42 - .../tests/Core/Json/NestedUnionArrayTest.php | 89 - .../tests/Core/Json/NullPropertyTest.php | 53 - .../tests/Core/Json/NullableArrayTest.php | 49 - .../tests/Core/Json/ScalarTest.php | 116 - .../tests/Core/Json/TraitTest.php | 60 - .../tests/Core/Json/UnionArrayTest.php | 57 - .../tests/Core/Json/UnionPropertyTest.php | 111 - seed/php-sdk/allof/.fern/metadata.json | 7 - seed/php-sdk/allof/.github/workflows/ci.yml | 52 - seed/php-sdk/allof/.gitignore | 5 - seed/php-sdk/allof/README.md | 169 - seed/php-sdk/allof/composer.json | 46 - seed/php-sdk/allof/phpstan.neon | 6 - seed/php-sdk/allof/phpunit.xml | 7 - seed/php-sdk/allof/reference.md | 171 - seed/php-sdk/allof/snippet.json | 0 .../allof/src/Core/Client/BaseApiRequest.php | 22 - .../src/Core/Client/HttpClientBuilder.php | 56 - .../allof/src/Core/Client/HttpMethod.php | 12 - .../allof/src/Core/Client/MockHttpClient.php | 75 - .../allof/src/Core/Client/RawClient.php | 310 -- .../src/Core/Client/RetryDecoratingClient.php | 241 - .../allof/src/Core/Json/JsonApiRequest.php | 28 - .../allof/src/Core/Json/JsonDecoder.php | 161 - .../allof/src/Core/Json/JsonDeserializer.php | 218 - .../allof/src/Core/Json/JsonEncoder.php | 20 - .../allof/src/Core/Json/JsonProperty.php | 13 - .../src/Core/Json/JsonSerializableType.php | 225 - .../allof/src/Core/Json/JsonSerializer.php | 205 - seed/php-sdk/allof/src/Core/Json/Utils.php | 62 - .../Core/Multipart/MultipartApiRequest.php | 28 - .../src/Core/Multipart/MultipartFormData.php | 58 - .../Core/Multipart/MultipartFormDataPart.php | 62 - .../allof/src/Core/Types/ArrayType.php | 16 - .../php-sdk/allof/src/Core/Types/Constant.php | 12 - seed/php-sdk/allof/src/Core/Types/Date.php | 16 - seed/php-sdk/allof/src/Core/Types/Union.php | 62 - seed/php-sdk/allof/src/Environments.php | 8 - .../allof/src/Exceptions/SeedApiException.php | 53 - .../allof/src/Exceptions/SeedException.php | 12 - .../allof/src/Requests/RuleCreateRequest.php | 35 - .../src/Requests/SearchRuleTypesRequest.php | 24 - seed/php-sdk/allof/src/SeedClient.php | 302 -- seed/php-sdk/allof/src/Traits/AuditInfo.php | 42 - seed/php-sdk/allof/src/Types/AuditInfo.php | 63 - seed/php-sdk/allof/src/Types/BaseOrg.php | 42 - .../allof/src/Types/BaseOrgMetadata.php | 42 - .../allof/src/Types/CombinedEntity.php | 58 - .../allof/src/Types/CombinedEntityStatus.php | 9 - seed/php-sdk/allof/src/Types/Describable.php | 42 - seed/php-sdk/allof/src/Types/DetailedOrg.php | 34 - .../allof/src/Types/DetailedOrgMetadata.php | 42 - seed/php-sdk/allof/src/Types/Identifiable.php | 42 - seed/php-sdk/allof/src/Types/Organization.php | 50 - .../allof/src/Types/PaginatedResult.php | 43 - .../php-sdk/allof/src/Types/PagingCursors.php | 42 - .../allof/src/Types/RuleExecutionContext.php | 10 - seed/php-sdk/allof/src/Types/RuleResponse.php | 70 - .../allof/src/Types/RuleResponseStatus.php | 10 - seed/php-sdk/allof/src/Types/RuleType.php | 50 - .../src/Types/RuleTypeSearchResponse.php | 43 - seed/php-sdk/allof/src/Types/User.php | 42 - .../allof/src/Types/UserSearchResponse.php | 43 - seed/php-sdk/allof/src/Utils/File.php | 129 - .../src/dynamic-snippets/example0/snippet.php | 15 - .../src/dynamic-snippets/example1/snippet.php | 17 - .../src/dynamic-snippets/example2/snippet.php | 19 - .../src/dynamic-snippets/example3/snippet.php | 19 - .../src/dynamic-snippets/example4/snippet.php | 12 - .../src/dynamic-snippets/example5/snippet.php | 12 - .../src/dynamic-snippets/example6/snippet.php | 12 - .../src/dynamic-snippets/example7/snippet.php | 12 - .../src/dynamic-snippets/example8/snippet.php | 12 - .../src/dynamic-snippets/example9/snippet.php | 12 - .../allof/tests/Core/Client/RawClientTest.php | 1074 ---- .../Core/Json/AdditionalPropertiesTest.php | 76 - .../allof/tests/Core/Json/DateArrayTest.php | 54 - .../allof/tests/Core/Json/EmptyArrayTest.php | 71 - .../allof/tests/Core/Json/EnumTest.php | 77 - .../allof/tests/Core/Json/ExhaustiveTest.php | 197 - .../allof/tests/Core/Json/InvalidTest.php | 42 - .../tests/Core/Json/NestedUnionArrayTest.php | 89 - .../tests/Core/Json/NullPropertyTest.php | 53 - .../tests/Core/Json/NullableArrayTest.php | 49 - .../allof/tests/Core/Json/ScalarTest.php | 116 - .../allof/tests/Core/Json/TraitTest.php | 60 - .../allof/tests/Core/Json/UnionArrayTest.php | 57 - .../tests/Core/Json/UnionPropertyTest.php | 111 - .../allof-inline/.github/workflows/ci.yml | 71 - seed/pydantic/allof-inline/.gitignore | 5 - seed/pydantic/allof-inline/README.md | 0 seed/pydantic/allof-inline/poetry.lock | 546 -- seed/pydantic/allof-inline/pyproject.toml | 93 - seed/pydantic/allof-inline/requirements.txt | 2 - seed/pydantic/allof-inline/snippet.json | 0 .../allof-inline/src/seed/api/__init__.py | 47 - .../allof-inline/src/seed/api/audit_info.py | 43 - .../allof-inline/src/seed/api/base_org.py | 19 - .../src/seed/api/base_org_metadata.py | 25 - .../src/seed/api/combined_entity.py | 33 - .../src/seed/api/combined_entity_status.py | 5 - .../src/seed/api/core/__init__.py | 29 - .../src/seed/api/core/datetime_utils.py | 70 - .../allof-inline/src/seed/api/core/enum.py | 20 - .../src/seed/api/core/pydantic_utilities.py | 515 -- .../src/seed/api/core/serialization.py | 276 - .../allof-inline/src/seed/api/describable.py | 25 - .../allof-inline/src/seed/api/detailed_org.py | 18 - .../src/seed/api/detailed_org_metadata.py | 25 - .../allof-inline/src/seed/api/identifiable.py | 25 - .../allof-inline/src/seed/api/organization.py | 20 - .../src/seed/api/organization_metadata.py | 25 - .../src/seed/api/paginated_result.py | 22 - .../src/seed/api/paging_cursors.py | 25 - .../allof-inline/src/seed/api/py.typed | 0 .../src/seed/api/rule_execution_context.py | 5 - .../src/seed/api/rule_response.py | 49 - .../src/seed/api/rule_response_status.py | 5 - .../allof-inline/src/seed/api/rule_type.py | 19 - .../src/seed/api/rule_type_search_response.py | 23 - .../allof-inline/src/seed/api/user.py | 18 - .../src/seed/api/user_search_response.py | 23 - .../allof-inline/tests/custom/test_client.py | 7 - seed/pydantic/allof/.github/workflows/ci.yml | 71 - seed/pydantic/allof/.gitignore | 5 - seed/pydantic/allof/README.md | 0 seed/pydantic/allof/poetry.lock | 546 -- seed/pydantic/allof/pyproject.toml | 93 - seed/pydantic/allof/requirements.txt | 2 - seed/pydantic/allof/snippet.json | 0 seed/pydantic/allof/src/seed/api/__init__.py | 45 - .../pydantic/allof/src/seed/api/audit_info.py | 43 - seed/pydantic/allof/src/seed/api/base_org.py | 19 - .../allof/src/seed/api/base_org_metadata.py | 25 - .../allof/src/seed/api/combined_entity.py | 32 - .../src/seed/api/combined_entity_status.py | 5 - .../allof/src/seed/api/core/__init__.py | 29 - .../allof/src/seed/api/core/datetime_utils.py | 70 - seed/pydantic/allof/src/seed/api/core/enum.py | 20 - .../src/seed/api/core/pydantic_utilities.py | 515 -- .../allof/src/seed/api/core/serialization.py | 276 - .../allof/src/seed/api/describable.py | 25 - .../allof/src/seed/api/detailed_org.py | 18 - .../src/seed/api/detailed_org_metadata.py | 25 - .../allof/src/seed/api/identifiable.py | 25 - .../allof/src/seed/api/organization.py | 20 - .../allof/src/seed/api/paginated_result.py | 22 - .../allof/src/seed/api/paging_cursors.py | 25 - seed/pydantic/allof/src/seed/api/py.typed | 0 .../src/seed/api/rule_execution_context.py | 5 - .../allof/src/seed/api/rule_response.py | 29 - .../src/seed/api/rule_response_status.py | 5 - seed/pydantic/allof/src/seed/api/rule_type.py | 19 - .../src/seed/api/rule_type_search_response.py | 24 - seed/pydantic/allof/src/seed/api/user.py | 18 - .../src/seed/api/user_search_response.py | 24 - .../allof/tests/custom/test_client.py | 7 - .../no-custom-config/.fern/metadata.json | 10 - .../no-custom-config/.github/workflows/ci.yml | 71 - .../allof-inline/no-custom-config/.gitignore | 5 - .../allof-inline/no-custom-config/README.md | 176 - .../allof-inline/no-custom-config/poetry.lock | 1614 ------ .../no-custom-config/pyproject.toml | 102 - .../no-custom-config/reference.md | 268 - .../no-custom-config/requirements.txt | 4 - .../no-custom-config/snippet.json | 70 - .../no-custom-config/src/seed/__init__.py | 113 - .../src/seed/_default_clients.py | 32 - .../no-custom-config/src/seed/client.py | 500 -- .../src/seed/core/__init__.py | 127 - .../src/seed/core/api_error.py | 23 - .../src/seed/core/client_wrapper.py | 95 - .../src/seed/core/datetime_utils.py | 70 - .../no-custom-config/src/seed/core/file.py | 67 - .../src/seed/core/force_multipart.py | 18 - .../src/seed/core/http_client.py | 840 --- .../src/seed/core/http_response.py | 59 - .../src/seed/core/http_sse/__init__.py | 42 - .../src/seed/core/http_sse/_api.py | 112 - .../src/seed/core/http_sse/_decoders.py | 61 - .../src/seed/core/http_sse/_exceptions.py | 7 - .../src/seed/core/http_sse/_models.py | 17 - .../src/seed/core/jsonable_encoder.py | 120 - .../no-custom-config/src/seed/core/logging.py | 107 - .../src/seed/core/parse_error.py | 36 - .../src/seed/core/pydantic_utilities.py | 634 --- .../src/seed/core/query_encoder.py | 58 - .../src/seed/core/remove_none_from_dict.py | 11 - .../src/seed/core/request_options.py | 35 - .../src/seed/core/serialization.py | 276 - .../no-custom-config/src/seed/environment.py | 7 - .../no-custom-config/src/seed/py.typed | 0 .../no-custom-config/src/seed/raw_client.py | 451 -- .../src/seed/types/__init__.py | 95 - .../src/seed/types/audit_info.py | 45 - .../src/seed/types/base_org.py | 21 - .../src/seed/types/base_org_metadata.py | 27 - .../src/seed/types/combined_entity.py | 35 - .../src/seed/types/combined_entity_status.py | 5 - .../src/seed/types/describable.py | 27 - .../src/seed/types/detailed_org.py | 20 - .../src/seed/types/detailed_org_metadata.py | 27 - .../src/seed/types/identifiable.py | 27 - .../src/seed/types/organization.py | 22 - .../src/seed/types/organization_metadata.py | 27 - .../src/seed/types/paginated_result.py | 24 - .../src/seed/types/paging_cursors.py | 27 - .../src/seed/types/rule_execution_context.py | 5 - .../src/seed/types/rule_response.py | 51 - .../src/seed/types/rule_response_status.py | 5 - .../src/seed/types/rule_type.py | 21 - .../seed/types/rule_type_search_response.py | 25 - .../no-custom-config/src/seed/types/user.py | 20 - .../src/seed/types/user_search_response.py | 25 - .../no-custom-config/src/seed/version.py | 3 - .../no-custom-config/tests/conftest.py | 147 - .../tests/custom/test_client.py | 7 - .../tests/test_aiohttp_autodetect.py | 116 - .../no-custom-config/tests/utils/__init__.py | 2 - .../tests/utils/assets/models/__init__.py | 21 - .../tests/utils/assets/models/circle.py | 11 - .../tests/utils/assets/models/color.py | 7 - .../assets/models/object_with_defaults.py | 15 - .../models/object_with_optional_field.py | 35 - .../tests/utils/assets/models/shape.py | 28 - .../tests/utils/assets/models/square.py | 11 - .../assets/models/undiscriminated_shape.py | 10 - .../tests/utils/test_http_client.py | 662 --- .../tests/utils/test_query_encoding.py | 36 - .../tests/utils/test_serialization.py | 72 - .../no-custom-config/tests/wire/__init__.py | 0 .../no-custom-config/tests/wire/conftest.py | 78 - .../no-custom-config/tests/wire/test_.py | 44 - .../wiremock/docker-compose.test.yml | 14 - .../wiremock/wiremock-mappings.json | 141 - .../no-custom-config/.fern/metadata.json | 10 - .../no-custom-config/.github/workflows/ci.yml | 71 - .../allof/no-custom-config/.gitignore | 5 - .../allof/no-custom-config/README.md | 176 - .../allof/no-custom-config/poetry.lock | 1614 ------ .../allof/no-custom-config/pyproject.toml | 102 - .../allof/no-custom-config/reference.md | 268 - .../allof/no-custom-config/requirements.txt | 4 - .../allof/no-custom-config/snippet.json | 70 - .../no-custom-config/src/seed/__init__.py | 110 - .../src/seed/_default_clients.py | 30 - .../allof/no-custom-config/src/seed/client.py | 500 -- .../src/seed/core/__init__.py | 127 - .../src/seed/core/api_error.py | 23 - .../src/seed/core/client_wrapper.py | 95 - .../src/seed/core/datetime_utils.py | 70 - .../no-custom-config/src/seed/core/file.py | 67 - .../src/seed/core/force_multipart.py | 18 - .../src/seed/core/http_client.py | 840 --- .../src/seed/core/http_response.py | 59 - .../src/seed/core/http_sse/__init__.py | 42 - .../src/seed/core/http_sse/_api.py | 112 - .../src/seed/core/http_sse/_decoders.py | 61 - .../src/seed/core/http_sse/_exceptions.py | 7 - .../src/seed/core/http_sse/_models.py | 17 - .../src/seed/core/jsonable_encoder.py | 120 - .../no-custom-config/src/seed/core/logging.py | 107 - .../src/seed/core/parse_error.py | 36 - .../src/seed/core/pydantic_utilities.py | 634 --- .../src/seed/core/query_encoder.py | 58 - .../src/seed/core/remove_none_from_dict.py | 11 - .../src/seed/core/request_options.py | 35 - .../src/seed/core/serialization.py | 276 - .../no-custom-config/src/seed/environment.py | 7 - .../allof/no-custom-config/src/seed/py.typed | 0 .../no-custom-config/src/seed/raw_client.py | 451 -- .../src/seed/types/__init__.py | 92 - .../src/seed/types/audit_info.py | 45 - .../src/seed/types/base_org.py | 21 - .../src/seed/types/base_org_metadata.py | 27 - .../src/seed/types/combined_entity.py | 34 - .../src/seed/types/combined_entity_status.py | 5 - .../src/seed/types/describable.py | 27 - .../src/seed/types/detailed_org.py | 20 - .../src/seed/types/detailed_org_metadata.py | 27 - .../src/seed/types/identifiable.py | 27 - .../src/seed/types/organization.py | 22 - .../src/seed/types/paginated_result.py | 24 - .../src/seed/types/paging_cursors.py | 27 - .../src/seed/types/rule_execution_context.py | 5 - .../src/seed/types/rule_response.py | 31 - .../src/seed/types/rule_response_status.py | 5 - .../src/seed/types/rule_type.py | 21 - .../seed/types/rule_type_search_response.py | 26 - .../no-custom-config/src/seed/types/user.py | 20 - .../src/seed/types/user_search_response.py | 26 - .../no-custom-config/src/seed/version.py | 3 - .../allof/no-custom-config/tests/conftest.py | 147 - .../tests/custom/test_client.py | 7 - .../tests/test_aiohttp_autodetect.py | 116 - .../no-custom-config/tests/utils/__init__.py | 2 - .../tests/utils/assets/models/__init__.py | 21 - .../tests/utils/assets/models/circle.py | 11 - .../tests/utils/assets/models/color.py | 7 - .../assets/models/object_with_defaults.py | 15 - .../models/object_with_optional_field.py | 35 - .../tests/utils/assets/models/shape.py | 28 - .../tests/utils/assets/models/square.py | 11 - .../assets/models/undiscriminated_shape.py | 10 - .../tests/utils/test_http_client.py | 662 --- .../tests/utils/test_query_encoding.py | 36 - .../tests/utils/test_serialization.py | 72 - .../no-custom-config/tests/wire/__init__.py | 0 .../no-custom-config/tests/wire/conftest.py | 78 - .../no-custom-config/tests/wire/test_.py | 44 - .../wiremock/docker-compose.test.yml | 14 - .../wiremock/wiremock-mappings.json | 141 - .../allof-inline/.fern/metadata.json | 7 - .../allof-inline/.github/workflows/ci.yml | 75 - seed/ruby-sdk-v2/allof-inline/.gitignore | 1 - seed/ruby-sdk-v2/allof-inline/.rubocop.yml | 69 - seed/ruby-sdk-v2/allof-inline/Gemfile | 19 - seed/ruby-sdk-v2/allof-inline/Gemfile.custom | 14 - seed/ruby-sdk-v2/allof-inline/README.md | 165 - seed/ruby-sdk-v2/allof-inline/Rakefile | 20 - .../allof-inline/custom.gemspec.rb | 16 - .../dynamic-snippets/example0/snippet.rb | 5 - .../dynamic-snippets/example1/snippet.rb | 5 - .../dynamic-snippets/example2/snippet.rb | 8 - .../dynamic-snippets/example3/snippet.rb | 8 - .../dynamic-snippets/example4/snippet.rb | 5 - .../dynamic-snippets/example5/snippet.rb | 5 - .../dynamic-snippets/example6/snippet.rb | 5 - .../dynamic-snippets/example7/snippet.rb | 5 - .../dynamic-snippets/example8/snippet.rb | 5 - .../dynamic-snippets/example9/snippet.rb | 5 - seed/ruby-sdk-v2/allof-inline/lib/seed.rb | 58 - .../allof-inline/lib/seed/environment.rb | 7 - .../allof-inline/lib/seed/errors/api_error.rb | 8 - .../lib/seed/errors/client_error.rb | 17 - .../lib/seed/errors/redirect_error.rb | 8 - .../lib/seed/errors/response_error.rb | 42 - .../lib/seed/errors/server_error.rb | 11 - .../lib/seed/errors/timeout_error.rb | 8 - .../seed/internal/errors/constraint_error.rb | 10 - .../lib/seed/internal/errors/type_error.rb | 10 - .../lib/seed/internal/http/base_request.rb | 51 - .../lib/seed/internal/http/raw_client.rb | 214 - .../iterators/cursor_item_iterator.rb | 28 - .../iterators/cursor_page_iterator.rb | 51 - .../seed/internal/iterators/item_iterator.rb | 59 - .../iterators/offset_item_iterator.rb | 30 - .../iterators/offset_page_iterator.rb | 83 - .../lib/seed/internal/json/request.rb | 41 - .../lib/seed/internal/json/serializable.rb | 25 - .../internal/multipart/multipart_encoder.rb | 141 - .../internal/multipart/multipart_form_data.rb | 78 - .../multipart/multipart_form_data_part.rb | 51 - .../internal/multipart/multipart_request.rb | 40 - .../lib/seed/internal/types/array.rb | 47 - .../lib/seed/internal/types/boolean.rb | 34 - .../lib/seed/internal/types/enum.rb | 56 - .../lib/seed/internal/types/hash.rb | 36 - .../lib/seed/internal/types/model.rb | 208 - .../lib/seed/internal/types/model/field.rb | 38 - .../lib/seed/internal/types/type.rb | 35 - .../lib/seed/internal/types/union.rb | 161 - .../lib/seed/internal/types/unknown.rb | 15 - .../lib/seed/internal/types/utils.rb | 116 - .../allof-inline/lib/seed/types/audit_info.rb | 13 - .../allof-inline/lib/seed/types/base_org.rb | 10 - .../lib/seed/types/base_org_metadata.rb | 10 - .../lib/seed/types/combined_entity.rb | 12 - .../lib/seed/types/combined_entity_status.rb | 12 - .../lib/seed/types/describable.rb | 10 - .../lib/seed/types/detailed_org.rb | 9 - .../lib/seed/types/detailed_org_metadata.rb | 10 - .../lib/seed/types/identifiable.rb | 10 - .../lib/seed/types/organization.rb | 11 - .../lib/seed/types/organization_metadata.rb | 10 - .../lib/seed/types/paginated_result.rb | 10 - .../lib/seed/types/paging_cursors.rb | 10 - .../lib/seed/types/rule_execution_context.rb | 13 - .../lib/seed/types/rule_response.rb | 16 - .../lib/seed/types/rule_response_status.rb | 13 - .../allof-inline/lib/seed/types/rule_type.rb | 11 - .../seed/types/rule_type_search_response.rb | 10 - .../allof-inline/lib/seed/types/user.rb | 10 - .../lib/seed/types/user_search_response.rb | 10 - .../allof-inline/lib/seed/version.rb | 5 - seed/ruby-sdk-v2/allof-inline/reference.md | 228 - seed/ruby-sdk-v2/allof-inline/seed.gemspec | 36 - seed/ruby-sdk-v2/allof-inline/snippet.json | 0 .../allof-inline/test/custom.test.rb | 15 - .../allof-inline/test/test_helper.rb | 3 - .../iterators/test_cursor_item_iterator.rb | 189 - .../iterators/test_offset_item_iterator.rb | 151 - .../test/unit/internal/types/test_array.rb | 37 - .../test/unit/internal/types/test_boolean.rb | 35 - .../test/unit/internal/types/test_enum.rb | 42 - .../test/unit/internal/types/test_hash.rb | 50 - .../test/unit/internal/types/test_model.rb | 154 - .../test/unit/internal/types/test_union.rb | 62 - .../test/unit/internal/types/test_utils.rb | 212 - seed/ruby-sdk-v2/allof/.fern/metadata.json | 7 - .../allof/.github/workflows/ci.yml | 75 - seed/ruby-sdk-v2/allof/.gitignore | 1 - seed/ruby-sdk-v2/allof/.rubocop.yml | 69 - seed/ruby-sdk-v2/allof/Gemfile | 19 - seed/ruby-sdk-v2/allof/Gemfile.custom | 14 - seed/ruby-sdk-v2/allof/README.md | 165 - seed/ruby-sdk-v2/allof/Rakefile | 20 - seed/ruby-sdk-v2/allof/custom.gemspec.rb | 16 - .../dynamic-snippets/example0/snippet.rb | 5 - .../dynamic-snippets/example1/snippet.rb | 5 - .../dynamic-snippets/example2/snippet.rb | 8 - .../dynamic-snippets/example3/snippet.rb | 8 - .../dynamic-snippets/example4/snippet.rb | 5 - .../dynamic-snippets/example5/snippet.rb | 5 - .../dynamic-snippets/example6/snippet.rb | 5 - .../dynamic-snippets/example7/snippet.rb | 5 - .../dynamic-snippets/example8/snippet.rb | 5 - .../dynamic-snippets/example9/snippet.rb | 5 - seed/ruby-sdk-v2/allof/lib/seed.rb | 57 - .../ruby-sdk-v2/allof/lib/seed/environment.rb | 7 - .../allof/lib/seed/errors/api_error.rb | 8 - .../allof/lib/seed/errors/client_error.rb | 17 - .../allof/lib/seed/errors/redirect_error.rb | 8 - .../allof/lib/seed/errors/response_error.rb | 42 - .../allof/lib/seed/errors/server_error.rb | 11 - .../allof/lib/seed/errors/timeout_error.rb | 8 - .../seed/internal/errors/constraint_error.rb | 10 - .../lib/seed/internal/errors/type_error.rb | 10 - .../lib/seed/internal/http/base_request.rb | 51 - .../lib/seed/internal/http/raw_client.rb | 214 - .../iterators/cursor_item_iterator.rb | 28 - .../iterators/cursor_page_iterator.rb | 51 - .../seed/internal/iterators/item_iterator.rb | 59 - .../iterators/offset_item_iterator.rb | 30 - .../iterators/offset_page_iterator.rb | 83 - .../allof/lib/seed/internal/json/request.rb | 41 - .../lib/seed/internal/json/serializable.rb | 25 - .../internal/multipart/multipart_encoder.rb | 141 - .../internal/multipart/multipart_form_data.rb | 78 - .../multipart/multipart_form_data_part.rb | 51 - .../internal/multipart/multipart_request.rb | 40 - .../allof/lib/seed/internal/types/array.rb | 47 - .../allof/lib/seed/internal/types/boolean.rb | 34 - .../allof/lib/seed/internal/types/enum.rb | 56 - .../allof/lib/seed/internal/types/hash.rb | 36 - .../allof/lib/seed/internal/types/model.rb | 208 - .../lib/seed/internal/types/model/field.rb | 38 - .../allof/lib/seed/internal/types/type.rb | 35 - .../allof/lib/seed/internal/types/union.rb | 161 - .../allof/lib/seed/internal/types/unknown.rb | 15 - .../allof/lib/seed/internal/types/utils.rb | 116 - .../allof/lib/seed/types/audit_info.rb | 13 - .../allof/lib/seed/types/base_org.rb | 10 - .../allof/lib/seed/types/base_org_metadata.rb | 10 - .../allof/lib/seed/types/combined_entity.rb | 12 - .../lib/seed/types/combined_entity_status.rb | 12 - .../allof/lib/seed/types/describable.rb | 10 - .../allof/lib/seed/types/detailed_org.rb | 9 - .../lib/seed/types/detailed_org_metadata.rb | 10 - .../allof/lib/seed/types/identifiable.rb | 10 - .../allof/lib/seed/types/organization.rb | 11 - .../allof/lib/seed/types/paginated_result.rb | 10 - .../allof/lib/seed/types/paging_cursors.rb | 10 - .../lib/seed/types/rule_execution_context.rb | 13 - .../allof/lib/seed/types/rule_response.rb | 12 - .../lib/seed/types/rule_response_status.rb | 13 - .../allof/lib/seed/types/rule_type.rb | 11 - .../seed/types/rule_type_search_response.rb | 10 - seed/ruby-sdk-v2/allof/lib/seed/types/user.rb | 10 - .../lib/seed/types/user_search_response.rb | 10 - seed/ruby-sdk-v2/allof/lib/seed/version.rb | 5 - seed/ruby-sdk-v2/allof/reference.md | 228 - seed/ruby-sdk-v2/allof/seed.gemspec | 36 - seed/ruby-sdk-v2/allof/snippet.json | 0 seed/ruby-sdk-v2/allof/test/custom.test.rb | 15 - seed/ruby-sdk-v2/allof/test/test_helper.rb | 3 - .../iterators/test_cursor_item_iterator.rb | 189 - .../iterators/test_offset_item_iterator.rb | 151 - .../test/unit/internal/types/test_array.rb | 37 - .../test/unit/internal/types/test_boolean.rb | 35 - .../test/unit/internal/types/test_enum.rb | 42 - .../test/unit/internal/types/test_hash.rb | 50 - .../test/unit/internal/types/test_model.rb | 154 - .../test/unit/internal/types/test_union.rb | 62 - .../test/unit/internal/types/test_utils.rb | 212 - .../allof-inline/.fern/metadata.json | 7 - seed/rust-model/allof-inline/snippet.json | 0 .../rust-model/allof-inline/src/audit_info.rs | 73 - seed/rust-model/allof-inline/src/base_org.rs | 44 - .../allof-inline/src/base_org_metadata.rs | 46 - .../allof-inline/src/combined_entity.rs | 65 - .../src/combined_entity_status.rs | 42 - .../allof-inline/src/describable.rs | 44 - .../allof-inline/src/detailed_org.rs | 33 - .../allof-inline/src/detailed_org_metadata.rs | 46 - seed/rust-model/allof-inline/src/error.rs | 19 - .../allof-inline/src/identifiable.rs | 46 - seed/rust-model/allof-inline/src/lib.rs | 7 - seed/rust-model/allof-inline/src/mod.rs | 56 - .../allof-inline/src/organization.rs | 54 - .../allof-inline/src/organization_metadata.rs | 46 - .../allof-inline/src/paginated_result.rs | 46 - .../allof-inline/src/paging_cursors.rs | 46 - .../allof-inline/src/rule_create_request.rs | 46 - .../src/rule_execution_context.rs | 47 - .../allof-inline/src/rule_response.rs | 112 - .../allof-inline/src/rule_response_status.rs | 46 - seed/rust-model/allof-inline/src/rule_type.rs | 54 - .../src/rule_type_search_response.rs | 45 - .../src/search_rule_types_query_request.rs | 35 - seed/rust-model/allof-inline/src/user.rs | 45 - .../allof-inline/src/user_search_response.rs | 45 - seed/rust-model/allof/.fern/metadata.json | 7 - seed/rust-model/allof/snippet.json | 0 seed/rust-model/allof/src/audit_info.rs | 73 - seed/rust-model/allof/src/base_org.rs | 44 - .../rust-model/allof/src/base_org_metadata.rs | 46 - seed/rust-model/allof/src/combined_entity.rs | 65 - .../allof/src/combined_entity_status.rs | 42 - seed/rust-model/allof/src/describable.rs | 44 - seed/rust-model/allof/src/detailed_org.rs | 33 - .../allof/src/detailed_org_metadata.rs | 46 - seed/rust-model/allof/src/error.rs | 19 - seed/rust-model/allof/src/identifiable.rs | 46 - seed/rust-model/allof/src/lib.rs | 7 - seed/rust-model/allof/src/mod.rs | 54 - seed/rust-model/allof/src/organization.rs | 54 - seed/rust-model/allof/src/paginated_result.rs | 46 - seed/rust-model/allof/src/paging_cursors.rs | 46 - .../allof/src/rule_create_request.rs | 46 - .../allof/src/rule_execution_context.rs | 47 - seed/rust-model/allof/src/rule_response.rs | 74 - .../allof/src/rule_response_status.rs | 46 - seed/rust-model/allof/src/rule_type.rs | 54 - .../allof/src/rule_type_search_response.rs | 45 - .../src/search_rule_types_query_request.rs | 35 - seed/rust-model/allof/src/user.rs | 45 - .../allof/src/user_search_response.rs | 45 - .../rust-sdk/allof-inline/.fern/metadata.json | 7 - .../allof-inline/.github/workflows/ci.yml | 63 - seed/rust-sdk/allof-inline/.gitignore | 5 - seed/rust-sdk/allof-inline/Cargo.toml | 25 - seed/rust-sdk/allof-inline/README.md | 181 - .../allof-inline/dynamic-snippets/example0.rs | 18 - .../allof-inline/dynamic-snippets/example1.rs | 19 - .../allof-inline/dynamic-snippets/example2.rs | 19 - .../allof-inline/dynamic-snippets/example3.rs | 19 - .../allof-inline/dynamic-snippets/example4.rs | 11 - .../allof-inline/dynamic-snippets/example5.rs | 11 - .../allof-inline/dynamic-snippets/example6.rs | 11 - .../allof-inline/dynamic-snippets/example7.rs | 11 - .../allof-inline/dynamic-snippets/example8.rs | 11 - .../allof-inline/dynamic-snippets/example9.rs | 11 - seed/rust-sdk/allof-inline/reference.md | 224 - seed/rust-sdk/allof-inline/rustfmt.toml | 4 - seed/rust-sdk/allof-inline/snippet.json | 0 seed/rust-sdk/allof-inline/src/api/mod.rs | 15 - .../allof-inline/src/api/resources/mod.rs | 82 - .../allof-inline/src/api/types/audit_info.rs | 73 - .../allof-inline/src/api/types/base_org.rs | 44 - .../src/api/types/base_org_metadata.rs | 48 - .../src/api/types/combined_entity.rs | 67 - .../src/api/types/combined_entity_status.rs | 42 - .../allof-inline/src/api/types/describable.rs | 44 - .../src/api/types/detailed_org.rs | 33 - .../src/api/types/detailed_org_metadata.rs | 48 - .../src/api/types/identifiable.rs | 46 - .../allof-inline/src/api/types/mod.rs | 45 - .../src/api/types/organization.rs | 54 - .../src/api/types/organization_metadata.rs | 48 - .../src/api/types/paginated_result.rs | 50 - .../src/api/types/paging_cursors.rs | 46 - .../src/api/types/rule_create_request.rs | 47 - .../src/api/types/rule_execution_context.rs | 47 - .../src/api/types/rule_response.rs | 114 - .../src/api/types/rule_response_status.rs | 46 - .../allof-inline/src/api/types/rule_type.rs | 54 - .../api/types/rule_type_search_response.rs | 47 - .../types/search_rule_types_query_request.rs | 32 - .../allof-inline/src/api/types/user.rs | 47 - .../src/api/types/user_search_response.rs | 47 - seed/rust-sdk/allof-inline/src/client.rs | 247 - seed/rust-sdk/allof-inline/src/config.rs | 39 - .../src/core/flexible_datetime.rs | 270 - .../allof-inline/src/core/http_client.rs | 569 -- seed/rust-sdk/allof-inline/src/core/mod.rs | 14 - .../src/core/oauth_token_provider.rs | 363 -- .../allof-inline/src/core/pagination.rs | 541 -- .../src/core/query_parameter_builder.rs | 576 -- .../allof-inline/src/core/request_options.rs | 176 - seed/rust-sdk/allof-inline/src/core/utils.rs | 77 - seed/rust-sdk/allof-inline/src/environment.rs | 19 - seed/rust-sdk/allof-inline/src/error.rs | 54 - seed/rust-sdk/allof-inline/src/lib.rs | 49 - seed/rust-sdk/allof-inline/src/prelude.rs | 19 - seed/rust-sdk/allof/.fern/metadata.json | 7 - seed/rust-sdk/allof/.github/workflows/ci.yml | 63 - seed/rust-sdk/allof/.gitignore | 5 - seed/rust-sdk/allof/Cargo.toml | 25 - seed/rust-sdk/allof/README.md | 181 - .../allof/dynamic-snippets/example0.rs | 18 - .../allof/dynamic-snippets/example1.rs | 19 - .../allof/dynamic-snippets/example2.rs | 19 - .../allof/dynamic-snippets/example3.rs | 19 - .../allof/dynamic-snippets/example4.rs | 11 - .../allof/dynamic-snippets/example5.rs | 11 - .../allof/dynamic-snippets/example6.rs | 11 - .../allof/dynamic-snippets/example7.rs | 11 - .../allof/dynamic-snippets/example8.rs | 11 - .../allof/dynamic-snippets/example9.rs | 11 - seed/rust-sdk/allof/reference.md | 224 - seed/rust-sdk/allof/rustfmt.toml | 4 - seed/rust-sdk/allof/snippet.json | 0 seed/rust-sdk/allof/src/api/mod.rs | 15 - seed/rust-sdk/allof/src/api/resources/mod.rs | 82 - .../allof/src/api/types/audit_info.rs | 73 - seed/rust-sdk/allof/src/api/types/base_org.rs | 44 - .../allof/src/api/types/base_org_metadata.rs | 48 - .../allof/src/api/types/combined_entity.rs | 67 - .../src/api/types/combined_entity_status.rs | 42 - .../allof/src/api/types/describable.rs | 44 - .../allof/src/api/types/detailed_org.rs | 33 - .../src/api/types/detailed_org_metadata.rs | 48 - .../allof/src/api/types/identifiable.rs | 46 - seed/rust-sdk/allof/src/api/types/mod.rs | 43 - .../allof/src/api/types/organization.rs | 54 - .../allof/src/api/types/paginated_result.rs | 50 - .../allof/src/api/types/paging_cursors.rs | 46 - .../src/api/types/rule_create_request.rs | 47 - .../src/api/types/rule_execution_context.rs | 47 - .../allof/src/api/types/rule_response.rs | 78 - .../src/api/types/rule_response_status.rs | 46 - .../rust-sdk/allof/src/api/types/rule_type.rs | 54 - .../api/types/rule_type_search_response.rs | 47 - .../types/search_rule_types_query_request.rs | 32 - seed/rust-sdk/allof/src/api/types/user.rs | 47 - .../src/api/types/user_search_response.rs | 47 - seed/rust-sdk/allof/src/client.rs | 247 - seed/rust-sdk/allof/src/config.rs | 39 - .../allof/src/core/flexible_datetime.rs | 270 - seed/rust-sdk/allof/src/core/http_client.rs | 569 -- seed/rust-sdk/allof/src/core/mod.rs | 14 - .../allof/src/core/oauth_token_provider.rs | 363 -- seed/rust-sdk/allof/src/core/pagination.rs | 541 -- .../allof/src/core/query_parameter_builder.rs | 576 -- .../allof/src/core/request_options.rs | 176 - seed/rust-sdk/allof/src/core/utils.rs | 77 - seed/rust-sdk/allof/src/environment.rs | 19 - seed/rust-sdk/allof/src/error.rs | 54 - seed/rust-sdk/allof/src/lib.rs | 49 - seed/rust-sdk/allof/src/prelude.rs | 19 - .../allof-inline/.fern/metadata.json | 8 - seed/swift-sdk/allof-inline/Package.swift | 31 - seed/swift-sdk/allof-inline/README.md | 180 - .../allof-inline/Snippets/Example0.swift | 10 - .../allof-inline/Snippets/Example1.swift | 10 - .../allof-inline/Snippets/Example2.swift | 13 - .../allof-inline/Snippets/Example3.swift | 13 - .../allof-inline/Snippets/Example4.swift | 10 - .../allof-inline/Snippets/Example5.swift | 10 - .../allof-inline/Snippets/Example6.swift | 10 - .../allof-inline/Snippets/Example7.swift | 10 - .../allof-inline/Snippets/Example8.swift | 10 - .../allof-inline/Snippets/Example9.swift | 10 - .../allof-inline/Sources/ApiClient.swift | 104 - .../allof-inline/Sources/ApiEnvironment.swift | 5 - .../allof-inline/Sources/ApiError.swift | 57 - .../Sources/Core/Data+String.swift | 15 - .../Sources/Core/Networking/HTTP.swift | 24 - .../Sources/Core/Networking/HTTPClient.swift | 397 -- .../Core/Networking/MultipartFormData.swift | 45 - .../MultipartFormDataConvertible.swift | 42 - .../Core/Networking/MultipartFormField.swift | 17 - .../Core/Networking/QueryParameter.swift | 48 - .../Serde/Decoder+AdditionalProperties.swift | 27 - .../Sources/Core/Serde/EncodableValue.swift | 10 - .../Serde/Encoder+AdditionalProperties.swift | 13 - .../Serde/JSONEncoder+EncodableValue.swift | 20 - .../KeyedDecodingContainer+Nullable.swift | 20 - .../KeyedEncodingContainer+Nullable.swift | 19 - .../Sources/Core/Serde/Serde.swift | 35 - .../Sources/Core/Serde/StringKey.swift | 18 - .../Sources/Core/String+URLEncoding.swift | 11 - .../Sources/Public/CalendarDate.swift | 105 - .../Sources/Public/ClientConfig.swift | 82 - .../Sources/Public/FormFile.swift | 32 - .../Sources/Public/HTTPError.swift | 190 - .../Sources/Public/Indirect.swift | 27 - .../Sources/Public/JSONValue.swift | 136 - .../Sources/Public/Networking.swift | 21 - .../Sources/Public/Nullable.swift | 59 - .../Sources/Public/RequestOptions.swift | 45 - .../Requests/Requests+RuleCreateRequest.swift | 40 - .../Sources/Requests/Requests.swift | 6 - .../Sources/Schemas/AuditInfo.swift | 55 - .../Sources/Schemas/BaseOrg.swift | 38 - .../Sources/Schemas/BaseOrgMetadata.swift | 40 - .../Sources/Schemas/CombinedEntity.swift | 53 - .../Schemas/CombinedEntityStatus.swift | 6 - .../Sources/Schemas/Describable.swift | 40 - .../Sources/Schemas/DetailedOrg.swift | 32 - .../Sources/Schemas/DetailedOrgMetadata.swift | 40 - .../Sources/Schemas/Identifiable.swift | 40 - .../Sources/Schemas/Organization.swift | 44 - .../Schemas/OrganizationMetadata.swift | 40 - .../Sources/Schemas/PaginatedResult.swift | 39 - .../Sources/Schemas/PagingCursors.swift | 40 - .../Schemas/RuleExecutionContext.swift | 8 - .../Sources/Schemas/RuleResponse.swift | 78 - .../Sources/Schemas/RuleResponseStatus.swift | 7 - .../Sources/Schemas/RuleType.swift | 44 - .../Schemas/RuleTypeSearchResponse.swift | 39 - .../allof-inline/Sources/Schemas/User.swift | 38 - .../Sources/Schemas/UserSearchResponse.swift | 39 - .../allof-inline/Sources/Version.swift | 1 - .../Tests/Core/ClientErrorTests.swift | 222 - .../Tests/Core/ClientRetryTests.swift | 355 -- .../Tests/Utilities/HTTPStub.swift | 317 -- seed/swift-sdk/allof-inline/reference.md | 265 - seed/swift-sdk/allof-inline/snippet.json | 0 seed/swift-sdk/allof/.fern/metadata.json | 8 - seed/swift-sdk/allof/Package.swift | 31 - seed/swift-sdk/allof/README.md | 180 - seed/swift-sdk/allof/Snippets/Example0.swift | 10 - seed/swift-sdk/allof/Snippets/Example1.swift | 10 - seed/swift-sdk/allof/Snippets/Example2.swift | 13 - seed/swift-sdk/allof/Snippets/Example3.swift | 13 - seed/swift-sdk/allof/Snippets/Example4.swift | 10 - seed/swift-sdk/allof/Snippets/Example5.swift | 10 - seed/swift-sdk/allof/Snippets/Example6.swift | 10 - seed/swift-sdk/allof/Snippets/Example7.swift | 10 - seed/swift-sdk/allof/Snippets/Example8.swift | 10 - seed/swift-sdk/allof/Snippets/Example9.swift | 10 - seed/swift-sdk/allof/Sources/ApiClient.swift | 104 - .../allof/Sources/ApiEnvironment.swift | 5 - seed/swift-sdk/allof/Sources/ApiError.swift | 57 - .../allof/Sources/Core/Data+String.swift | 15 - .../allof/Sources/Core/Networking/HTTP.swift | 24 - .../Sources/Core/Networking/HTTPClient.swift | 397 -- .../Core/Networking/MultipartFormData.swift | 45 - .../MultipartFormDataConvertible.swift | 42 - .../Core/Networking/MultipartFormField.swift | 17 - .../Core/Networking/QueryParameter.swift | 48 - .../Serde/Decoder+AdditionalProperties.swift | 27 - .../Sources/Core/Serde/EncodableValue.swift | 10 - .../Serde/Encoder+AdditionalProperties.swift | 13 - .../Serde/JSONEncoder+EncodableValue.swift | 20 - .../KeyedDecodingContainer+Nullable.swift | 20 - .../KeyedEncodingContainer+Nullable.swift | 19 - .../allof/Sources/Core/Serde/Serde.swift | 35 - .../allof/Sources/Core/Serde/StringKey.swift | 18 - .../Sources/Core/String+URLEncoding.swift | 11 - .../allof/Sources/Public/CalendarDate.swift | 105 - .../allof/Sources/Public/ClientConfig.swift | 82 - .../allof/Sources/Public/FormFile.swift | 32 - .../allof/Sources/Public/HTTPError.swift | 190 - .../allof/Sources/Public/Indirect.swift | 27 - .../allof/Sources/Public/JSONValue.swift | 136 - .../allof/Sources/Public/Networking.swift | 21 - .../allof/Sources/Public/Nullable.swift | 59 - .../allof/Sources/Public/RequestOptions.swift | 45 - .../Requests/Requests+RuleCreateRequest.swift | 40 - .../allof/Sources/Requests/Requests.swift | 6 - .../allof/Sources/Schemas/AuditInfo.swift | 55 - .../allof/Sources/Schemas/BaseOrg.swift | 38 - .../Sources/Schemas/BaseOrgMetadata.swift | 40 - .../Sources/Schemas/CombinedEntity.swift | 53 - .../Schemas/CombinedEntityStatus.swift | 6 - .../allof/Sources/Schemas/Describable.swift | 40 - .../allof/Sources/Schemas/DetailedOrg.swift | 32 - .../Sources/Schemas/DetailedOrgMetadata.swift | 40 - .../allof/Sources/Schemas/Identifiable.swift | 40 - .../allof/Sources/Schemas/Organization.swift | 44 - .../Sources/Schemas/PaginatedResult.swift | 39 - .../allof/Sources/Schemas/PagingCursors.swift | 40 - .../Schemas/RuleExecutionContext.swift | 8 - .../allof/Sources/Schemas/RuleResponse.swift | 78 - .../Sources/Schemas/RuleResponseStatus.swift | 7 - .../allof/Sources/Schemas/RuleType.swift | 44 - .../Schemas/RuleTypeSearchResponse.swift | 39 - .../allof/Sources/Schemas/User.swift | 38 - .../Sources/Schemas/UserSearchResponse.swift | 39 - seed/swift-sdk/allof/Sources/Version.swift | 1 - .../allof/Tests/Core/ClientErrorTests.swift | 222 - .../allof/Tests/Core/ClientRetryTests.swift | 355 -- .../allof/Tests/Utilities/HTTPStub.swift | 317 -- seed/swift-sdk/allof/reference.md | 265 - seed/swift-sdk/allof/snippet.json | 0 seed/ts-sdk/allof-inline/.fern/metadata.json | 7 - .../allof-inline/.github/workflows/ci.yml | 46 - seed/ts-sdk/allof-inline/.gitignore | 3 - seed/ts-sdk/allof-inline/CONTRIBUTING.md | 133 - seed/ts-sdk/allof-inline/README.md | 292 - seed/ts-sdk/allof-inline/biome.json | 74 - seed/ts-sdk/allof-inline/package.json | 69 - seed/ts-sdk/allof-inline/pnpm-workspace.yaml | 1 - seed/ts-sdk/allof-inline/reference.md | 225 - .../scripts/rename-to-esm-files.js | 123 - seed/ts-sdk/allof-inline/snippet.json | 60 - seed/ts-sdk/allof-inline/src/BaseClient.ts | 60 - seed/ts-sdk/allof-inline/src/Client.ts | 303 -- .../allof-inline/src/api/client/index.ts | 1 - .../api/client/requests/RuleCreateRequest.ts | 15 - .../client/requests/SearchRuleTypesRequest.ts | 9 - .../src/api/client/requests/index.ts | 2 - seed/ts-sdk/allof-inline/src/api/index.ts | 2 - .../allof-inline/src/api/types/AuditInfo.ts | 15 - .../allof-inline/src/api/types/BaseOrg.ts | 15 - .../src/api/types/CombinedEntity.ts | 19 - .../allof-inline/src/api/types/Describable.ts | 8 - .../allof-inline/src/api/types/DetailedOrg.ts | 14 - .../src/api/types/Identifiable.ts | 8 - .../src/api/types/Organization.ts | 16 - .../src/api/types/PaginatedResult.ts | 9 - .../src/api/types/PagingCursors.ts | 8 - .../src/api/types/RuleExecutionContext.ts | 9 - .../src/api/types/RuleResponse.ts | 27 - .../allof-inline/src/api/types/RuleType.ts | 7 - .../src/api/types/RuleTypeSearchResponse.ts | 9 - .../ts-sdk/allof-inline/src/api/types/User.ts | 6 - .../src/api/types/UserSearchResponse.ts | 9 - .../allof-inline/src/api/types/index.ts | 15 - seed/ts-sdk/allof-inline/src/core/exports.ts | 1 - .../src/core/fetcher/APIResponse.ts | 23 - .../src/core/fetcher/BinaryResponse.ts | 34 - .../src/core/fetcher/EndpointMetadata.ts | 13 - .../src/core/fetcher/EndpointSupplier.ts | 14 - .../allof-inline/src/core/fetcher/Fetcher.ts | 404 -- .../allof-inline/src/core/fetcher/Headers.ts | 93 - .../src/core/fetcher/HttpResponsePromise.ts | 116 - .../src/core/fetcher/RawResponse.ts | 61 - .../allof-inline/src/core/fetcher/Supplier.ts | 11 - .../src/core/fetcher/createRequestUrl.ts | 6 - .../src/core/fetcher/getErrorResponseBody.ts | 33 - .../src/core/fetcher/getFetchFn.ts | 3 - .../src/core/fetcher/getHeader.ts | 8 - .../src/core/fetcher/getRequestBody.ts | 20 - .../src/core/fetcher/getResponseBody.ts | 58 - .../allof-inline/src/core/fetcher/index.ts | 13 - .../core/fetcher/makePassthroughRequest.ts | 189 - .../src/core/fetcher/makeRequest.ts | 70 - .../src/core/fetcher/requestWithRetries.ts | 64 - .../allof-inline/src/core/fetcher/signals.ts | 26 - seed/ts-sdk/allof-inline/src/core/headers.ts | 33 - seed/ts-sdk/allof-inline/src/core/index.ts | 4 - seed/ts-sdk/allof-inline/src/core/json.ts | 27 - .../allof-inline/src/core/logging/exports.ts | 19 - .../allof-inline/src/core/logging/index.ts | 1 - .../allof-inline/src/core/logging/logger.ts | 203 - .../allof-inline/src/core/runtime/index.ts | 1 - .../allof-inline/src/core/runtime/runtime.ts | 134 - .../src/core/url/encodePathParam.ts | 18 - .../ts-sdk/allof-inline/src/core/url/index.ts | 3 - seed/ts-sdk/allof-inline/src/core/url/join.ts | 79 - seed/ts-sdk/allof-inline/src/core/url/qs.ts | 74 - seed/ts-sdk/allof-inline/src/environments.ts | 7 - .../allof-inline/src/errors/SeedApiError.ts | 64 - .../src/errors/SeedApiTimeoutError.ts | 18 - .../src/errors/handleNonStatusCodeError.ts | 40 - seed/ts-sdk/allof-inline/src/errors/index.ts | 2 - seed/ts-sdk/allof-inline/src/exports.ts | 1 - seed/ts-sdk/allof-inline/src/index.ts | 6 - seed/ts-sdk/allof-inline/src/version.ts | 1 - seed/ts-sdk/allof-inline/tests/custom.test.ts | 13 - .../tests/mock-server/MockServer.ts | 29 - .../tests/mock-server/MockServerPool.ts | 106 - .../tests/mock-server/mockEndpointBuilder.ts | 234 - .../tests/mock-server/randomBaseUrl.ts | 4 - .../allof-inline/tests/mock-server/setup.ts | 10 - .../tests/mock-server/withFormUrlEncoded.ts | 104 - .../tests/mock-server/withHeaders.ts | 70 - .../tests/mock-server/withJson.ts | 173 - seed/ts-sdk/allof-inline/tests/setup.ts | 80 - seed/ts-sdk/allof-inline/tests/tsconfig.json | 10 - .../tests/unit/fetcher/Fetcher.test.ts | 262 - .../unit/fetcher/HttpResponsePromise.test.ts | 143 - .../tests/unit/fetcher/RawResponse.test.ts | 34 - .../unit/fetcher/createRequestUrl.test.ts | 163 - .../tests/unit/fetcher/getRequestBody.test.ts | 129 - .../unit/fetcher/getResponseBody.test.ts | 97 - .../tests/unit/fetcher/logging.test.ts | 517 -- .../fetcher/makePassthroughRequest.test.ts | 398 -- .../tests/unit/fetcher/makeRequest.test.ts | 158 - .../tests/unit/fetcher/redacting.test.ts | 1115 ---- .../unit/fetcher/requestWithRetries.test.ts | 230 - .../tests/unit/fetcher/signals.test.ts | 69 - .../tests/unit/fetcher/test-file.txt | 1 - .../tests/unit/logging/logger.test.ts | 454 -- .../allof-inline/tests/unit/url/join.test.ts | 284 - .../allof-inline/tests/unit/url/qs.test.ts | 278 - seed/ts-sdk/allof-inline/tests/wire/.gitkeep | 0 .../allof-inline/tests/wire/main.test.ts | 91 - seed/ts-sdk/allof-inline/tsconfig.base.json | 17 - seed/ts-sdk/allof-inline/tsconfig.cjs.json | 9 - seed/ts-sdk/allof-inline/tsconfig.esm.json | 10 - seed/ts-sdk/allof-inline/tsconfig.json | 3 - seed/ts-sdk/allof-inline/vitest.config.mts | 32 - seed/ts-sdk/allof/.fern/metadata.json | 7 - seed/ts-sdk/allof/.github/workflows/ci.yml | 46 - seed/ts-sdk/allof/.gitignore | 3 - seed/ts-sdk/allof/CONTRIBUTING.md | 133 - seed/ts-sdk/allof/README.md | 292 - seed/ts-sdk/allof/biome.json | 74 - seed/ts-sdk/allof/package.json | 69 - seed/ts-sdk/allof/pnpm-workspace.yaml | 1 - seed/ts-sdk/allof/reference.md | 225 - .../allof/scripts/rename-to-esm-files.js | 123 - seed/ts-sdk/allof/snippet.json | 60 - seed/ts-sdk/allof/src/BaseClient.ts | 60 - seed/ts-sdk/allof/src/Client.ts | 303 -- seed/ts-sdk/allof/src/api/client/index.ts | 1 - .../api/client/requests/RuleCreateRequest.ts | 15 - .../client/requests/SearchRuleTypesRequest.ts | 9 - .../allof/src/api/client/requests/index.ts | 2 - seed/ts-sdk/allof/src/api/index.ts | 2 - seed/ts-sdk/allof/src/api/types/AuditInfo.ts | 15 - seed/ts-sdk/allof/src/api/types/BaseOrg.ts | 15 - .../allof/src/api/types/CombinedEntity.ts | 19 - .../ts-sdk/allof/src/api/types/Describable.ts | 8 - .../ts-sdk/allof/src/api/types/DetailedOrg.ts | 14 - .../allof/src/api/types/Identifiable.ts | 8 - .../allof/src/api/types/Organization.ts | 16 - .../allof/src/api/types/PaginatedResult.ts | 9 - .../allof/src/api/types/PagingCursors.ts | 8 - .../src/api/types/RuleExecutionContext.ts | 9 - .../allof/src/api/types/RuleResponse.ts | 19 - seed/ts-sdk/allof/src/api/types/RuleType.ts | 7 - .../src/api/types/RuleTypeSearchResponse.ts | 9 - seed/ts-sdk/allof/src/api/types/User.ts | 6 - .../allof/src/api/types/UserSearchResponse.ts | 9 - seed/ts-sdk/allof/src/api/types/index.ts | 15 - seed/ts-sdk/allof/src/core/exports.ts | 1 - .../allof/src/core/fetcher/APIResponse.ts | 23 - .../allof/src/core/fetcher/BinaryResponse.ts | 34 - .../src/core/fetcher/EndpointMetadata.ts | 13 - .../src/core/fetcher/EndpointSupplier.ts | 14 - seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts | 404 -- seed/ts-sdk/allof/src/core/fetcher/Headers.ts | 93 - .../src/core/fetcher/HttpResponsePromise.ts | 116 - .../allof/src/core/fetcher/RawResponse.ts | 61 - .../ts-sdk/allof/src/core/fetcher/Supplier.ts | 11 - .../src/core/fetcher/createRequestUrl.ts | 6 - .../src/core/fetcher/getErrorResponseBody.ts | 33 - .../allof/src/core/fetcher/getFetchFn.ts | 3 - .../allof/src/core/fetcher/getHeader.ts | 8 - .../allof/src/core/fetcher/getRequestBody.ts | 20 - .../allof/src/core/fetcher/getResponseBody.ts | 58 - seed/ts-sdk/allof/src/core/fetcher/index.ts | 13 - .../core/fetcher/makePassthroughRequest.ts | 189 - .../allof/src/core/fetcher/makeRequest.ts | 70 - .../src/core/fetcher/requestWithRetries.ts | 64 - seed/ts-sdk/allof/src/core/fetcher/signals.ts | 26 - seed/ts-sdk/allof/src/core/headers.ts | 33 - seed/ts-sdk/allof/src/core/index.ts | 4 - seed/ts-sdk/allof/src/core/json.ts | 27 - seed/ts-sdk/allof/src/core/logging/exports.ts | 19 - seed/ts-sdk/allof/src/core/logging/index.ts | 1 - seed/ts-sdk/allof/src/core/logging/logger.ts | 203 - seed/ts-sdk/allof/src/core/runtime/index.ts | 1 - seed/ts-sdk/allof/src/core/runtime/runtime.ts | 134 - .../allof/src/core/url/encodePathParam.ts | 18 - seed/ts-sdk/allof/src/core/url/index.ts | 3 - seed/ts-sdk/allof/src/core/url/join.ts | 79 - seed/ts-sdk/allof/src/core/url/qs.ts | 74 - seed/ts-sdk/allof/src/environments.ts | 7 - seed/ts-sdk/allof/src/errors/SeedApiError.ts | 64 - .../allof/src/errors/SeedApiTimeoutError.ts | 18 - .../src/errors/handleNonStatusCodeError.ts | 40 - seed/ts-sdk/allof/src/errors/index.ts | 2 - seed/ts-sdk/allof/src/exports.ts | 1 - seed/ts-sdk/allof/src/index.ts | 6 - seed/ts-sdk/allof/src/version.ts | 1 - seed/ts-sdk/allof/tests/custom.test.ts | 13 - .../allof/tests/mock-server/MockServer.ts | 29 - .../allof/tests/mock-server/MockServerPool.ts | 106 - .../tests/mock-server/mockEndpointBuilder.ts | 234 - .../allof/tests/mock-server/randomBaseUrl.ts | 4 - seed/ts-sdk/allof/tests/mock-server/setup.ts | 10 - .../tests/mock-server/withFormUrlEncoded.ts | 104 - .../allof/tests/mock-server/withHeaders.ts | 70 - .../allof/tests/mock-server/withJson.ts | 173 - seed/ts-sdk/allof/tests/setup.ts | 80 - seed/ts-sdk/allof/tests/tsconfig.json | 10 - .../allof/tests/unit/fetcher/Fetcher.test.ts | 262 - .../unit/fetcher/HttpResponsePromise.test.ts | 143 - .../tests/unit/fetcher/RawResponse.test.ts | 34 - .../unit/fetcher/createRequestUrl.test.ts | 163 - .../tests/unit/fetcher/getRequestBody.test.ts | 129 - .../unit/fetcher/getResponseBody.test.ts | 97 - .../allof/tests/unit/fetcher/logging.test.ts | 517 -- .../fetcher/makePassthroughRequest.test.ts | 398 -- .../tests/unit/fetcher/makeRequest.test.ts | 158 - .../tests/unit/fetcher/redacting.test.ts | 1115 ---- .../unit/fetcher/requestWithRetries.test.ts | 230 - .../allof/tests/unit/fetcher/signals.test.ts | 69 - .../allof/tests/unit/fetcher/test-file.txt | 1 - .../allof/tests/unit/logging/logger.test.ts | 454 -- seed/ts-sdk/allof/tests/unit/url/join.test.ts | 284 - seed/ts-sdk/allof/tests/unit/url/qs.test.ts | 278 - seed/ts-sdk/allof/tests/wire/.gitkeep | 0 seed/ts-sdk/allof/tests/wire/main.test.ts | 91 - seed/ts-sdk/allof/tsconfig.base.json | 17 - seed/ts-sdk/allof/tsconfig.cjs.json | 9 - seed/ts-sdk/allof/tsconfig.esm.json | 10 - seed/ts-sdk/allof/tsconfig.json | 3 - seed/ts-sdk/allof/vitest.config.mts | 32 - 1862 files changed, 174064 deletions(-) delete mode 100644 seed/csharp-model/allof-inline/.editorconfig delete mode 100644 seed/csharp-model/allof-inline/.fern/metadata.json delete mode 100644 seed/csharp-model/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/csharp-model/allof-inline/.gitignore delete mode 100644 seed/csharp-model/allof-inline/SeedApi.slnx delete mode 100644 seed/csharp-model/allof-inline/snippet.json delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Describable.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/Organization.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/User.cs delete mode 100644 seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs delete mode 100644 seed/csharp-model/allof/.editorconfig delete mode 100644 seed/csharp-model/allof/.fern/metadata.json delete mode 100644 seed/csharp-model/allof/.github/workflows/ci.yml delete mode 100644 seed/csharp-model/allof/.gitignore delete mode 100644 seed/csharp-model/allof/SeedApi.slnx delete mode 100644 seed/csharp-model/allof/snippet.json delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/AuditInfo.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/BaseOrg.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Constants.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Optional.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Describable.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Identifiable.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/Organization.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/PagingCursors.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/RuleResponse.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/RuleType.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props delete mode 100644 seed/csharp-model/allof/src/SeedApi/SeedApi.csproj delete mode 100644 seed/csharp-model/allof/src/SeedApi/User.cs delete mode 100644 seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs delete mode 100644 seed/csharp-sdk/allof-inline/.editorconfig delete mode 100644 seed/csharp-sdk/allof-inline/.fern/metadata.json delete mode 100644 seed/csharp-sdk/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/csharp-sdk/allof-inline/.gitignore delete mode 100644 seed/csharp-sdk/allof-inline/README.md delete mode 100644 seed/csharp-sdk/allof-inline/SeedApi.slnx delete mode 100644 seed/csharp-sdk/allof-inline/reference.md delete mode 100644 seed/csharp-sdk/allof-inline/snippet.json delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs delete mode 100644 seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs delete mode 100644 seed/csharp-sdk/allof/.editorconfig delete mode 100644 seed/csharp-sdk/allof/.fern/metadata.json delete mode 100644 seed/csharp-sdk/allof/.github/workflows/ci.yml delete mode 100644 seed/csharp-sdk/allof/.gitignore delete mode 100644 seed/csharp-sdk/allof/README.md delete mode 100644 seed/csharp-sdk/allof/SeedApi.slnx delete mode 100644 seed/csharp-sdk/allof/reference.md delete mode 100644 seed/csharp-sdk/allof/snippet.json delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/User.cs delete mode 100644 seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs delete mode 100644 seed/go-model/allof-inline/.fern/metadata.json delete mode 100644 seed/go-model/allof-inline/doc.go delete mode 100644 seed/go-model/allof-inline/go.mod delete mode 100644 seed/go-model/allof-inline/go.sum delete mode 100644 seed/go-model/allof-inline/internal/extra_properties.go delete mode 100644 seed/go-model/allof-inline/internal/extra_properties_test.go delete mode 100644 seed/go-model/allof-inline/internal/stringer.go delete mode 100644 seed/go-model/allof-inline/internal/time.go delete mode 100644 seed/go-model/allof-inline/snippet.json delete mode 100644 seed/go-model/allof-inline/types.go delete mode 100644 seed/go-model/allof/.fern/metadata.json delete mode 100644 seed/go-model/allof/doc.go delete mode 100644 seed/go-model/allof/go.mod delete mode 100644 seed/go-model/allof/go.sum delete mode 100644 seed/go-model/allof/internal/extra_properties.go delete mode 100644 seed/go-model/allof/internal/extra_properties_test.go delete mode 100644 seed/go-model/allof/internal/stringer.go delete mode 100644 seed/go-model/allof/internal/time.go delete mode 100644 seed/go-model/allof/snippet.json delete mode 100644 seed/go-model/allof/types.go delete mode 100644 seed/go-sdk/allof-inline/.fern/metadata.json delete mode 100644 seed/go-sdk/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/go-sdk/allof-inline/README.md delete mode 100644 seed/go-sdk/allof-inline/client/client.go delete mode 100644 seed/go-sdk/allof-inline/client/client_test.go delete mode 100644 seed/go-sdk/allof-inline/client/raw_client.go delete mode 100644 seed/go-sdk/allof-inline/core/api_error.go delete mode 100644 seed/go-sdk/allof-inline/core/http.go delete mode 100644 seed/go-sdk/allof-inline/core/request_option.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go delete mode 100644 seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go delete mode 100644 seed/go-sdk/allof-inline/environments.go delete mode 100644 seed/go-sdk/allof-inline/error_codes.go delete mode 100644 seed/go-sdk/allof-inline/file_param.go delete mode 100644 seed/go-sdk/allof-inline/go.mod delete mode 100644 seed/go-sdk/allof-inline/go.sum delete mode 100644 seed/go-sdk/allof-inline/internal/caller.go delete mode 100644 seed/go-sdk/allof-inline/internal/caller_test.go delete mode 100644 seed/go-sdk/allof-inline/internal/error_decoder.go delete mode 100644 seed/go-sdk/allof-inline/internal/error_decoder_test.go delete mode 100644 seed/go-sdk/allof-inline/internal/explicit_fields.go delete mode 100644 seed/go-sdk/allof-inline/internal/explicit_fields_test.go delete mode 100644 seed/go-sdk/allof-inline/internal/extra_properties.go delete mode 100644 seed/go-sdk/allof-inline/internal/extra_properties_test.go delete mode 100644 seed/go-sdk/allof-inline/internal/http.go delete mode 100644 seed/go-sdk/allof-inline/internal/query.go delete mode 100644 seed/go-sdk/allof-inline/internal/query_test.go delete mode 100644 seed/go-sdk/allof-inline/internal/retrier.go delete mode 100644 seed/go-sdk/allof-inline/internal/retrier_test.go delete mode 100644 seed/go-sdk/allof-inline/internal/stringer.go delete mode 100644 seed/go-sdk/allof-inline/internal/time.go delete mode 100644 seed/go-sdk/allof-inline/option/request_option.go delete mode 100644 seed/go-sdk/allof-inline/pointer.go delete mode 100644 seed/go-sdk/allof-inline/pointer_test.go delete mode 100644 seed/go-sdk/allof-inline/reference.md delete mode 100644 seed/go-sdk/allof-inline/snippet.json delete mode 100644 seed/go-sdk/allof-inline/types.go delete mode 100644 seed/go-sdk/allof-inline/types_test.go delete mode 100644 seed/go-sdk/allof/.fern/metadata.json delete mode 100644 seed/go-sdk/allof/.github/workflows/ci.yml delete mode 100644 seed/go-sdk/allof/README.md delete mode 100644 seed/go-sdk/allof/client/client.go delete mode 100644 seed/go-sdk/allof/client/client_test.go delete mode 100644 seed/go-sdk/allof/client/raw_client.go delete mode 100644 seed/go-sdk/allof/core/api_error.go delete mode 100644 seed/go-sdk/allof/core/http.go delete mode 100644 seed/go-sdk/allof/core/request_option.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example0/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example1/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example2/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example3/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example4/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example5/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example6/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example7/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example8/snippet.go delete mode 100644 seed/go-sdk/allof/dynamic-snippets/example9/snippet.go delete mode 100644 seed/go-sdk/allof/environments.go delete mode 100644 seed/go-sdk/allof/error_codes.go delete mode 100644 seed/go-sdk/allof/file_param.go delete mode 100644 seed/go-sdk/allof/go.mod delete mode 100644 seed/go-sdk/allof/go.sum delete mode 100644 seed/go-sdk/allof/internal/caller.go delete mode 100644 seed/go-sdk/allof/internal/caller_test.go delete mode 100644 seed/go-sdk/allof/internal/error_decoder.go delete mode 100644 seed/go-sdk/allof/internal/error_decoder_test.go delete mode 100644 seed/go-sdk/allof/internal/explicit_fields.go delete mode 100644 seed/go-sdk/allof/internal/explicit_fields_test.go delete mode 100644 seed/go-sdk/allof/internal/extra_properties.go delete mode 100644 seed/go-sdk/allof/internal/extra_properties_test.go delete mode 100644 seed/go-sdk/allof/internal/http.go delete mode 100644 seed/go-sdk/allof/internal/query.go delete mode 100644 seed/go-sdk/allof/internal/query_test.go delete mode 100644 seed/go-sdk/allof/internal/retrier.go delete mode 100644 seed/go-sdk/allof/internal/retrier_test.go delete mode 100644 seed/go-sdk/allof/internal/stringer.go delete mode 100644 seed/go-sdk/allof/internal/time.go delete mode 100644 seed/go-sdk/allof/option/request_option.go delete mode 100644 seed/go-sdk/allof/pointer.go delete mode 100644 seed/go-sdk/allof/pointer_test.go delete mode 100644 seed/go-sdk/allof/reference.md delete mode 100644 seed/go-sdk/allof/snippet.json delete mode 100644 seed/go-sdk/allof/types.go delete mode 100644 seed/go-sdk/allof/types_test.go delete mode 100644 seed/java-model/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/java-model/allof-inline/.gitignore delete mode 100644 seed/java-model/allof-inline/build.gradle delete mode 100644 seed/java-model/allof-inline/settings.gradle delete mode 100644 seed/java-model/allof-inline/snippet.json delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java delete mode 100644 seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java delete mode 100644 seed/java-model/allof/.github/workflows/ci.yml delete mode 100644 seed/java-model/allof/.gitignore delete mode 100644 seed/java-model/allof/build.gradle delete mode 100644 seed/java-model/allof/settings.gradle delete mode 100644 seed/java-model/allof/snippet.json delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/User.java delete mode 100644 seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java delete mode 100644 seed/java-sdk/allof-inline/.fern/metadata.json delete mode 100644 seed/java-sdk/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/java-sdk/allof-inline/.gitignore delete mode 100644 seed/java-sdk/allof-inline/README.md delete mode 100644 seed/java-sdk/allof-inline/build.gradle delete mode 100644 seed/java-sdk/allof-inline/reference.md delete mode 100644 seed/java-sdk/allof-inline/sample-app/build.gradle delete mode 100644 seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java delete mode 100644 seed/java-sdk/allof-inline/settings.gradle delete mode 100644 seed/java-sdk/allof-inline/snippet.json delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java delete mode 100644 seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java delete mode 100644 seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java delete mode 100644 seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java delete mode 100644 seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java delete mode 100644 seed/java-sdk/allof/.fern/metadata.json delete mode 100644 seed/java-sdk/allof/.github/workflows/ci.yml delete mode 100644 seed/java-sdk/allof/.gitignore delete mode 100644 seed/java-sdk/allof/README.md delete mode 100644 seed/java-sdk/allof/build.gradle delete mode 100644 seed/java-sdk/allof/reference.md delete mode 100644 seed/java-sdk/allof/sample-app/build.gradle delete mode 100644 seed/java-sdk/allof/sample-app/src/main/java/sample/App.java delete mode 100644 seed/java-sdk/allof/settings.gradle delete mode 100644 seed/java-sdk/allof/snippet.json delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example0.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example1.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example2.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example3.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example4.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example5.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example6.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example7.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example8.java delete mode 100644 seed/java-sdk/allof/src/main/java/com/snippets/Example9.java delete mode 100644 seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java delete mode 100644 seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java delete mode 100644 seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java delete mode 100644 seed/openapi/allof-inline/openapi.yml delete mode 100644 seed/openapi/allof-inline/snippet.json delete mode 100644 seed/openapi/allof/openapi.yml delete mode 100644 seed/openapi/allof/snippet.json delete mode 100644 seed/php-model/allof-inline/.fern/metadata.json delete mode 100644 seed/php-model/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/php-model/allof-inline/.gitignore delete mode 100644 seed/php-model/allof-inline/composer.json delete mode 100644 seed/php-model/allof-inline/phpstan.neon delete mode 100644 seed/php-model/allof-inline/phpunit.xml delete mode 100644 seed/php-model/allof-inline/snippet.json delete mode 100644 seed/php-model/allof-inline/src/AuditInfo.php delete mode 100644 seed/php-model/allof-inline/src/BaseOrg.php delete mode 100644 seed/php-model/allof-inline/src/BaseOrgMetadata.php delete mode 100644 seed/php-model/allof-inline/src/CombinedEntity.php delete mode 100644 seed/php-model/allof-inline/src/CombinedEntityStatus.php delete mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonDecoder.php delete mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php delete mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php delete mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonProperty.php delete mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonSerializableType.php delete mode 100644 seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php delete mode 100644 seed/php-model/allof-inline/src/Core/Json/Utils.php delete mode 100644 seed/php-model/allof-inline/src/Core/Types/ArrayType.php delete mode 100644 seed/php-model/allof-inline/src/Core/Types/Constant.php delete mode 100644 seed/php-model/allof-inline/src/Core/Types/Date.php delete mode 100644 seed/php-model/allof-inline/src/Core/Types/Union.php delete mode 100644 seed/php-model/allof-inline/src/Describable.php delete mode 100644 seed/php-model/allof-inline/src/DetailedOrg.php delete mode 100644 seed/php-model/allof-inline/src/DetailedOrgMetadata.php delete mode 100644 seed/php-model/allof-inline/src/Identifiable.php delete mode 100644 seed/php-model/allof-inline/src/Organization.php delete mode 100644 seed/php-model/allof-inline/src/OrganizationMetadata.php delete mode 100644 seed/php-model/allof-inline/src/PaginatedResult.php delete mode 100644 seed/php-model/allof-inline/src/PagingCursors.php delete mode 100644 seed/php-model/allof-inline/src/RuleExecutionContext.php delete mode 100644 seed/php-model/allof-inline/src/RuleResponse.php delete mode 100644 seed/php-model/allof-inline/src/RuleResponseStatus.php delete mode 100644 seed/php-model/allof-inline/src/RuleType.php delete mode 100644 seed/php-model/allof-inline/src/RuleTypeSearchResponse.php delete mode 100644 seed/php-model/allof-inline/src/User.php delete mode 100644 seed/php-model/allof-inline/src/UserSearchResponse.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/EnumTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/TraitTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php delete mode 100644 seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php delete mode 100644 seed/php-model/allof/.fern/metadata.json delete mode 100644 seed/php-model/allof/.github/workflows/ci.yml delete mode 100644 seed/php-model/allof/.gitignore delete mode 100644 seed/php-model/allof/composer.json delete mode 100644 seed/php-model/allof/phpstan.neon delete mode 100644 seed/php-model/allof/phpunit.xml delete mode 100644 seed/php-model/allof/snippet.json delete mode 100644 seed/php-model/allof/src/AuditInfo.php delete mode 100644 seed/php-model/allof/src/BaseOrg.php delete mode 100644 seed/php-model/allof/src/BaseOrgMetadata.php delete mode 100644 seed/php-model/allof/src/CombinedEntity.php delete mode 100644 seed/php-model/allof/src/CombinedEntityStatus.php delete mode 100644 seed/php-model/allof/src/Core/Json/JsonDecoder.php delete mode 100644 seed/php-model/allof/src/Core/Json/JsonDeserializer.php delete mode 100644 seed/php-model/allof/src/Core/Json/JsonEncoder.php delete mode 100644 seed/php-model/allof/src/Core/Json/JsonProperty.php delete mode 100644 seed/php-model/allof/src/Core/Json/JsonSerializableType.php delete mode 100644 seed/php-model/allof/src/Core/Json/JsonSerializer.php delete mode 100644 seed/php-model/allof/src/Core/Json/Utils.php delete mode 100644 seed/php-model/allof/src/Core/Types/ArrayType.php delete mode 100644 seed/php-model/allof/src/Core/Types/Constant.php delete mode 100644 seed/php-model/allof/src/Core/Types/Date.php delete mode 100644 seed/php-model/allof/src/Core/Types/Union.php delete mode 100644 seed/php-model/allof/src/Describable.php delete mode 100644 seed/php-model/allof/src/DetailedOrg.php delete mode 100644 seed/php-model/allof/src/DetailedOrgMetadata.php delete mode 100644 seed/php-model/allof/src/Identifiable.php delete mode 100644 seed/php-model/allof/src/Organization.php delete mode 100644 seed/php-model/allof/src/PaginatedResult.php delete mode 100644 seed/php-model/allof/src/PagingCursors.php delete mode 100644 seed/php-model/allof/src/RuleExecutionContext.php delete mode 100644 seed/php-model/allof/src/RuleResponse.php delete mode 100644 seed/php-model/allof/src/RuleResponseStatus.php delete mode 100644 seed/php-model/allof/src/RuleType.php delete mode 100644 seed/php-model/allof/src/RuleTypeSearchResponse.php delete mode 100644 seed/php-model/allof/src/Traits/AuditInfo.php delete mode 100644 seed/php-model/allof/src/User.php delete mode 100644 seed/php-model/allof/src/UserSearchResponse.php delete mode 100644 seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/DateArrayTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/EnumTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/InvalidTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/NullPropertyTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/NullableArrayTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/ScalarTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/TraitTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/UnionArrayTest.php delete mode 100644 seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php delete mode 100644 seed/php-sdk/allof-inline/.fern/metadata.json delete mode 100644 seed/php-sdk/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/php-sdk/allof-inline/.gitignore delete mode 100644 seed/php-sdk/allof-inline/README.md delete mode 100644 seed/php-sdk/allof-inline/composer.json delete mode 100644 seed/php-sdk/allof-inline/phpstan.neon delete mode 100644 seed/php-sdk/allof-inline/phpunit.xml delete mode 100644 seed/php-sdk/allof-inline/reference.md delete mode 100644 seed/php-sdk/allof-inline/snippet.json delete mode 100644 seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Client/HttpMethod.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Client/MockHttpClient.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonProperty.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonSerializableType.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Types/Constant.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Types/Date.php delete mode 100644 seed/php-sdk/allof-inline/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/allof-inline/src/Environments.php delete mode 100644 seed/php-sdk/allof-inline/src/Exceptions/SeedApiException.php delete mode 100644 seed/php-sdk/allof-inline/src/Exceptions/SeedException.php delete mode 100644 seed/php-sdk/allof-inline/src/Requests/RuleCreateRequest.php delete mode 100644 seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php delete mode 100644 seed/php-sdk/allof-inline/src/SeedClient.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/AuditInfo.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/BaseOrg.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/CombinedEntity.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/Describable.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/DetailedOrg.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/Identifiable.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/Organization.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/PaginatedResult.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/PagingCursors.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/RuleResponse.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/RuleType.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/User.php delete mode 100644 seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php delete mode 100644 seed/php-sdk/allof-inline/src/Utils/File.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php delete mode 100644 seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php delete mode 100644 seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php delete mode 100644 seed/php-sdk/allof/.fern/metadata.json delete mode 100644 seed/php-sdk/allof/.github/workflows/ci.yml delete mode 100644 seed/php-sdk/allof/.gitignore delete mode 100644 seed/php-sdk/allof/README.md delete mode 100644 seed/php-sdk/allof/composer.json delete mode 100644 seed/php-sdk/allof/phpstan.neon delete mode 100644 seed/php-sdk/allof/phpunit.xml delete mode 100644 seed/php-sdk/allof/reference.md delete mode 100644 seed/php-sdk/allof/snippet.json delete mode 100644 seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php delete mode 100644 seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php delete mode 100644 seed/php-sdk/allof/src/Core/Client/HttpMethod.php delete mode 100644 seed/php-sdk/allof/src/Core/Client/MockHttpClient.php delete mode 100644 seed/php-sdk/allof/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/JsonDecoder.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/JsonEncoder.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/JsonProperty.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/JsonSerializableType.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/JsonSerializer.php delete mode 100644 seed/php-sdk/allof/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php delete mode 100644 seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php delete mode 100644 seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php delete mode 100644 seed/php-sdk/allof/src/Core/Types/ArrayType.php delete mode 100644 seed/php-sdk/allof/src/Core/Types/Constant.php delete mode 100644 seed/php-sdk/allof/src/Core/Types/Date.php delete mode 100644 seed/php-sdk/allof/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/allof/src/Environments.php delete mode 100644 seed/php-sdk/allof/src/Exceptions/SeedApiException.php delete mode 100644 seed/php-sdk/allof/src/Exceptions/SeedException.php delete mode 100644 seed/php-sdk/allof/src/Requests/RuleCreateRequest.php delete mode 100644 seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php delete mode 100644 seed/php-sdk/allof/src/SeedClient.php delete mode 100644 seed/php-sdk/allof/src/Traits/AuditInfo.php delete mode 100644 seed/php-sdk/allof/src/Types/AuditInfo.php delete mode 100644 seed/php-sdk/allof/src/Types/BaseOrg.php delete mode 100644 seed/php-sdk/allof/src/Types/BaseOrgMetadata.php delete mode 100644 seed/php-sdk/allof/src/Types/CombinedEntity.php delete mode 100644 seed/php-sdk/allof/src/Types/CombinedEntityStatus.php delete mode 100644 seed/php-sdk/allof/src/Types/Describable.php delete mode 100644 seed/php-sdk/allof/src/Types/DetailedOrg.php delete mode 100644 seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php delete mode 100644 seed/php-sdk/allof/src/Types/Identifiable.php delete mode 100644 seed/php-sdk/allof/src/Types/Organization.php delete mode 100644 seed/php-sdk/allof/src/Types/PaginatedResult.php delete mode 100644 seed/php-sdk/allof/src/Types/PagingCursors.php delete mode 100644 seed/php-sdk/allof/src/Types/RuleExecutionContext.php delete mode 100644 seed/php-sdk/allof/src/Types/RuleResponse.php delete mode 100644 seed/php-sdk/allof/src/Types/RuleResponseStatus.php delete mode 100644 seed/php-sdk/allof/src/Types/RuleType.php delete mode 100644 seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php delete mode 100644 seed/php-sdk/allof/src/Types/User.php delete mode 100644 seed/php-sdk/allof/src/Types/UserSearchResponse.php delete mode 100644 seed/php-sdk/allof/src/Utils/File.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php delete mode 100644 seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php delete mode 100644 seed/php-sdk/allof/tests/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/EnumTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/InvalidTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/ScalarTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/TraitTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php delete mode 100644 seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php delete mode 100644 seed/pydantic/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/pydantic/allof-inline/.gitignore delete mode 100644 seed/pydantic/allof-inline/README.md delete mode 100644 seed/pydantic/allof-inline/poetry.lock delete mode 100644 seed/pydantic/allof-inline/pyproject.toml delete mode 100644 seed/pydantic/allof-inline/requirements.txt delete mode 100644 seed/pydantic/allof-inline/snippet.json delete mode 100644 seed/pydantic/allof-inline/src/seed/api/__init__.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/audit_info.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/base_org.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/combined_entity.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/core/__init__.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/core/enum.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/core/serialization.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/describable.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/detailed_org.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/identifiable.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/organization.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/organization_metadata.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/paginated_result.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/paging_cursors.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/py.typed delete mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_response.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_response_status.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_type.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/user.py delete mode 100644 seed/pydantic/allof-inline/src/seed/api/user_search_response.py delete mode 100644 seed/pydantic/allof-inline/tests/custom/test_client.py delete mode 100644 seed/pydantic/allof/.github/workflows/ci.yml delete mode 100644 seed/pydantic/allof/.gitignore delete mode 100644 seed/pydantic/allof/README.md delete mode 100644 seed/pydantic/allof/poetry.lock delete mode 100644 seed/pydantic/allof/pyproject.toml delete mode 100644 seed/pydantic/allof/requirements.txt delete mode 100644 seed/pydantic/allof/snippet.json delete mode 100644 seed/pydantic/allof/src/seed/api/__init__.py delete mode 100644 seed/pydantic/allof/src/seed/api/audit_info.py delete mode 100644 seed/pydantic/allof/src/seed/api/base_org.py delete mode 100644 seed/pydantic/allof/src/seed/api/base_org_metadata.py delete mode 100644 seed/pydantic/allof/src/seed/api/combined_entity.py delete mode 100644 seed/pydantic/allof/src/seed/api/combined_entity_status.py delete mode 100644 seed/pydantic/allof/src/seed/api/core/__init__.py delete mode 100644 seed/pydantic/allof/src/seed/api/core/datetime_utils.py delete mode 100644 seed/pydantic/allof/src/seed/api/core/enum.py delete mode 100644 seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py delete mode 100644 seed/pydantic/allof/src/seed/api/core/serialization.py delete mode 100644 seed/pydantic/allof/src/seed/api/describable.py delete mode 100644 seed/pydantic/allof/src/seed/api/detailed_org.py delete mode 100644 seed/pydantic/allof/src/seed/api/detailed_org_metadata.py delete mode 100644 seed/pydantic/allof/src/seed/api/identifiable.py delete mode 100644 seed/pydantic/allof/src/seed/api/organization.py delete mode 100644 seed/pydantic/allof/src/seed/api/paginated_result.py delete mode 100644 seed/pydantic/allof/src/seed/api/paging_cursors.py delete mode 100644 seed/pydantic/allof/src/seed/api/py.typed delete mode 100644 seed/pydantic/allof/src/seed/api/rule_execution_context.py delete mode 100644 seed/pydantic/allof/src/seed/api/rule_response.py delete mode 100644 seed/pydantic/allof/src/seed/api/rule_response_status.py delete mode 100644 seed/pydantic/allof/src/seed/api/rule_type.py delete mode 100644 seed/pydantic/allof/src/seed/api/rule_type_search_response.py delete mode 100644 seed/pydantic/allof/src/seed/api/user.py delete mode 100644 seed/pydantic/allof/src/seed/api/user_search_response.py delete mode 100644 seed/pydantic/allof/tests/custom/test_client.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/.gitignore delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/README.md delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/poetry.lock delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/pyproject.toml delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/reference.md delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/requirements.txt delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/snippet.json delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/py.typed delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/wire/__init__.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml delete mode 100644 seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json delete mode 100644 seed/python-sdk/allof/no-custom-config/.fern/metadata.json delete mode 100644 seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml delete mode 100644 seed/python-sdk/allof/no-custom-config/.gitignore delete mode 100644 seed/python-sdk/allof/no-custom-config/README.md delete mode 100644 seed/python-sdk/allof/no-custom-config/poetry.lock delete mode 100644 seed/python-sdk/allof/no-custom-config/pyproject.toml delete mode 100644 seed/python-sdk/allof/no-custom-config/reference.md delete mode 100644 seed/python-sdk/allof/no-custom-config/requirements.txt delete mode 100644 seed/python-sdk/allof/no-custom-config/snippet.json delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/__init__.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/client.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/file.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/environment.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/py.typed delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/user.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py delete mode 100644 seed/python-sdk/allof/no-custom-config/src/seed/version.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/conftest.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/wire/__init__.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py delete mode 100644 seed/python-sdk/allof/no-custom-config/tests/wire/test_.py delete mode 100644 seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml delete mode 100644 seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json delete mode 100644 seed/ruby-sdk-v2/allof-inline/.fern/metadata.json delete mode 100644 seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/ruby-sdk-v2/allof-inline/.gitignore delete mode 100644 seed/ruby-sdk-v2/allof-inline/.rubocop.yml delete mode 100644 seed/ruby-sdk-v2/allof-inline/Gemfile delete mode 100644 seed/ruby-sdk-v2/allof-inline/Gemfile.custom delete mode 100644 seed/ruby-sdk-v2/allof-inline/README.md delete mode 100644 seed/ruby-sdk-v2/allof-inline/Rakefile delete mode 100644 seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/reference.md delete mode 100644 seed/ruby-sdk-v2/allof-inline/seed.gemspec delete mode 100644 seed/ruby-sdk-v2/allof-inline/snippet.json delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/custom.test.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/test_helper.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb delete mode 100644 seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb delete mode 100644 seed/ruby-sdk-v2/allof/.fern/metadata.json delete mode 100644 seed/ruby-sdk-v2/allof/.github/workflows/ci.yml delete mode 100644 seed/ruby-sdk-v2/allof/.gitignore delete mode 100644 seed/ruby-sdk-v2/allof/.rubocop.yml delete mode 100644 seed/ruby-sdk-v2/allof/Gemfile delete mode 100644 seed/ruby-sdk-v2/allof/Gemfile.custom delete mode 100644 seed/ruby-sdk-v2/allof/README.md delete mode 100644 seed/ruby-sdk-v2/allof/Rakefile delete mode 100644 seed/ruby-sdk-v2/allof/custom.gemspec.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/environment.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/user.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb delete mode 100644 seed/ruby-sdk-v2/allof/lib/seed/version.rb delete mode 100644 seed/ruby-sdk-v2/allof/reference.md delete mode 100644 seed/ruby-sdk-v2/allof/seed.gemspec delete mode 100644 seed/ruby-sdk-v2/allof/snippet.json delete mode 100644 seed/ruby-sdk-v2/allof/test/custom.test.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/test_helper.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb delete mode 100644 seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb delete mode 100644 seed/rust-model/allof-inline/.fern/metadata.json delete mode 100644 seed/rust-model/allof-inline/snippet.json delete mode 100644 seed/rust-model/allof-inline/src/audit_info.rs delete mode 100644 seed/rust-model/allof-inline/src/base_org.rs delete mode 100644 seed/rust-model/allof-inline/src/base_org_metadata.rs delete mode 100644 seed/rust-model/allof-inline/src/combined_entity.rs delete mode 100644 seed/rust-model/allof-inline/src/combined_entity_status.rs delete mode 100644 seed/rust-model/allof-inline/src/describable.rs delete mode 100644 seed/rust-model/allof-inline/src/detailed_org.rs delete mode 100644 seed/rust-model/allof-inline/src/detailed_org_metadata.rs delete mode 100644 seed/rust-model/allof-inline/src/error.rs delete mode 100644 seed/rust-model/allof-inline/src/identifiable.rs delete mode 100644 seed/rust-model/allof-inline/src/lib.rs delete mode 100644 seed/rust-model/allof-inline/src/mod.rs delete mode 100644 seed/rust-model/allof-inline/src/organization.rs delete mode 100644 seed/rust-model/allof-inline/src/organization_metadata.rs delete mode 100644 seed/rust-model/allof-inline/src/paginated_result.rs delete mode 100644 seed/rust-model/allof-inline/src/paging_cursors.rs delete mode 100644 seed/rust-model/allof-inline/src/rule_create_request.rs delete mode 100644 seed/rust-model/allof-inline/src/rule_execution_context.rs delete mode 100644 seed/rust-model/allof-inline/src/rule_response.rs delete mode 100644 seed/rust-model/allof-inline/src/rule_response_status.rs delete mode 100644 seed/rust-model/allof-inline/src/rule_type.rs delete mode 100644 seed/rust-model/allof-inline/src/rule_type_search_response.rs delete mode 100644 seed/rust-model/allof-inline/src/search_rule_types_query_request.rs delete mode 100644 seed/rust-model/allof-inline/src/user.rs delete mode 100644 seed/rust-model/allof-inline/src/user_search_response.rs delete mode 100644 seed/rust-model/allof/.fern/metadata.json delete mode 100644 seed/rust-model/allof/snippet.json delete mode 100644 seed/rust-model/allof/src/audit_info.rs delete mode 100644 seed/rust-model/allof/src/base_org.rs delete mode 100644 seed/rust-model/allof/src/base_org_metadata.rs delete mode 100644 seed/rust-model/allof/src/combined_entity.rs delete mode 100644 seed/rust-model/allof/src/combined_entity_status.rs delete mode 100644 seed/rust-model/allof/src/describable.rs delete mode 100644 seed/rust-model/allof/src/detailed_org.rs delete mode 100644 seed/rust-model/allof/src/detailed_org_metadata.rs delete mode 100644 seed/rust-model/allof/src/error.rs delete mode 100644 seed/rust-model/allof/src/identifiable.rs delete mode 100644 seed/rust-model/allof/src/lib.rs delete mode 100644 seed/rust-model/allof/src/mod.rs delete mode 100644 seed/rust-model/allof/src/organization.rs delete mode 100644 seed/rust-model/allof/src/paginated_result.rs delete mode 100644 seed/rust-model/allof/src/paging_cursors.rs delete mode 100644 seed/rust-model/allof/src/rule_create_request.rs delete mode 100644 seed/rust-model/allof/src/rule_execution_context.rs delete mode 100644 seed/rust-model/allof/src/rule_response.rs delete mode 100644 seed/rust-model/allof/src/rule_response_status.rs delete mode 100644 seed/rust-model/allof/src/rule_type.rs delete mode 100644 seed/rust-model/allof/src/rule_type_search_response.rs delete mode 100644 seed/rust-model/allof/src/search_rule_types_query_request.rs delete mode 100644 seed/rust-model/allof/src/user.rs delete mode 100644 seed/rust-model/allof/src/user_search_response.rs delete mode 100644 seed/rust-sdk/allof-inline/.fern/metadata.json delete mode 100644 seed/rust-sdk/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/rust-sdk/allof-inline/.gitignore delete mode 100644 seed/rust-sdk/allof-inline/Cargo.toml delete mode 100644 seed/rust-sdk/allof-inline/README.md delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs delete mode 100644 seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs delete mode 100644 seed/rust-sdk/allof-inline/reference.md delete mode 100644 seed/rust-sdk/allof-inline/rustfmt.toml delete mode 100644 seed/rust-sdk/allof-inline/snippet.json delete mode 100644 seed/rust-sdk/allof-inline/src/api/mod.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/resources/mod.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/audit_info.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/base_org.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/describable.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/identifiable.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/mod.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/organization.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_response.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_type.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/user.rs delete mode 100644 seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs delete mode 100644 seed/rust-sdk/allof-inline/src/client.rs delete mode 100644 seed/rust-sdk/allof-inline/src/config.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/http_client.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/mod.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/pagination.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/request_options.rs delete mode 100644 seed/rust-sdk/allof-inline/src/core/utils.rs delete mode 100644 seed/rust-sdk/allof-inline/src/environment.rs delete mode 100644 seed/rust-sdk/allof-inline/src/error.rs delete mode 100644 seed/rust-sdk/allof-inline/src/lib.rs delete mode 100644 seed/rust-sdk/allof-inline/src/prelude.rs delete mode 100644 seed/rust-sdk/allof/.fern/metadata.json delete mode 100644 seed/rust-sdk/allof/.github/workflows/ci.yml delete mode 100644 seed/rust-sdk/allof/.gitignore delete mode 100644 seed/rust-sdk/allof/Cargo.toml delete mode 100644 seed/rust-sdk/allof/README.md delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example0.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example1.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example2.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example3.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example4.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example5.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example6.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example7.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example8.rs delete mode 100644 seed/rust-sdk/allof/dynamic-snippets/example9.rs delete mode 100644 seed/rust-sdk/allof/reference.md delete mode 100644 seed/rust-sdk/allof/rustfmt.toml delete mode 100644 seed/rust-sdk/allof/snippet.json delete mode 100644 seed/rust-sdk/allof/src/api/mod.rs delete mode 100644 seed/rust-sdk/allof/src/api/resources/mod.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/audit_info.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/base_org.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/base_org_metadata.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/combined_entity.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/combined_entity_status.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/describable.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/detailed_org.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/identifiable.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/mod.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/organization.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/paginated_result.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/paging_cursors.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/rule_create_request.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/rule_execution_context.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/rule_response.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/rule_response_status.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/rule_type.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/user.rs delete mode 100644 seed/rust-sdk/allof/src/api/types/user_search_response.rs delete mode 100644 seed/rust-sdk/allof/src/client.rs delete mode 100644 seed/rust-sdk/allof/src/config.rs delete mode 100644 seed/rust-sdk/allof/src/core/flexible_datetime.rs delete mode 100644 seed/rust-sdk/allof/src/core/http_client.rs delete mode 100644 seed/rust-sdk/allof/src/core/mod.rs delete mode 100644 seed/rust-sdk/allof/src/core/oauth_token_provider.rs delete mode 100644 seed/rust-sdk/allof/src/core/pagination.rs delete mode 100644 seed/rust-sdk/allof/src/core/query_parameter_builder.rs delete mode 100644 seed/rust-sdk/allof/src/core/request_options.rs delete mode 100644 seed/rust-sdk/allof/src/core/utils.rs delete mode 100644 seed/rust-sdk/allof/src/environment.rs delete mode 100644 seed/rust-sdk/allof/src/error.rs delete mode 100644 seed/rust-sdk/allof/src/lib.rs delete mode 100644 seed/rust-sdk/allof/src/prelude.rs delete mode 100644 seed/swift-sdk/allof-inline/.fern/metadata.json delete mode 100644 seed/swift-sdk/allof-inline/Package.swift delete mode 100644 seed/swift-sdk/allof-inline/README.md delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example0.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example1.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example2.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example3.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example4.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example5.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example6.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example7.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example8.swift delete mode 100644 seed/swift-sdk/allof-inline/Snippets/Example9.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/ApiClient.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/ApiError.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/Networking.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/User.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift delete mode 100644 seed/swift-sdk/allof-inline/Sources/Version.swift delete mode 100644 seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift delete mode 100644 seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift delete mode 100644 seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift delete mode 100644 seed/swift-sdk/allof-inline/reference.md delete mode 100644 seed/swift-sdk/allof-inline/snippet.json delete mode 100644 seed/swift-sdk/allof/.fern/metadata.json delete mode 100644 seed/swift-sdk/allof/Package.swift delete mode 100644 seed/swift-sdk/allof/README.md delete mode 100644 seed/swift-sdk/allof/Snippets/Example0.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example1.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example2.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example3.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example4.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example5.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example6.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example7.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example8.swift delete mode 100644 seed/swift-sdk/allof/Snippets/Example9.swift delete mode 100644 seed/swift-sdk/allof/Sources/ApiClient.swift delete mode 100644 seed/swift-sdk/allof/Sources/ApiEnvironment.swift delete mode 100644 seed/swift-sdk/allof/Sources/ApiError.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Data+String.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift delete mode 100644 seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/CalendarDate.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/ClientConfig.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/FormFile.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/HTTPError.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/Indirect.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/JSONValue.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/Networking.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/Nullable.swift delete mode 100644 seed/swift-sdk/allof/Sources/Public/RequestOptions.swift delete mode 100644 seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift delete mode 100644 seed/swift-sdk/allof/Sources/Requests/Requests.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/Describable.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/Organization.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleType.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/User.swift delete mode 100644 seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift delete mode 100644 seed/swift-sdk/allof/Sources/Version.swift delete mode 100644 seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift delete mode 100644 seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift delete mode 100644 seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift delete mode 100644 seed/swift-sdk/allof/reference.md delete mode 100644 seed/swift-sdk/allof/snippet.json delete mode 100644 seed/ts-sdk/allof-inline/.fern/metadata.json delete mode 100644 seed/ts-sdk/allof-inline/.github/workflows/ci.yml delete mode 100644 seed/ts-sdk/allof-inline/.gitignore delete mode 100644 seed/ts-sdk/allof-inline/CONTRIBUTING.md delete mode 100644 seed/ts-sdk/allof-inline/README.md delete mode 100644 seed/ts-sdk/allof-inline/biome.json delete mode 100644 seed/ts-sdk/allof-inline/package.json delete mode 100644 seed/ts-sdk/allof-inline/pnpm-workspace.yaml delete mode 100644 seed/ts-sdk/allof-inline/reference.md delete mode 100644 seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js delete mode 100644 seed/ts-sdk/allof-inline/snippet.json delete mode 100644 seed/ts-sdk/allof-inline/src/BaseClient.ts delete mode 100644 seed/ts-sdk/allof-inline/src/Client.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/client/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/client/requests/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/Describable.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/Organization.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleType.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/User.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts delete mode 100644 seed/ts-sdk/allof-inline/src/api/types/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/exports.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/headers.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/json.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/logging/exports.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/logging/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/logging/logger.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/runtime/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/url/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/url/join.ts delete mode 100644 seed/ts-sdk/allof-inline/src/core/url/qs.ts delete mode 100644 seed/ts-sdk/allof-inline/src/environments.ts delete mode 100644 seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts delete mode 100644 seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts delete mode 100644 seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts delete mode 100644 seed/ts-sdk/allof-inline/src/errors/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/exports.ts delete mode 100644 seed/ts-sdk/allof-inline/src/index.ts delete mode 100644 seed/ts-sdk/allof-inline/src/version.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/custom.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/setup.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/setup.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/tsconfig.json delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tests/wire/.gitkeep delete mode 100644 seed/ts-sdk/allof-inline/tests/wire/main.test.ts delete mode 100644 seed/ts-sdk/allof-inline/tsconfig.base.json delete mode 100644 seed/ts-sdk/allof-inline/tsconfig.cjs.json delete mode 100644 seed/ts-sdk/allof-inline/tsconfig.esm.json delete mode 100644 seed/ts-sdk/allof-inline/tsconfig.json delete mode 100644 seed/ts-sdk/allof-inline/vitest.config.mts delete mode 100644 seed/ts-sdk/allof/.fern/metadata.json delete mode 100644 seed/ts-sdk/allof/.github/workflows/ci.yml delete mode 100644 seed/ts-sdk/allof/.gitignore delete mode 100644 seed/ts-sdk/allof/CONTRIBUTING.md delete mode 100644 seed/ts-sdk/allof/README.md delete mode 100644 seed/ts-sdk/allof/biome.json delete mode 100644 seed/ts-sdk/allof/package.json delete mode 100644 seed/ts-sdk/allof/pnpm-workspace.yaml delete mode 100644 seed/ts-sdk/allof/reference.md delete mode 100644 seed/ts-sdk/allof/scripts/rename-to-esm-files.js delete mode 100644 seed/ts-sdk/allof/snippet.json delete mode 100644 seed/ts-sdk/allof/src/BaseClient.ts delete mode 100644 seed/ts-sdk/allof/src/Client.ts delete mode 100644 seed/ts-sdk/allof/src/api/client/index.ts delete mode 100644 seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts delete mode 100644 seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts delete mode 100644 seed/ts-sdk/allof/src/api/client/requests/index.ts delete mode 100644 seed/ts-sdk/allof/src/api/index.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/AuditInfo.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/BaseOrg.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/CombinedEntity.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/Describable.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/DetailedOrg.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/Identifiable.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/Organization.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/PaginatedResult.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/PagingCursors.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/RuleResponse.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/RuleType.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/User.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts delete mode 100644 seed/ts-sdk/allof/src/api/types/index.ts delete mode 100644 seed/ts-sdk/allof/src/core/exports.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/Headers.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/Supplier.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/getHeader.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/index.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts delete mode 100644 seed/ts-sdk/allof/src/core/fetcher/signals.ts delete mode 100644 seed/ts-sdk/allof/src/core/headers.ts delete mode 100644 seed/ts-sdk/allof/src/core/index.ts delete mode 100644 seed/ts-sdk/allof/src/core/json.ts delete mode 100644 seed/ts-sdk/allof/src/core/logging/exports.ts delete mode 100644 seed/ts-sdk/allof/src/core/logging/index.ts delete mode 100644 seed/ts-sdk/allof/src/core/logging/logger.ts delete mode 100644 seed/ts-sdk/allof/src/core/runtime/index.ts delete mode 100644 seed/ts-sdk/allof/src/core/runtime/runtime.ts delete mode 100644 seed/ts-sdk/allof/src/core/url/encodePathParam.ts delete mode 100644 seed/ts-sdk/allof/src/core/url/index.ts delete mode 100644 seed/ts-sdk/allof/src/core/url/join.ts delete mode 100644 seed/ts-sdk/allof/src/core/url/qs.ts delete mode 100644 seed/ts-sdk/allof/src/environments.ts delete mode 100644 seed/ts-sdk/allof/src/errors/SeedApiError.ts delete mode 100644 seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts delete mode 100644 seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts delete mode 100644 seed/ts-sdk/allof/src/errors/index.ts delete mode 100644 seed/ts-sdk/allof/src/exports.ts delete mode 100644 seed/ts-sdk/allof/src/index.ts delete mode 100644 seed/ts-sdk/allof/src/version.ts delete mode 100644 seed/ts-sdk/allof/tests/custom.test.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/MockServer.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/setup.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/withHeaders.ts delete mode 100644 seed/ts-sdk/allof/tests/mock-server/withJson.ts delete mode 100644 seed/ts-sdk/allof/tests/setup.ts delete mode 100644 seed/ts-sdk/allof/tests/tsconfig.json delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt delete mode 100644 seed/ts-sdk/allof/tests/unit/logging/logger.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/url/join.test.ts delete mode 100644 seed/ts-sdk/allof/tests/unit/url/qs.test.ts delete mode 100644 seed/ts-sdk/allof/tests/wire/.gitkeep delete mode 100644 seed/ts-sdk/allof/tests/wire/main.test.ts delete mode 100644 seed/ts-sdk/allof/tsconfig.base.json delete mode 100644 seed/ts-sdk/allof/tsconfig.cjs.json delete mode 100644 seed/ts-sdk/allof/tsconfig.esm.json delete mode 100644 seed/ts-sdk/allof/tsconfig.json delete mode 100644 seed/ts-sdk/allof/vitest.config.mts diff --git a/seed/csharp-model/allof-inline/.editorconfig b/seed/csharp-model/allof-inline/.editorconfig deleted file mode 100644 index 1e7a0adbac80..000000000000 --- a/seed/csharp-model/allof-inline/.editorconfig +++ /dev/null @@ -1,35 +0,0 @@ -root = true - -[*.cs] -resharper_arrange_object_creation_when_type_evident_highlighting = hint -resharper_auto_property_can_be_made_get_only_global_highlighting = hint -resharper_check_namespace_highlighting = hint -resharper_class_never_instantiated_global_highlighting = hint -resharper_class_never_instantiated_local_highlighting = hint -resharper_collection_never_updated_global_highlighting = hint -resharper_convert_type_check_pattern_to_null_check_highlighting = hint -resharper_inconsistent_naming_highlighting = hint -resharper_member_can_be_private_global_highlighting = hint -resharper_member_hides_static_from_outer_class_highlighting = hint -resharper_not_accessed_field_local_highlighting = hint -resharper_nullable_warning_suppression_is_used_highlighting = suggestion -resharper_partial_type_with_single_part_highlighting = hint -resharper_prefer_concrete_value_over_default_highlighting = none -resharper_private_field_can_be_converted_to_local_variable_highlighting = hint -resharper_property_can_be_made_init_only_global_highlighting = hint -resharper_property_can_be_made_init_only_local_highlighting = hint -resharper_redundant_name_qualifier_highlighting = none -resharper_redundant_using_directive_highlighting = hint -resharper_replace_slice_with_range_indexer_highlighting = none -resharper_unused_auto_property_accessor_global_highlighting = hint -resharper_unused_auto_property_accessor_local_highlighting = hint -resharper_unused_member_global_highlighting = hint -resharper_unused_type_global_highlighting = hint -resharper_use_string_interpolation_highlighting = hint -dotnet_diagnostic.CS1591.severity = suggestion - -[src/**/Types/*.cs] -resharper_check_namespace_highlighting = none - -[src/**/Core/Public/*.cs] -resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-model/allof-inline/.fern/metadata.json b/seed/csharp-model/allof-inline/.fern/metadata.json deleted file mode 100644 index bdf3cbf8c785..000000000000 --- a/seed/csharp-model/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-csharp-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/csharp-model/allof-inline/.github/workflows/ci.yml b/seed/csharp-model/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 87068349b616..000000000000 --- a/seed/csharp-model/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -env: - DOTNET_NOLOGO: true - -jobs: - ci: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.x - - - name: Install tools - run: dotnet tool restore - - - name: Restore dependencies - run: dotnet restore src/SeedApi/SeedApi.csproj - - - name: Build - run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release - - - name: Restore test dependencies - run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj - - - name: Build tests - run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release - - - name: Test - run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release - - - name: Pack - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release - - - name: Publish to NuGet.org - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} - run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" - diff --git a/seed/csharp-model/allof-inline/.gitignore b/seed/csharp-model/allof-inline/.gitignore deleted file mode 100644 index 11014f2b33d7..000000000000 --- a/seed/csharp-model/allof-inline/.gitignore +++ /dev/null @@ -1,484 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -## This is based on `dotnet new gitignore` and customized by Fern - -# dotenv files -.env - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -# [Rr]elease/ (Ignored by Fern) -# [Rr]eleases/ (Ignored by Fern) -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -# [Ll]og/ (Ignored by Fern) -# [Ll]ogs/ (Ignored by Fern) - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET -project.lock.json -project.fragment.lock.json -artifacts/ - -# Tye -.tye/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml -.idea - -## -## Visual studio for Mac -## - - -# globs -Makefile.in -*.userprefs -*.usertasks -config.make -config.status -aclocal.m4 -install-sh -autom4te.cache/ -*.tar.gz -tarballs/ -test-results/ - -# Mac bundle stuff -*.dmg -*.app - -# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Vim temporary swap files -*.swp diff --git a/seed/csharp-model/allof-inline/SeedApi.slnx b/seed/csharp-model/allof-inline/SeedApi.slnx deleted file mode 100644 index d4c63c241aad..000000000000 --- a/seed/csharp-model/allof-inline/SeedApi.slnx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/seed/csharp-model/allof-inline/snippet.json b/seed/csharp-model/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs deleted file mode 100644 index a12183113312..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs +++ /dev/null @@ -1,365 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class AdditionalPropertiesTests -{ - [Test] - public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); - Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); - }); - } - - [Test] - public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecord - { - Id = "1", - AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.Id, Is.EqualTo("1")); - Assert.That( - deserializedRecord.AdditionalProperties["category"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), - Is.EqualTo("fiction") - ); - Assert.That( - deserializedRecord.AdditionalProperties["title"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() - { - // Arrange - var extensionData = new Dictionary - { - ["key1"] = JsonUtils.SerializeToElement("value1"), - ["key2"] = JsonUtils.SerializeToElement(123), - }; - var readOnlyProps = new ReadOnlyAdditionalProperties(); - readOnlyProps.CopyFromExtensionData(extensionData); - - // Act & Assert - Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); - Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); - } - - [Test] - public void AdditionalProperties_ShouldBehaveAsDictionary() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - additionalProps["key3"] = true; - - // Assert - Assert.Multiple(() => - { - Assert.That(additionalProps["key1"], Is.EqualTo("value1")); - Assert.That(additionalProps["key2"], Is.EqualTo(123)); - Assert.That((bool)additionalProps["key3"]!, Is.True); - Assert.That(additionalProps.Count, Is.EqualTo(3)); - }); - } - - [Test] - public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - var jsonObject = additionalProps.ToJsonObject(); - - Assert.Multiple(() => - { - // Assert - Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); - Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); - }); - } - - [Test] - public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - var record = JsonUtils.Deserialize(json); - - // Act - record.AdditionalProperties["category"] = "non-fiction"; - - // Assert - Assert.Multiple(() => - { - Assert.That(record, Is.Not.Null); - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); - Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); - Assert.That( - ((JsonElement)record.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": 42, - "extra2": 99 - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithInts - { - AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": { "key1": true, "key2": false }, - "extra2": { "key3": true } - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithDictionaries - { - AdditionalProperties = - { - ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, - ["extra2"] = new Dictionary { { "key3", true } }, - }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - private record Record : IJsonOnDeserialized - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithInts : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithDictionaries : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties< - Dictionary - > AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties> AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs deleted file mode 100644 index c0f258680b78..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateOnlyJsonTests -{ - [Test] - public void SerializeDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly? dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly? expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateOnlyKey() - { - var key = new DateOnly(2023, 10, 5); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateOnlyKey() - { - var json = """ - { - "2023-10-05": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateOnly(2023, 10, 5); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs deleted file mode 100644 index 1dde45a8e939..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateTimeJsonTests -{ - [Test] - public void SerializeDateTime_ShouldMatchExpectedFormat() - { - (DateTime dateTime, string expected)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - foreach (var (dateTime, expected) in testCases) - { - var json = JsonUtils.Serialize(dateTime); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateTime_ShouldMatchExpectedDateTime() - { - (DateTime expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateTime_ShouldMatchExpectedFormat() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateTimeKey() - { - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateTimeKey() - { - var json = """ - { - "2023-10-05T14:30:00.000Z": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs deleted file mode 100644 index 969acd620998..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class JsonAccessAttributeTests -{ - private class MyClass - { - [JsonPropertyName("read_only_prop")] - [JsonAccess(JsonAccessType.ReadOnly)] - public string? ReadOnlyProp { get; set; } - - [JsonPropertyName("write_only_prop")] - [JsonAccess(JsonAccessType.WriteOnly)] - public string? WriteOnlyProp { get; set; } - - [JsonPropertyName("normal_prop")] - public string? NormalProp { get; set; } - - [JsonPropertyName("read_only_nullable_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable? ReadOnlyNullableList { get; set; } - - [JsonPropertyName("read_only_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable ReadOnlyList { get; set; } = []; - - [JsonPropertyName("write_only_nullable_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable? WriteOnlyNullableList { get; set; } - - [JsonPropertyName("write_only_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable WriteOnlyList { get; set; } = []; - - [JsonPropertyName("normal_list")] - public IEnumerable NormalList { get; set; } = []; - - [JsonPropertyName("normal_nullable_list")] - public IEnumerable? NullableNormalList { get; set; } - } - - [Test] - public void JsonAccessAttribute_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "write_only_prop": "write", - "normal_prop": "normal_prop", - "read_only_nullable_list": ["item1", "item2"], - "read_only_list": ["item3", "item4"], - "write_only_nullable_list": ["item5", "item6"], - "write_only_list": ["item7", "item8"], - "normal_list": ["normal1", "normal2"], - "normal_nullable_list": ["normal1", "normal2"] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // String properties - Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); - Assert.That(obj.WriteOnlyProp, Is.Null); - Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); - - // List properties - read only - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Not.Null); - Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); - Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); - Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); - - var readOnlyList = obj.ReadOnlyList.ToArray(); - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Has.Length.EqualTo(2)); - Assert.That(readOnlyList[0], Is.EqualTo("item3")); - Assert.That(readOnlyList[1], Is.EqualTo("item4")); - - // List properties - write only - Assert.That(obj.WriteOnlyNullableList, Is.Null); - Assert.That(obj.WriteOnlyList, Is.Not.Null); - Assert.That(obj.WriteOnlyList, Is.Empty); - - // Normal list property - var normalList = obj.NormalList.ToArray(); - Assert.That(normalList, Is.Not.Null); - Assert.That(normalList, Has.Length.EqualTo(2)); - Assert.That(normalList[0], Is.EqualTo("normal1")); - Assert.That(normalList[1], Is.EqualTo("normal2")); - }); - - // Set up values for serialization - obj.WriteOnlyProp = "write"; - obj.NormalProp = "new_value"; - obj.WriteOnlyNullableList = new List { "write1", "write2" }; - obj.WriteOnlyList = new List { "write3", "write4" }; - obj.NormalList = new List { "new_normal" }; - obj.NullableNormalList = new List { "new_normal" }; - - var serializedJson = JsonUtils.Serialize(obj); - const string expectedJson = """ - { - "write_only_prop": "write", - "normal_prop": "new_value", - "write_only_nullable_list": [ - "write1", - "write2" - ], - "write_only_list": [ - "write3", - "write4" - ], - "normal_list": [ - "new_normal" - ], - "normal_nullable_list": [ - "new_normal" - ] - } - """; - Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); - } - - [Test] - public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "normal_prop": "normal_prop", - "read_only_nullable_list": null, - "read_only_list": [] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // Read-only nullable list should be null when JSON contains null - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Null); - - // Read-only non-nullable list should never be null, but empty when JSON contains null - var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Is.Empty); - }); - - // Serialize and verify read-only lists are not included - var serializedJson = JsonUtils.Serialize(obj); - Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); - Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); - Assert.That(serializedJson, Does.Not.Contain("read_only_list")); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs deleted file mode 100644 index 42d165830baf..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs +++ /dev/null @@ -1,314 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using OneOf; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -[Parallelizable(ParallelScope.All)] -public class OneOfSerializerTests -{ - private class Foo - { - [JsonPropertyName("string_prop")] - public required string StringProp { get; set; } - } - - private class Bar - { - [JsonPropertyName("int_prop")] - public required int IntProp { get; set; } - } - - private static readonly OneOf OneOf1 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT2(new { }); - private const string OneOf1String = "{}"; - - private static readonly OneOf OneOf2 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT0("test"); - private const string OneOf2String = "\"test\""; - - private static readonly OneOf OneOf3 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT1(123); - private const string OneOf3String = "123"; - - private static readonly OneOf OneOf4 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT3(new Foo { StringProp = "test" }); - private const string OneOf4String = "{\"string_prop\": \"test\"}"; - - private static readonly OneOf OneOf5 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT4(new Bar { IntProp = 5 }); - private const string OneOf5String = "{\"int_prop\": 5}"; - - [Test] - public void Serialize_OneOfs_Should_Return_Expected_String() - { - (OneOf, string)[] testData = - [ - (OneOf1, OneOf1String), - (OneOf2, OneOf2String), - (OneOf3, OneOf3String), - (OneOf4, OneOf4String), - (OneOf5, OneOf5String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, expected) in testData) - { - var result = JsonUtils.Serialize(oneOf); - Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void OneOfs_Should_Deserialize_From_String() - { - (OneOf, string)[] testData = - [ - (OneOf1, OneOf1String), - (OneOf2, OneOf2String), - (OneOf3, OneOf3String), - (OneOf4, OneOf4String), - (OneOf5, OneOf5String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, json) in testData) - { - var result = JsonUtils.Deserialize>(json); - Assert.That(result.Index, Is.EqualTo(oneOf.Index)); - Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); - } - }); - } - - private static readonly OneOf? NullableOneOf1 = null; - private const string NullableOneOf1String = "null"; - - private static readonly OneOf? NullableOneOf2 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT4(new Bar { IntProp = 5 }); - private const string NullableOneOf2String = "{\"int_prop\": 5}"; - - [Test] - public void Serialize_NullableOneOfs_Should_Return_Expected_String() - { - (OneOf?, string)[] testData = - [ - (NullableOneOf1, NullableOneOf1String), - (NullableOneOf2, NullableOneOf2String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, expected) in testData) - { - var result = JsonUtils.Serialize(oneOf); - Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void NullableOneOfs_Should_Deserialize_From_String() - { - (OneOf?, string)[] testData = - [ - (NullableOneOf1, NullableOneOf1String), - (NullableOneOf2, NullableOneOf2String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, json) in testData) - { - var result = JsonUtils.Deserialize?>(json); - Assert.That(result?.Index, Is.EqualTo(oneOf?.Index)); - Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result?.Value)).IgnoreWhiteSpace); - } - }); - } - - private static readonly OneOf OneOfWithNullable1 = OneOf< - string, - int, - Foo? - >.FromT2(null); - private const string OneOfWithNullable1String = "null"; - - private static readonly OneOf OneOfWithNullable2 = OneOf< - string, - int, - Foo? - >.FromT2(new Foo { StringProp = "test" }); - private const string OneOfWithNullable2String = "{\"string_prop\": \"test\"}"; - - private static readonly OneOf OneOfWithNullable3 = OneOf< - string, - int, - Foo? - >.FromT0("test"); - private const string OneOfWithNullable3String = "\"test\""; - - [Test] - public void Serialize_OneOfWithNullables_Should_Return_Expected_String() - { - (OneOf, string)[] testData = - [ - (OneOfWithNullable1, OneOfWithNullable1String), - (OneOfWithNullable2, OneOfWithNullable2String), - (OneOfWithNullable3, OneOfWithNullable3String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, expected) in testData) - { - var result = JsonUtils.Serialize(oneOf); - Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void OneOfWithNullables_Should_Deserialize_From_String() - { - (OneOf, string)[] testData = - [ - // (OneOfWithNullable1, OneOfWithNullable1String), // not possible with .NET's JSON serializer - (OneOfWithNullable2, OneOfWithNullable2String), - (OneOfWithNullable3, OneOfWithNullable3String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, json) in testData) - { - var result = JsonUtils.Deserialize>(json); - Assert.That(result.Index, Is.EqualTo(oneOf.Index)); - Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void Serialize_OneOfWithObjectLast_Should_Return_Expected_String() - { - var oneOfWithObjectLast = OneOf.FromT4( - new { random = "data" } - ); - const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; - - var result = JsonUtils.Serialize(oneOfWithObjectLast); - Assert.That(result, Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace); - } - - [Test] - public void OneOfWithObjectLast_Should_Deserialize_From_String() - { - const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; - var result = JsonUtils.Deserialize>( - oneOfWithObjectLastString - ); - Assert.Multiple(() => - { - Assert.That(result.Index, Is.EqualTo(4)); - Assert.That(result.Value, Is.InstanceOf()); - Assert.That( - JsonUtils.Serialize(result.Value), - Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace - ); - }); - } - - [Test] - public void Serialize_OneOfWithObjectNotLast_Should_Return_Expected_String() - { - var oneOfWithObjectNotLast = OneOf.FromT1( - new { random = "data" } - ); - const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; - - var result = JsonUtils.Serialize(oneOfWithObjectNotLast); - Assert.That(result, Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace); - } - - [Test] - public void OneOfWithObjectNotLast_Should_Deserialize_From_String() - { - const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; - var result = JsonUtils.Deserialize>( - oneOfWithObjectNotLastString - ); - Assert.Multiple(() => - { - Assert.That(result.Index, Is.EqualTo(1)); - Assert.That(result.Value, Is.InstanceOf()); - Assert.That( - JsonUtils.Serialize(result.Value), - Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace - ); - }); - } - - [Test] - public void Serialize_OneOfSingleType_Should_Return_Expected_String() - { - var oneOfSingle = OneOf.FromT0("single"); - const string oneOfSingleString = "\"single\""; - - var result = JsonUtils.Serialize(oneOfSingle); - Assert.That(result, Is.EqualTo(oneOfSingleString).IgnoreWhiteSpace); - } - - [Test] - public void OneOfSingleType_Should_Deserialize_From_String() - { - const string oneOfSingleString = "\"single\""; - var result = JsonUtils.Deserialize>(oneOfSingleString); - Assert.Multiple(() => - { - Assert.That(result.Index, Is.EqualTo(0)); - Assert.That(result.Value, Is.EqualTo("single")); - }); - } - - [Test] - public void Deserialize_InvalidData_Should_Throw_Exception() - { - const string invalidJson = "{\"invalid\": \"data\"}"; - - Assert.Throws(() => - { - JsonUtils.Deserialize>(invalidJson); - }); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props deleted file mode 100644 index aac9b5020d80..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj deleted file mode 100644 index 77e1a9943739..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - net9.0 - 12 - enable - enable - false - true - true - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs deleted file mode 100644 index 3ac7e5310f95..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs +++ /dev/null @@ -1,219 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; -using SeedApi; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle AdditionalProperties values. -/// -public static class AdditionalPropertiesComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their - /// serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) - { - constraint.Using( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - /// - /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing - /// their serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) - { - constraint.Using>( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => - JsonElementsAreEqual(x, y); - - private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) - { - if (x.ValueKind != y.ValueKind) - { - return false; - } - - return x.ValueKind switch - { - JsonValueKind.Object => CompareJsonObjects(x, y), - JsonValueKind.Array => CompareJsonArrays(x, y), - JsonValueKind.String => x.GetString() == y.GetString(), - JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), - JsonValueKind.True => true, - JsonValueKind.False => true, - JsonValueKind.Null => true, - _ => false, - }; - } - - private static bool CompareJsonObjects(JsonElement x, JsonElement y) - { - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - if (xProps.Count != yProps.Count) - { - return false; - } - - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - return false; - } - - if (!JsonElementsAreEqual(xProps[key], yProps[key])) - { - return false; - } - } - - return true; - } - - private static bool CompareJsonArrays(JsonElement x, JsonElement y) - { - var xArray = x.EnumerateArray().ToList(); - var yArray = y.EnumerateArray().ToList(); - - if (xArray.Count != yArray.Count) - { - return false; - } - - for (var i = 0; i < xArray.Count; i++) - { - if (!JsonElementsAreEqual(xArray[i], yArray[i])) - { - return false; - } - } - - return true; - } - - /// - /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. - /// When UsingPropertiesComparer() walks object properties and encounters a property typed as - /// 'object', the expected side may be a Dictionary<object, object?> while the actual - /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing - /// the non-JsonElement side and comparing JSON representations. - /// - /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic - /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only - /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all - /// same-type comparisons normally. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) - { - // Handle: expected is non-JsonElement, actual is JsonElement - constraint.Using( - (actualJsonElement, expectedObj) => - { - try - { - var expectedElement = JsonUtils.SerializeToElement(expectedObj); - return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); - } - catch - { - return false; - } - } - ); - // Handle reverse: expected is JsonElement, actual is non-JsonElement - constraint.Using( - (actualObj, expectedJsonElement) => - { - try - { - var actualElement = JsonUtils.SerializeToElement(actualObj); - return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); - } - catch - { - return false; - } - } - ); - return constraint; - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs deleted file mode 100644 index 3f4b5eb602b2..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs +++ /dev/null @@ -1,29 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Utils; - -internal static class JsonAssert -{ - /// - /// Asserts that the serialized JSON of an object equals the expected JSON string. - /// Uses JsonElement comparison for reliable deep equality of collections and union types. - /// - internal static void AreEqual(object actual, string expectedJson) - { - var actualElement = JsonUtils.SerializeToElement(actual); - var expectedElement = JsonUtils.Deserialize(expectedJson); - Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); - } - - /// - /// Asserts that the given JSON string survives a deserialization/serialization round-trip - /// intact: deserializes to T then re-serializes and compares to the original JSON. - /// - internal static void Roundtrips(string json) - { - var deserialized = JsonUtils.Deserialize(json); - AreEqual(deserialized!, json); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs deleted file mode 100644 index a37ef402c1ac..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs +++ /dev/null @@ -1,236 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle JsonElement objects. -/// -public static class JsonElementComparerExtensions -{ - /// - /// Extension method for comparing JsonElement objects in NUnit tests. - /// Property order doesn't matter, but array order does matter. - /// Includes special handling for DateTime string formats. - /// - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare JsonElements with detailed diffs. - public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) - { - return constraint.Using(new JsonElementComparer()); - } -} - -/// -/// Equality comparer for JsonElement with detailed reporting. -/// Property order doesn't matter, but array order does matter. -/// Now includes special handling for DateTime string formats with improved null handling. -/// -public class JsonElementComparer : IEqualityComparer -{ - private string _failurePath = string.Empty; - - /// - public bool Equals(JsonElement x, JsonElement y) - { - _failurePath = string.Empty; - return CompareJsonElements(x, y, string.Empty); - } - - /// - public int GetHashCode(JsonElement obj) - { - return JsonSerializer.Serialize(obj).GetHashCode(); - } - - private bool CompareJsonElements(JsonElement x, JsonElement y, string path) - { - // If value kinds don't match, they're not equivalent - if (x.ValueKind != y.ValueKind) - { - _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; - return false; - } - - switch (x.ValueKind) - { - case JsonValueKind.Object: - return CompareJsonObjects(x, y, path); - - case JsonValueKind.Array: - return CompareJsonArraysInOrder(x, y, path); - - case JsonValueKind.String: - string? xStr = x.GetString(); - string? yStr = y.GetString(); - - // Handle null strings - if (xStr is null && yStr is null) - return true; - - if (xStr is null || yStr is null) - { - _failurePath = - $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; - return false; - } - - // Check if they are identical strings - if (xStr == yStr) - return true; - - // Try to handle DateTime strings - if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) - { - if (AreEquivalentDateTimeStrings(xStr, yStr)) - return true; - } - - _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; - return false; - - case JsonValueKind.Number: - if (x.GetDecimal() != y.GetDecimal()) - { - _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; - return false; - } - - return true; - - case JsonValueKind.True: - case JsonValueKind.False: - if (x.GetBoolean() != y.GetBoolean()) - { - _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; - return false; - } - - return true; - - case JsonValueKind.Null: - return true; - - default: - _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; - return false; - } - } - - private bool IsLikelyDateTimeString(string? str) - { - // Simple heuristic to identify likely ISO date time strings - return str is not null - && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); - } - - private bool AreEquivalentDateTimeStrings(string str1, string str2) - { - // Try to parse both as DateTime - if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) - { - return dt1 == dt2; - } - - return false; - } - - private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) - { - // Create dictionaries for both JSON objects - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - // Check if all properties in x exist in y - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - _failurePath = $"{path}: Missing property '{key}'"; - return false; - } - } - - // Check if y has extra properties - foreach (var key in yProps.Keys) - { - if (!xProps.ContainsKey(key)) - { - _failurePath = $"{path}: Unexpected property '{key}'"; - return false; - } - } - - // Compare each property value - foreach (var key in xProps.Keys) - { - var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; - if (!CompareJsonElements(xProps[key], yProps[key], propPath)) - { - return false; - } - } - - return true; - } - - private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) - { - var xArray = x.EnumerateArray(); - var yArray = y.EnumerateArray(); - - // Count x elements - var xCount = 0; - var xElements = new List(); - foreach (var item in xArray) - { - xElements.Add(item); - xCount++; - } - - // Count y elements - var yCount = 0; - var yElements = new List(); - foreach (var item in yArray) - { - yElements.Add(item); - yCount++; - } - - // Check if counts match - if (xCount != yCount) - { - _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; - return false; - } - - // Compare elements in order - for (var i = 0; i < xCount; i++) - { - var itemPath = $"{path}[{i}]"; - if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) - { - return false; - } - } - - return true; - } - - /// - public override string ToString() - { - if (!string.IsNullOrEmpty(_failurePath)) - { - return $"JSON comparison failed at {_failurePath}"; - } - - return "JsonElementEqualityComparer"; - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs deleted file mode 100644 index 816f4c010e6e..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class NUnitExtensions -{ - /// - /// Modifies the EqualConstraint to use our own set of default comparers. - /// - /// - /// - public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => - constraint - .UsingPropertiesComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingOneOfComparer() - .UsingJsonElementComparer() - .UsingOptionalComparer() - .UsingObjectDictionaryComparer() - .UsingAdditionalPropertiesComparer() - .UsingJsonSerializationComparer(); -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs deleted file mode 100644 index 767439174363..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle OneOf values. -/// -public static class EqualConstraintExtensions -{ - /// - /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOneOf types - constraint.Using( - (x, y) => - { - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (x.Value is null && y.Value is null) - { - return true; - } - - if (x.Value is null) - { - return false; - } - - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs deleted file mode 100644 index 98bfcac477b8..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs +++ /dev/null @@ -1,104 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle Optional values. -/// -public static class OptionalComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOptional types - constraint.Using( - (x, y) => - { - // Both must have the same IsDefined state - if (x.IsDefined != y.IsDefined) - { - return false; - } - - // If both are undefined, they're equal - if (!x.IsDefined) - { - return true; - } - - // Both are defined, compare their boxed values - var xValue = x.GetBoxedValue(); - var yValue = y.GetBoxedValue(); - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (xValue is null && yValue is null) - { - return true; - } - - if (xValue is null || yValue is null) - { - return false; - } - - // Use NUnit's property comparer for the inner values - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values within Optional types. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs deleted file mode 100644 index fc0b595a5e54..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class ReadOnlyMemoryComparerExtensions -{ - /// - /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. - /// - /// The type of elements in the ReadOnlyMemory. - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare ReadOnlyMemory<T>. - public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) - where T : IComparable - { - return constraint.Using(new ReadOnlyMemoryComparer()); - } -} - -/// -/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. -/// -/// -/// The type of elements in the ReadOnlyMemory. -/// -public class ReadOnlyMemoryComparer : IComparer> - where T : IComparable -{ - /// - public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) - { - // Check if sequences are equal - var xSpan = x.Span; - var ySpan = y.Span; - - // Optimized case for IEquatable implementations - if (typeof(IEquatable).IsAssignableFrom(typeof(T))) - { - var areEqual = xSpan.SequenceEqual(ySpan); - if (areEqual) - { - return 0; // Sequences are equal - } - } - else - { - // Manual equality check for non-IEquatable types - if (xSpan.Length == ySpan.Length) - { - var areEqual = true; - for (var i = 0; i < xSpan.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - areEqual = false; - break; - } - } - - if (areEqual) - { - return 0; // Sequences are equal - } - } - } - - // For non-equal sequences, we need to return a consistent ordering - // First compare lengths - if (x.Length != y.Length) - return x.Length.CompareTo(y.Length); - - // Same length but different content - compare first differing element - for (var i = 0; i < x.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - return xSpan[i].CompareTo(ySpan[i]); - } - } - - // Should never reach here if not equal - return 0; - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs b/seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs deleted file mode 100644 index c0d843281678..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/AuditInfo.cs +++ /dev/null @@ -1,56 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -/// -/// Common audit metadata. -/// -[Serializable] -public record AuditInfo : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs b/seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs deleted file mode 100644 index eb944d0030da..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/BaseOrg.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public BaseOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs b/seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs deleted file mode 100644 index fcb0efec5fea..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/BaseOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from BaseOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Subscription tier. - /// - [JsonPropertyName("tier")] - public string? Tier { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs b/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs deleted file mode 100644 index 633fb725fb81..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record CombinedEntity : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Describable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonPropertyName("status")] - public required CombinedEntityStatus Status { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs b/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs deleted file mode 100644 index 0ab2467f6bd7..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/CombinedEntityStatus.cs +++ /dev/null @@ -1,115 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] -[Serializable] -public readonly record struct CombinedEntityStatus : IStringEnum -{ - public static readonly CombinedEntityStatus Active = new(Values.Active); - - public static readonly CombinedEntityStatus Archived = new(Values.Archived); - - public CombinedEntityStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static CombinedEntityStatus FromCustom(string value) - { - return new CombinedEntityStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(CombinedEntityStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(CombinedEntityStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(CombinedEntityStatus value) => value.Value; - - public static explicit operator CombinedEntityStatus(string value) => new(value); - - internal class CombinedEntityStatusSerializer : JsonConverter - { - public override CombinedEntityStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override CombinedEntityStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Archived = "archived"; - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs deleted file mode 100644 index b684f33d750e..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs +++ /dev/null @@ -1,91 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Json collection converter. -/// -/// Type of item to convert. -/// Converter to use for individual items. -internal class CollectionItemSerializer - : JsonConverter> - where TConverterType : JsonConverter, new() -{ - private static readonly TConverterType _converter = new TConverterType(); - - /// - /// Reads a json string and deserializes it into an object. - /// - /// Json reader. - /// Type to convert. - /// Serializer options. - /// Created object. - public override IEnumerable? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - var returnValue = new List(); - - while (reader.TokenType != JsonTokenType.EndArray) - { - if (reader.TokenType != JsonTokenType.StartArray) - { - var item = (TDatatype)( - JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) - ?? throw new global::System.Exception( - $"Failed to deserialize collection item of type {typeof(TDatatype)}" - ) - ); - returnValue.Add(item); - } - - reader.Read(); - } - - return returnValue; - } - - /// - /// Writes a json string. - /// - /// Json writer. - /// Value to write. - /// Serializer options. - public override void Write( - Utf8JsonWriter writer, - IEnumerable? value, - JsonSerializerOptions options - ) - { - if (value is null) - { - writer.WriteNullValue(); - return; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - writer.WriteStartArray(); - - foreach (var data in value) - { - JsonSerializer.Serialize(writer, data, jsonSerializerOptions); - } - - writer.WriteEndArray(); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs deleted file mode 100644 index ccf4e963cc89..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi.Core; - -internal static class Constants -{ - public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; - public const string DateFormat = "yyyy-MM-dd"; -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs deleted file mode 100644 index af61cc061ae5..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs +++ /dev/null @@ -1,747 +0,0 @@ -// ReSharper disable All -#pragma warning disable - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using global::System.Diagnostics; -using global::System.Diagnostics.CodeAnalysis; -using global::System.Globalization; -using global::System.Runtime.CompilerServices; -using global::System.Runtime.InteropServices; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -// ReSharper disable SuggestVarOrType_SimpleTypes -// ReSharper disable SuggestVarOrType_BuiltInTypes - -namespace SeedApi.Core -{ - /// - /// Custom converter for handling the data type with the System.Text.Json library. - /// - /// - /// This class backported from: - /// - /// System.Text.Json.Serialization.Converters.DateOnlyConverter - /// - public sealed class DateOnlyConverter : JsonConverter - { - private const int FormatLength = 10; // YYYY-MM-DD - - private const int MaxEscapedFormatLength = - FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; - - /// - public override DateOnly Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType != JsonTokenType.String) - { - ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); - } - - return ReadCore(ref reader); - } - - /// - public override DateOnly ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - return ReadCore(ref reader); - } - - private static DateOnly ReadCore(ref Utf8JsonReader reader) - { - if ( - !JsonHelpers.IsInRangeInclusive( - reader.ValueLength(), - FormatLength, - MaxEscapedFormatLength - ) - ) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - scoped ReadOnlySpan source; - if (!reader.HasValueSequence && !reader.ValueIsEscaped) - { - source = reader.ValueSpan; - } - else - { - Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; - int bytesWritten = reader.CopyString(stackSpan); - source = stackSpan.Slice(0, bytesWritten); - } - - if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - return value; - } - - /// - public override void Write( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WriteStringValue(buffer); - } - - /// - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WritePropertyName(buffer); - } - } - - internal static class JsonConstants - { - // The maximum number of fraction digits the Json DateTime parser allows - public const int DateTimeParseNumFractionDigits = 16; - - // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. - public const int MaxExpansionFactorWhileEscaping = 6; - - // The largest fraction expressible by TimeSpan and DateTime formats - public const int MaxDateTimeFraction = 9_999_999; - - // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. - public const int DateTimeNumFractionDigits = 7; - - public const byte UtcOffsetToken = (byte)'Z'; - - public const byte TimePrefix = (byte)'T'; - - public const byte Period = (byte)'.'; - - public const byte Hyphen = (byte)'-'; - - public const byte Colon = (byte)':'; - - public const byte Plus = (byte)'+'; - } - - // ReSharper disable SuggestVarOrType_Elsewhere - // ReSharper disable SuggestVarOrType_SimpleTypes - // ReSharper disable SuggestVarOrType_BuiltInTypes - - internal static class JsonHelpers - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => - (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); - - public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; - - [StructLayout(LayoutKind.Auto)] - private struct DateTimeParseData - { - public int Year; - public int Month; - public int Day; - public bool IsCalendarDateOnly; - public int Hour; - public int Minute; - public int Second; - public int Fraction; // This value should never be greater than 9_999_999. - public int OffsetHours; - public int OffsetMinutes; - - // ReSharper disable once NotAccessedField.Local - public byte OffsetToken; - } - - public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) - { - if ( - TryParseDateTimeOffset(source, out DateTimeParseData parseData) - && parseData.IsCalendarDateOnly - && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) - ) - { - value = DateOnly.FromDateTime(dateTime); - return true; - } - - value = default; - return false; - } - - /// - /// ISO 8601 date time parser (ISO 8601-1:2019). - /// - /// The date/time to parse in UTF-8 format. - /// The parsed for the given . - /// - /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day - /// representations with optional specification of seconds and fractional seconds. - /// - /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). - /// If unspecified they are considered to be local per spec. - /// - /// Examples: (TZD is either "Z" or hh:mm offset from UTC) - /// - /// YYYY-MM-DD (e.g. 1997-07-16) - /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) - /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) - /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) - /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) - /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) - /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) - /// - /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). - /// The extended variants have separator characters between components ('-', ':', '.', etc.). - /// Spaces are not permitted. - /// - /// "true" if successfully parsed. - private static bool TryParseDateTimeOffset( - ReadOnlySpan source, - out DateTimeParseData parseData - ) - { - parseData = default; - - // too short datetime - Debug.Assert(source.Length >= 10); - - // Parse the calendar date - // ----------------------- - // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" - // [dateX] = [year]["-"][month]["-"][day] - // [year] = [YYYY] [0000 - 9999] (4.3.2) - // [month] = [MM] [01 - 12] (4.3.3) - // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) - // - // Note: 5.2.2.2 "Representations with reduced precision" allows for - // just [year]["-"][month] (a) and just [year] (b), but we currently - // don't permit it. - - { - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - uint digit3 = source[2] - (uint)'0'; - uint digit4 = source[3] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) - { - return false; - } - - parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); - } - - if ( - source[4] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) - || source[7] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) - ) - { - return false; - } - - // We now have YYYY-MM-DD [dateX] - // ReSharper disable once ConvertIfStatementToSwitchStatement - if (source.Length == 10) - { - parseData.IsCalendarDateOnly = true; - return true; - } - - // Parse the time of day - // --------------------- - // - // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" - // [timeX] = ["T"][hour][":"][min][":"][sec] - // [hour] = [hh] [00 - 23] (4.3.8a) - // [minute] = [mm] [00 - 59] (4.3.9a) - // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) - // - // ISO 8601-1:2019 5.3.3 "UTC of day" - // [timeX]["Z"] - // - // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between - // local timescale and UTC" (Extended format) - // - // [shiftX] = ["+"|"-"][hour][":"][min] - // - // Notes: - // - // "T" is optional per spec, but _only_ when times are used alone. In our - // case, we're reading out a complete date & time and as such require "T". - // (5.4.2.1b). - // - // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations - // with reduced precision". 5.3.1.3b allows just specifying the hour, but - // we currently don't permit this. - // - // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). - // We only allow fractions for seconds currently. Lower order components - // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be - // one digit, but the max number of digits is implementation defined. We - // currently allow up to 16 digits of fractional seconds only. While we - // support 16 fractional digits we only parse the first seven, anything - // past that is considered a zero. This is to stay compatible with the - // DateTime implementation which is limited to this resolution. - - if (source.Length < 16) - { - // Source does not have enough characters for YYYY-MM-DDThh:mm - return false; - } - - // Parse THH:MM (e.g. "T10:32") - if ( - source[10] != JsonConstants.TimePrefix - || source[13] != JsonConstants.Colon - || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) - || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm - Debug.Assert(source.Length >= 16); - if (source.Length == 16) - { - return true; - } - - byte curByte = source[16]; - int sourceIndex = 17; - - // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Colon: - break; - default: - return false; - } - - // Try reading the seconds - if ( - source.Length < 19 - || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss - Debug.Assert(source.Length >= 19); - if (source.Length == 19) - { - return true; - } - - curByte = source[19]; - sourceIndex = 20; - - // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Period: - break; - default: - return false; - } - - // Source does not have enough characters for second fractions (i.e. ".s") - // YYYY-MM-DDThh:mm:ss.s - if (source.Length < 21) - { - return false; - } - - // Parse fraction. This value should never be greater than 9_999_999 - int numDigitsRead = 0; - int fractionEnd = Math.Min( - sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, - source.Length - ); - - while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) - { - if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); - numDigitsRead++; - } - - sourceIndex++; - } - - if (parseData.Fraction != 0) - { - while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction *= 10; - numDigitsRead++; - } - } - - // We now have YYYY-MM-DDThh:mm:ss.s - Debug.Assert(sourceIndex <= source.Length); - if (sourceIndex == source.Length) - { - return true; - } - - curByte = source[sourceIndex++]; - - // TZD ['Z'|'+'|'-'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - default: - return false; - } - - static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) - { - // Parse the hours for the offset - if ( - offsetData.Length < 2 - || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss.s+|-hh - - if (offsetData.Length == 2) - { - // Just hours offset specified - return true; - } - - // Ensure we have enough for ":mm" - return offsetData.Length == 5 - && offsetData[2] == JsonConstants.Colon - && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - // ReSharper disable once RedundantAssignment - private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) - { - Debug.Assert(source.Length == 2); - - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9) - { - value = 0; - return false; - } - - value = (int)(digit1 * 10 + digit2); - return true; - } - - // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs - - /// - /// Overflow-safe DateTime factory. - /// - private static bool TryCreateDateTime( - DateTimeParseData parseData, - DateTimeKind kind, - out DateTime value - ) - { - if (parseData.Year == 0) - { - value = default; - return false; - } - - Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. - - if ((uint)parseData.Month - 1 >= 12) - { - value = default; - return false; - } - - uint dayMinusOne = (uint)parseData.Day - 1; - if ( - dayMinusOne >= 28 - && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) - ) - { - value = default; - return false; - } - - if ((uint)parseData.Hour > 23) - { - value = default; - return false; - } - - if ((uint)parseData.Minute > 59) - { - value = default; - return false; - } - - // This needs to allow leap seconds when appropriate. - // See https://github.com/dotnet/runtime/issues/30135. - if ((uint)parseData.Second > 59) - { - value = default; - return false; - } - - Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. - - ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) - ? DaysToMonth366 - : DaysToMonth365; - int yearMinusOne = parseData.Year - 1; - int totalDays = - yearMinusOne * 365 - + yearMinusOne / 4 - - yearMinusOne / 100 - + yearMinusOne / 400 - + days[parseData.Month - 1] - + parseData.Day - - 1; - long ticks = totalDays * TimeSpan.TicksPerDay; - int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; - ticks += totalSeconds * TimeSpan.TicksPerSecond; - ticks += parseData.Fraction; - value = new DateTime(ticks: ticks, kind: kind); - return true; - } - - private static ReadOnlySpan DaysToMonth365 => - [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; - private static ReadOnlySpan DaysToMonth366 => - [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; - } - - internal static class ThrowHelper - { - private const string ExceptionSourceValueToRethrowAsJsonException = - "System.Text.Json.Rethrowable"; - - [DoesNotReturn] - public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) - { - throw GetInvalidOperationException("string", tokenType); - } - - public static void ThrowFormatException(DataType dataType) - { - throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - - private static global::System.Exception GetInvalidOperationException( - string message, - JsonTokenType tokenType - ) - { - return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); - } - - private static InvalidOperationException GetInvalidOperationException(string message) - { - return new InvalidOperationException(message) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - } - - internal static class Utf8JsonReaderExtensions - { - internal static int ValueLength(this Utf8JsonReader reader) => - reader.HasValueSequence - ? checked((int)reader.ValueSequence.Length) - : reader.ValueSpan.Length; - } - - internal enum DataType - { - TimeOnly, - DateOnly, - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - internal static class SR - { - private static readonly bool s_usingResourceKeys = - AppContext.TryGetSwitch( - "System.Resources.UseSystemResourceKeys", - out bool usingResourceKeys - ) && usingResourceKeys; - - public static string UnsupportedFormat => Strings.UnsupportedFormat; - - public static string InvalidCast => Strings.InvalidCast; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1) - : string.Format(resourceFormat, p1); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1, object? p2) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1, p2) - : string.Format(resourceFormat, p1, p2); - } - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute( - "System.Resources.Tools.StronglyTypedResourceBuilder", - "17.0.0.0" - )] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings - { - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( - "Microsoft.Performance", - "CA1811:AvoidUncalledPrivateCode" - )] - internal Strings() { } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = - new global::System.Resources.ResourceManager( - "System.Text.Json.Resources.Strings", - typeof(Strings).Assembly - ); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Globalization.CultureInfo Culture - { - get { return resourceCulture; } - set { resourceCulture = value; } - } - - /// - /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. - /// - internal static string InvalidCast - { - get { return ResourceManager.GetString("InvalidCast", resourceCulture); } - } - - /// - /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. - /// - internal static string UnsupportedFormat - { - get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } - } - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs deleted file mode 100644 index d7dedc7f165b..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using global::System.Globalization; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -internal class DateTimeSerializer : JsonConverter -{ - public override DateTime Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); - } - - public override DateTime ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateTime value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs deleted file mode 100644 index 93dcc6dd6bca..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace SeedApi.Core; - -[global::System.AttributeUsage( - global::System.AttributeTargets.Property | global::System.AttributeTargets.Field -)] -internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute -{ - internal JsonAccessType AccessType { get; init; } = accessType; -} - -internal enum JsonAccessType -{ - ReadOnly, - WriteOnly, -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs deleted file mode 100644 index 2fa8cfb6ad8c..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/JsonConfiguration.cs +++ /dev/null @@ -1,275 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Encodings.Web; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using global::System.Text.Json.Serialization; -using global::System.Text.Json.Serialization.Metadata; - -namespace SeedApi.Core; - -internal static partial class JsonOptions -{ - internal static readonly JsonSerializerOptions JsonSerializerOptions; - internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; - - static JsonOptions() - { - var options = new JsonSerializerOptions - { - Converters = - { - new DateTimeSerializer(), -#if USE_PORTABLE_DATE_ONLY - new DateOnlyConverter(), -#endif - new OneOfSerializer(), - new OptionalJsonConverterFactory(), - }, -#if DEBUG - WriteIndented = true, -#endif - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - TypeInfoResolver = new DefaultJsonTypeInfoResolver - { - Modifiers = - { - NullableOptionalModifier, - JsonAccessAndIgnoreModifier, - HandleExtensionDataFields, - }, - }, - }; - ConfigureJsonSerializerOptions(options); - JsonSerializerOptions = options; - - var relaxedOptions = new JsonSerializerOptions(options) - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; - JsonSerializerOptionsRelaxedEscaping = relaxedOptions; - } - - private static void NullableOptionalModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var property in typeInfo.Properties) - { - var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; - - if (propertyInfo is null) - continue; - - // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior - var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); - if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) - { - // ReadOnly means "never serialize", which completely overrides Optional/Nullable. - // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier - // will set ShouldSerialize = false anyway. - continue; - } - // Note: WriteOnly doesn't conflict with Optional/Nullable since it only - // affects deserialization (Set), not serialization (ShouldSerialize) - - var isOptionalType = - property.PropertyType.IsGenericType - && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); - - var hasOptionalAttribute = - propertyInfo.GetCustomAttribute() is not null; - var hasNullableAttribute = - propertyInfo.GetCustomAttribute() is not null; - - if (isOptionalType && hasOptionalAttribute) - { - var originalGetter = property.Get; - if (originalGetter is not null) - { - var capturedIsNullable = hasNullableAttribute; - - property.ShouldSerialize = (obj, value) => - { - var optionalValue = originalGetter(obj); - if (optionalValue is not IOptional optional) - return false; - - if (!optional.IsDefined) - return false; - - if (!capturedIsNullable) - { - var innerValue = optional.GetBoxedValue(); - if (innerValue is null) - return false; - } - - return true; - }; - } - } - else if (hasNullableAttribute) - { - // Force serialization of nullable properties even when null - property.ShouldSerialize = (obj, value) => true; - } - } - } - - private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var propertyInfo in typeInfo.Properties) - { - var jsonAccessAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonAccessAttribute is not null) - { - propertyInfo.IsRequired = false; - switch (jsonAccessAttribute.AccessType) - { - case JsonAccessType.ReadOnly: - propertyInfo.ShouldSerialize = (_, _) => false; - break; - case JsonAccessType.WriteOnly: - propertyInfo.Set = null; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - var jsonIgnoreAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonIgnoreAttribute is not null) - { - propertyInfo.IsRequired = false; - } - } - } - - private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) - { - if ( - typeInfo.Kind == JsonTypeInfoKind.Object - && typeInfo.Properties.All(prop => !prop.IsExtensionData) - ) - { - var extensionProp = typeInfo - .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) - .FirstOrDefault(prop => - prop.GetCustomAttribute() is not null - ); - - if (extensionProp is not null) - { - var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( - extensionProp.FieldType, - extensionProp.Name - ); - jsonPropertyInfo.Get = extensionProp.GetValue; - jsonPropertyInfo.Set = extensionProp.SetValue; - jsonPropertyInfo.IsExtensionData = true; - typeInfo.Properties.Add(jsonPropertyInfo); - } - } - } - - static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); -} - -internal static class JsonUtils -{ - internal static string Serialize(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); - - internal static string Serialize(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); - - internal static string SerializeRelaxedEscaping(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static JsonElement SerializeToElement(T obj) => - JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonElement SerializeToElement(object obj, global::System.Type type) => - JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); - - internal static JsonDocument SerializeToDocument(T obj) => - JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonNode? SerializeToNode(T obj) => - JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); - - internal static byte[] SerializeToUtf8Bytes(T obj) => - JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); - - internal static string SerializeWithAdditionalProperties( - T obj, - object? additionalProperties = null - ) - { - if (additionalProperties is null) - { - return Serialize(obj); - } - var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); - if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) - { - throw new InvalidOperationException( - "The additional properties must serialize to a JSON object." - ); - } - var jsonNode = SerializeToNode(obj); - if (jsonNode is not JsonObject jsonObject) - { - throw new InvalidOperationException( - "The serialized object must be a JSON object to add properties." - ); - } - MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); - return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); - } - - private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) - { - foreach (var property in overrideObject) - { - if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) - { - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - continue; - } - if ( - existingValue is JsonObject nestedBaseObject - && property.Value is JsonObject nestedOverrideObject - ) - { - // If both values are objects, recursively merge them. - MergeJsonObjects(nestedBaseObject, nestedOverrideObject); - continue; - } - // Otherwise, the overrideObject takes precedence. - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - } - } - - internal static T Deserialize(string json) => - JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs deleted file mode 100644 index a1d30328bf9a..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/NullableAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as nullable in the OpenAPI specification. -/// When applied to Optional properties, this indicates that null values should be -/// written to JSON when the optional is defined with null. -/// -/// -/// For regular (required) properties: -/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) -/// - With [Nullable]: null values are written to JSON -/// -/// For Optional properties (also marked with [Optional]): -/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) -/// - With [Nullable]: Optional.Of(null) → write null to JSON -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs deleted file mode 100644 index 6eeb68fcba46..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/OneOfSerializer.cs +++ /dev/null @@ -1,145 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using OneOf; - -namespace SeedApi.Core; - -internal class OneOfSerializer : JsonConverter -{ - public override IOneOf? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType is JsonTokenType.Null) - return default; - - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - var readerCopy = reader; - var result = JsonSerializer.Deserialize(ref readerCopy, type, options); - reader.Skip(); - return (IOneOf)cast.Invoke(null, [result])!; - } - catch (JsonException) { } - } - - throw new JsonException( - $"Cannot deserialize into one of the supported types for {typeToConvert}" - ); - } - - public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value.Value, options); - } - - public override IOneOf ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = reader.GetString(); - if (stringValue == null) - throw new JsonException("Cannot deserialize null property name into OneOf type"); - - // Try to deserialize the string value into one of the supported types - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - // For primitive types, try direct conversion - if (type == typeof(string)) - { - return (IOneOf)cast.Invoke(null, [stringValue])!; - } - - // For other types, try to deserialize from JSON string - var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); - if (result != null) - { - return (IOneOf)cast.Invoke(null, [result])!; - } - } - catch { } - } - - // If no type-specific deserialization worked, default to string if available - var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); - if (stringType != default) - { - return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; - } - - throw new JsonException( - $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" - ); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - IOneOf value, - JsonSerializerOptions options - ) - { - // Serialize the underlying value to a string suitable for use as a dictionary key - var stringValue = value.Value?.ToString() ?? "null"; - writer.WritePropertyName(stringValue); - } - - private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( - global::System.Type typeToConvert - ) - { - var type = typeToConvert; - if (Nullable.GetUnderlyingType(type) is { } underlyingType) - { - type = underlyingType; - } - - var casts = type.GetRuntimeMethods() - .Where(m => m.IsSpecialName && m.Name == "op_Implicit") - .ToArray(); - while (type is not null) - { - if ( - type.IsGenericType - && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) - ) - { - var genericArguments = type.GetGenericArguments(); - if (genericArguments.Length == 1) - { - return [(genericArguments[0], casts[0])]; - } - - // if object type is present, make sure it is last - var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); - if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) - { - genericArguments = genericArguments - .OrderBy(t => t == typeof(object) ? 1 : 0) - .ToArray(); - } - - return genericArguments - .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) - .ToArray(); - } - - type = type.BaseType; - } - - throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); - } - - public override bool CanConvert(global::System.Type typeToConvert) - { - return typeof(IOneOf).IsAssignableFrom(typeToConvert); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs deleted file mode 100644 index d174943cb2cf..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/Optional.cs +++ /dev/null @@ -1,474 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Non-generic interface for Optional types to enable reflection-free checks. -/// -public interface IOptional -{ - /// - /// Returns true if the value is defined (set), even if the value is null. - /// - bool IsDefined { get; } - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - object? GetBoxedValue(); -} - -/// -/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). -/// Use this for HTTP PATCH requests where you need to distinguish between: -/// -/// Undefined: Don't send this field (leave it unchanged on the server) -/// Defined with null: Send null (clear the field on the server) -/// Defined with value: Send the value (update the field on the server) -/// -/// -/// The type of the value. Use nullable types (T?) for fields that can be null. -/// -/// For nullable string fields, use Optional<string?>: -/// -/// public class UpdateUserRequest -/// { -/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; -/// } -/// -/// var request = new UpdateUserRequest -/// { -/// Name = "John" // Will send: { "name": "John" } -/// }; -/// -/// var request2 = new UpdateUserRequest -/// { -/// Name = Optional<string?>.Of(null) // Will send: { "name": null } -/// }; -/// -/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) -/// -/// -public readonly struct Optional : IOptional, IEquatable> -{ - private readonly T _value; - private readonly bool _isDefined; - - private Optional(T value, bool isDefined) - { - _value = value; - _isDefined = isDefined; - } - - /// - /// Creates an undefined value - the field will not be included in the HTTP request. - /// Use this as the default value for optional fields. - /// - /// - /// - /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; - /// - /// - public static Optional Undefined => new(default!, false); - - /// - /// Creates a defined value - the field will be included in the HTTP request. - /// The value can be null if T is a nullable type. - /// - /// The value to set. Can be null if T is nullable (e.g., string?, int?). - /// - /// - /// // Set to a value - /// request.Name = Optional<string?>.Of("John"); - /// - /// // Set to null (clears the field) - /// request.Email = Optional<string?>.Of(null); - /// - /// // Or use implicit conversion - /// request.Name = "John"; // Same as Of("John") - /// request.Email = null; // Same as Of(null) - /// - /// - public static Optional Of(T value) => new(value, true); - - /// - /// Returns true if the field is defined (set), even if the value is null. - /// Use this to determine if the field should be included in the HTTP request. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// requestBody["name"] = request.Name.Value; // Include in request (can be null) - /// } - /// - /// - public bool IsDefined => _isDefined; - - /// - /// Returns true if the field is undefined (not set). - /// Use this to check if the field should be excluded from the HTTP request. - /// - /// - /// - /// if (request.Email.IsUndefined) - /// { - /// // Don't include email in the request - leave it unchanged - /// } - /// - /// - public bool IsUndefined => !_isDefined; - - /// - /// Gets the value. The value may be null if T is a nullable type. - /// - /// Thrown if the value is undefined. - /// - /// Always check before accessing Value, or use instead. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> - /// } - /// - /// // Or check for null explicitly - /// if (request.Email.IsDefined && request.Email.Value is null) - /// { - /// // Email is explicitly set to null (clear it) - /// } - /// - /// - public T Value - { - get - { - if (!_isDefined) - throw new InvalidOperationException("Optional value is undefined"); - return _value; - } - } - - /// - /// Gets the value if defined, otherwise returns the specified default value. - /// Note: If the value is defined as null, this returns null (not the default). - /// - /// The value to return if undefined. - /// The actual value if defined (can be null), otherwise the default value. - /// - /// - /// string name = request.Name.GetValueOrDefault("Anonymous"); - /// // If Name is undefined: returns "Anonymous" - /// // If Name is Of(null): returns null - /// // If Name is Of("John"): returns "John" - /// - /// - public T GetValueOrDefault(T defaultValue = default!) - { - return _isDefined ? _value : defaultValue; - } - - /// - /// Tries to get the value. Returns true if the value is defined (even if null). - /// - /// - /// When this method returns, contains the value if defined, or default(T) if undefined. - /// The value may be null if T is nullable. - /// - /// True if the value is defined; otherwise, false. - /// - /// - /// if (request.Email.TryGetValue(out var email)) - /// { - /// requestBody["email"] = email; // email can be null - /// } - /// else - /// { - /// // Email is undefined - don't include in request - /// } - /// - /// - public bool TryGetValue(out T value) - { - if (_isDefined) - { - value = _value; - return true; - } - value = default!; - return false; - } - - /// - /// Implicitly converts a value to Optional<T>.Of(value). - /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). - /// - /// The value to convert (can be null if T is nullable). - public static implicit operator Optional(T value) => Of(value); - - /// - /// Returns a string representation of this Optional value. - /// - /// "Undefined" if not set, or "Defined(value)" if set. - public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - public object? GetBoxedValue() - { - if (!_isDefined) - return null; - return _value; - } - - /// - public bool Equals(Optional other) => - _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is Optional other && Equals(other); - - /// - public override int GetHashCode() - { - if (!_isDefined) - return 0; - unchecked - { - int hash = 17; - hash = hash * 31 + 1; // _isDefined = true - hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); - return hash; - } - } - - /// - /// Determines whether two Optional values are equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are equal; otherwise, false. - public static bool operator ==(Optional left, Optional right) => left.Equals(right); - - /// - /// Determines whether two Optional values are not equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are not equal; otherwise, false. - public static bool operator !=(Optional left, Optional right) => !left.Equals(right); -} - -/// -/// Extension methods for Optional to simplify common operations. -/// -public static class OptionalExtensions -{ - /// - /// Adds the value to a dictionary if the optional is defined (even if the value is null). - /// This is useful for building JSON request payloads where null values should be included. - /// - /// The type of the optional value. - /// The optional value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined - /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined - /// - /// - public static void AddTo( - this Optional optional, - Dictionary dictionary, - string key - ) - { - if (optional.IsDefined) - { - dictionary[key] = optional.Value; - } - } - - /// - /// Executes an action if the optional is defined. - /// - /// The type of the optional value. - /// The optional value. - /// The action to execute with the value. - /// - /// - /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); - /// - /// - public static void IfDefined(this Optional optional, Action action) - { - if (optional.IsDefined) - { - action(optional.Value); - } - } - - /// - /// Maps the value to a new type if the optional is defined, otherwise returns undefined. - /// - /// The type of the original value. - /// The type to map to. - /// The optional value to map. - /// The mapping function. - /// An optional containing the mapped value if defined, otherwise undefined. - /// - /// - /// Optional<string?> name = Optional<string?>.Of("John"); - /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) - /// - /// - public static Optional Map( - this Optional optional, - Func mapper - ) - { - return optional.IsDefined - ? Optional.Of(mapper(optional.Value)) - : Optional.Undefined; - } - - /// - /// Adds a nullable value to a dictionary only if it is not null. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The type of the value (must be a reference type or Nullable). - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : class - { - if (value is not null) - { - dictionary[key] = value; - } - } - - /// - /// Adds a nullable value type to a dictionary only if it has a value. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The underlying value type. - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : struct - { - if (value.HasValue) - { - dictionary[key] = value.Value; - } - } -} - -/// -/// JSON converter factory for Optional that handles undefined vs null correctly. -/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. -/// -public class OptionalJsonConverterFactory : JsonConverterFactory -{ - public override bool CanConvert(global::System.Type typeToConvert) - { - if (!typeToConvert.IsGenericType) - return false; - - return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); - } - - public override JsonConverter? CreateConverter( - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var valueType = typeToConvert.GetGenericArguments()[0]; - var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); - return (JsonConverter?)global::System.Activator.CreateInstance(converterType); - } -} - -/// -/// JSON converter for Optional that unwraps the value during serialization. -/// The actual property skipping is handled by the OptionalTypeInfoResolver. -/// -public class OptionalJsonConverter : JsonConverter> -{ - public override Optional Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return Optional.Of(default!); - } - - var value = JsonSerializer.Deserialize(ref reader, options); - return Optional.Of(value!); - } - - public override void Write( - Utf8JsonWriter writer, - Optional value, - JsonSerializerOptions options - ) - { - // This will be called by the serializer - // We need to unwrap and serialize the inner value - // The TypeInfoResolver will handle skipping undefined values - - if (value.IsUndefined) - { - // This shouldn't be called for undefined values due to ShouldSerialize - // But if it is, write null and let the resolver filter it - writer.WriteNullValue(); - return; - } - - // Get the inner value - var innerValue = value.Value; - - // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) - if (innerValue is null) - { - writer.WriteNullValue(); - return; - } - - // Serialize the unwrapped value - JsonSerializer.Serialize(writer, innerValue, options); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs deleted file mode 100644 index 4c4c4073a0ae..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/OptionalAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as optional in the OpenAPI specification. -/// Optional properties use the Optional type and can be undefined (not present in JSON). -/// -/// -/// Properties marked with [Optional] should use the Optional type: -/// - Undefined: Optional.Undefined → omitted from JSON -/// - Defined: Optional.Of(value) → written to JSON -/// -/// Combine with [Nullable] to allow null values: -/// - [Optional, Nullable] Optional → can be undefined, null, or a value -/// - [Optional] Optional → can be undefined or a value (null is invalid) -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs deleted file mode 100644 index 8b43322350bd..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs +++ /dev/null @@ -1,353 +0,0 @@ -using global::System.Collections; -using global::System.Collections.ObjectModel; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using SeedApi.Core; - -namespace SeedApi; - -public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties -{ - internal ReadOnlyAdditionalProperties() { } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record ReadOnlyAdditionalProperties : IReadOnlyDictionary -{ - private readonly Dictionary _extensionData = new(); - private readonly Dictionary _convertedCache = new(); - - internal ReadOnlyAdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - if (kvp.Value is JsonElement element) - { - _extensionData.Add(kvp.Key, element); - } - else - { - _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); - } - - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(JsonElement value) - { - if (typeof(T) == typeof(JsonElement)) - { - return (T)(object)value; - } - - return value.Deserialize(JsonOptions.JsonSerializerOptions)!; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var cached)) - { - return cached; - } - - var value = ConvertToT(_extensionData[key]); - _convertedCache[key] = value; - return value; - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public int Count => _extensionData.Count; - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var element)) - { - value = ConvertToT(element); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public T this[string key] => GetCached(key); - - public IEnumerable Keys => _extensionData.Keys; - - public IEnumerable Values => Keys.Select(GetCached); -} - -public record AdditionalProperties : AdditionalProperties -{ - public AdditionalProperties() { } - - public AdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record AdditionalProperties : IDictionary -{ - private readonly Dictionary _extensionData; - private readonly Dictionary _convertedCache; - - public AdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - public AdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - _extensionData[kvp.Key] = kvp.Value; - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(object? extensionDataValue) - { - return extensionDataValue switch - { - T value => value, - JsonElement jsonElement => jsonElement.Deserialize( - JsonOptions.JsonSerializerOptions - )!, - JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, - _ => JsonUtils - .SerializeToElement(extensionDataValue) - .Deserialize(JsonOptions.JsonSerializerOptions)!, - }; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - internal void CopyToExtensionData(IDictionary extensionData) - { - extensionData.Clear(); - foreach (var kvp in _extensionData) - { - extensionData[kvp.Key] = kvp.Value; - } - } - - public JsonObject ToJsonObject() => - ( - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ) - ).AsObject(); - - public JsonNode ToJsonNode() => - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ); - - public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); - - public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); - - public IReadOnlyDictionary ToJsonElementDictionary() - { - return new ReadOnlyDictionary( - _extensionData.ToDictionary( - kvp => kvp.Key, - kvp => - { - if (kvp.Value is JsonElement jsonElement) - { - return jsonElement; - } - - return JsonUtils.SerializeToElement(kvp.Value); - } - ) - ); - } - - public ICollection Keys => _extensionData.Keys; - - public ICollection Values - { - get - { - var values = new T[_extensionData.Count]; - var i = 0; - foreach (var key in Keys) - { - values[i++] = GetCached(key); - } - - return values; - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var value)) - { - return value; - } - - value = ConvertToT(_extensionData[key]); - _convertedCache.Add(key, value); - return value; - } - - private void SetCached(string key, T value) - { - _extensionData[key] = value; - _convertedCache[key] = value; - } - - private void AddCached(string key, T value) - { - _extensionData.Add(key, value); - _convertedCache.Add(key, value); - } - - private bool RemoveCached(string key) - { - var isRemoved = _extensionData.Remove(key); - _convertedCache.Remove(key); - return isRemoved; - } - - public int Count => _extensionData.Count; - public bool IsReadOnly => false; - - public T this[string key] - { - get => GetCached(key); - set => SetCached(key, value); - } - - public void Add(string key, T value) => AddCached(key, value); - - public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); - - public bool Remove(string key) => RemoveCached(key); - - public bool Remove(KeyValuePair item) => RemoveCached(item.Key); - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool Contains(KeyValuePair item) - { - return _extensionData.ContainsKey(item.Key) - && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); - } - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var extensionDataValue)) - { - value = ConvertToT(extensionDataValue); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public void Clear() - { - _extensionData.Clear(); - _convertedCache.Clear(); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array is null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (arrayIndex < 0 || arrayIndex > array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } - - if (array.Length - arrayIndex < _extensionData.Count) - { - throw new ArgumentException( - "The array does not have enough space to copy the elements." - ); - } - - foreach (var kvp in _extensionData) - { - array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); - } - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs deleted file mode 100644 index f33d49028884..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/FileParameter.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace SeedApi; - -/// -/// File parameter for uploading files. -/// -public record FileParameter : IDisposable -#if NET6_0_OR_GREATER - , IAsyncDisposable -#endif -{ - private bool _disposed; - - /// - /// The name of the file to be uploaded. - /// - public string? FileName { get; set; } - - /// - /// The content type of the file to be uploaded. - /// - public string? ContentType { get; set; } - - /// - /// The content of the file to be uploaded. - /// - public required Stream Stream { get; set; } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - if (disposing) - { - Stream.Dispose(); - } - - _disposed = true; - } - -#if NET6_0_OR_GREATER - /// - public async ValueTask DisposeAsync() - { - if (!_disposed) - { - await Stream.DisposeAsync().ConfigureAwait(false); - _disposed = true; - } - - GC.SuppressFinalize(this); - } -#endif - - public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs deleted file mode 100644 index 3d210b7e0b4c..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/Public/Version.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -[Serializable] -internal class Version -{ - public const string Current = "0.0.1"; -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs deleted file mode 100644 index 9f1f4a1c1181..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnum.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -public interface IStringEnum : IEquatable -{ - public string Value { get; } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs deleted file mode 100644 index 704cb6836ab8..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -internal static class StringEnumExtensions -{ - public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Describable.cs b/seed/csharp-model/allof-inline/src/SeedApi/Describable.cs deleted file mode 100644 index f521e9082be7..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Describable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Describable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Display name from Describable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs b/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs deleted file mode 100644 index 7bc064682236..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrg.cs +++ /dev/null @@ -1,28 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("metadata")] - public DetailedOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs b/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs deleted file mode 100644 index 798903997238..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/DetailedOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from DetailedOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Custom domain name. - /// - [JsonPropertyName("domain")] - public string? Domain { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs b/seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs deleted file mode 100644 index 05b3808b610b..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Identifiable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Identifiable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Identifiable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/Organization.cs b/seed/csharp-model/allof-inline/src/SeedApi/Organization.cs deleted file mode 100644 index 602a7e16c442..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/Organization.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Organization : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public OrganizationMetadata? Metadata { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs b/seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs deleted file mode 100644 index 084daaac298a..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/OrganizationMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record OrganizationMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from DetailedOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Custom domain name. - /// - [JsonPropertyName("domain")] - public string? Domain { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs b/seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs deleted file mode 100644 index 9f4b1b5c0820..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/PaginatedResult.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PaginatedResult : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable Results { get; set; } = new List(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs b/seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs deleted file mode 100644 index b1f0001a4733..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/PagingCursors.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PagingCursors : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Cursor for the next page of results. - /// - [JsonPropertyName("next")] - public required string Next { get; set; } - - /// - /// Cursor for the previous page of results. - /// - [JsonPropertyName("previous")] - public string? Previous { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs deleted file mode 100644 index d22a26079f4e..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/RuleExecutionContext.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] -[Serializable] -public readonly record struct RuleExecutionContext : IStringEnum -{ - public static readonly RuleExecutionContext Prod = new(Values.Prod); - - public static readonly RuleExecutionContext Staging = new(Values.Staging); - - public static readonly RuleExecutionContext Dev = new(Values.Dev); - - public RuleExecutionContext(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleExecutionContext FromCustom(string value) - { - return new RuleExecutionContext(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleExecutionContext value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleExecutionContext value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleExecutionContext value) => value.Value; - - public static explicit operator RuleExecutionContext(string value) => new(value); - - internal class RuleExecutionContextSerializer : JsonConverter - { - public override RuleExecutionContext Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleExecutionContext ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Prod = "prod"; - - public const string Staging = "staging"; - - public const string Dev = "dev"; - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs deleted file mode 100644 index 78e1a0e6d50e..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/RuleResponse.cs +++ /dev/null @@ -1,65 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("status")] - public required RuleResponseStatus Status { get; set; } - - [JsonPropertyName("executionContext")] - public RuleExecutionContext? ExecutionContext { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs deleted file mode 100644 index 9b587cdbcba0..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/RuleResponseStatus.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] -[Serializable] -public readonly record struct RuleResponseStatus : IStringEnum -{ - public static readonly RuleResponseStatus Active = new(Values.Active); - - public static readonly RuleResponseStatus Inactive = new(Values.Inactive); - - public static readonly RuleResponseStatus Draft = new(Values.Draft); - - public RuleResponseStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleResponseStatus FromCustom(string value) - { - return new RuleResponseStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleResponseStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleResponseStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleResponseStatus value) => value.Value; - - public static explicit operator RuleResponseStatus(string value) => new(value); - - internal class RuleResponseStatusSerializer : JsonConverter - { - public override RuleResponseStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleResponseStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Inactive = "inactive"; - - public const string Draft = "draft"; - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs deleted file mode 100644 index 578b90315dde..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/RuleType.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleType : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("description")] - public string? Description { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs b/seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs deleted file mode 100644 index e05899151a38..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/RuleTypeSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleTypeSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props b/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props deleted file mode 100644 index 17a84cada530..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.Custom.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj b/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj deleted file mode 100644 index 6ba194fdf143..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/SeedApi.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - net462;net8.0;net9.0;netstandard2.0 - enable - 12 - enable - 0.0.1 - $(Version) - $(Version) - README.md - https://github.com/allof-inline/fern - true - - - - false - - - $(DefineConstants);USE_PORTABLE_DATE_ONLY - true - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - <_Parameter1>SeedApi.Test - - - - - diff --git a/seed/csharp-model/allof-inline/src/SeedApi/User.cs b/seed/csharp-model/allof-inline/src/SeedApi/User.cs deleted file mode 100644 index abc389c0a6b6..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/User.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record User : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("email")] - public required string Email { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs b/seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs deleted file mode 100644 index 55c5368d774e..000000000000 --- a/seed/csharp-model/allof-inline/src/SeedApi/UserSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record UserSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/.editorconfig b/seed/csharp-model/allof/.editorconfig deleted file mode 100644 index 1e7a0adbac80..000000000000 --- a/seed/csharp-model/allof/.editorconfig +++ /dev/null @@ -1,35 +0,0 @@ -root = true - -[*.cs] -resharper_arrange_object_creation_when_type_evident_highlighting = hint -resharper_auto_property_can_be_made_get_only_global_highlighting = hint -resharper_check_namespace_highlighting = hint -resharper_class_never_instantiated_global_highlighting = hint -resharper_class_never_instantiated_local_highlighting = hint -resharper_collection_never_updated_global_highlighting = hint -resharper_convert_type_check_pattern_to_null_check_highlighting = hint -resharper_inconsistent_naming_highlighting = hint -resharper_member_can_be_private_global_highlighting = hint -resharper_member_hides_static_from_outer_class_highlighting = hint -resharper_not_accessed_field_local_highlighting = hint -resharper_nullable_warning_suppression_is_used_highlighting = suggestion -resharper_partial_type_with_single_part_highlighting = hint -resharper_prefer_concrete_value_over_default_highlighting = none -resharper_private_field_can_be_converted_to_local_variable_highlighting = hint -resharper_property_can_be_made_init_only_global_highlighting = hint -resharper_property_can_be_made_init_only_local_highlighting = hint -resharper_redundant_name_qualifier_highlighting = none -resharper_redundant_using_directive_highlighting = hint -resharper_replace_slice_with_range_indexer_highlighting = none -resharper_unused_auto_property_accessor_global_highlighting = hint -resharper_unused_auto_property_accessor_local_highlighting = hint -resharper_unused_member_global_highlighting = hint -resharper_unused_type_global_highlighting = hint -resharper_use_string_interpolation_highlighting = hint -dotnet_diagnostic.CS1591.severity = suggestion - -[src/**/Types/*.cs] -resharper_check_namespace_highlighting = none - -[src/**/Core/Public/*.cs] -resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-model/allof/.fern/metadata.json b/seed/csharp-model/allof/.fern/metadata.json deleted file mode 100644 index bdf3cbf8c785..000000000000 --- a/seed/csharp-model/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-csharp-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/csharp-model/allof/.github/workflows/ci.yml b/seed/csharp-model/allof/.github/workflows/ci.yml deleted file mode 100644 index 87068349b616..000000000000 --- a/seed/csharp-model/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -env: - DOTNET_NOLOGO: true - -jobs: - ci: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.x - - - name: Install tools - run: dotnet tool restore - - - name: Restore dependencies - run: dotnet restore src/SeedApi/SeedApi.csproj - - - name: Build - run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release - - - name: Restore test dependencies - run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj - - - name: Build tests - run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release - - - name: Test - run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release - - - name: Pack - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release - - - name: Publish to NuGet.org - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} - run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" - diff --git a/seed/csharp-model/allof/.gitignore b/seed/csharp-model/allof/.gitignore deleted file mode 100644 index 11014f2b33d7..000000000000 --- a/seed/csharp-model/allof/.gitignore +++ /dev/null @@ -1,484 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -## This is based on `dotnet new gitignore` and customized by Fern - -# dotenv files -.env - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -# [Rr]elease/ (Ignored by Fern) -# [Rr]eleases/ (Ignored by Fern) -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -# [Ll]og/ (Ignored by Fern) -# [Ll]ogs/ (Ignored by Fern) - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET -project.lock.json -project.fragment.lock.json -artifacts/ - -# Tye -.tye/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml -.idea - -## -## Visual studio for Mac -## - - -# globs -Makefile.in -*.userprefs -*.usertasks -config.make -config.status -aclocal.m4 -install-sh -autom4te.cache/ -*.tar.gz -tarballs/ -test-results/ - -# Mac bundle stuff -*.dmg -*.app - -# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Vim temporary swap files -*.swp diff --git a/seed/csharp-model/allof/SeedApi.slnx b/seed/csharp-model/allof/SeedApi.slnx deleted file mode 100644 index d4c63c241aad..000000000000 --- a/seed/csharp-model/allof/SeedApi.slnx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/seed/csharp-model/allof/snippet.json b/seed/csharp-model/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs deleted file mode 100644 index a12183113312..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs +++ /dev/null @@ -1,365 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class AdditionalPropertiesTests -{ - [Test] - public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); - Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); - }); - } - - [Test] - public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecord - { - Id = "1", - AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.Id, Is.EqualTo("1")); - Assert.That( - deserializedRecord.AdditionalProperties["category"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), - Is.EqualTo("fiction") - ); - Assert.That( - deserializedRecord.AdditionalProperties["title"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() - { - // Arrange - var extensionData = new Dictionary - { - ["key1"] = JsonUtils.SerializeToElement("value1"), - ["key2"] = JsonUtils.SerializeToElement(123), - }; - var readOnlyProps = new ReadOnlyAdditionalProperties(); - readOnlyProps.CopyFromExtensionData(extensionData); - - // Act & Assert - Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); - Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); - } - - [Test] - public void AdditionalProperties_ShouldBehaveAsDictionary() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - additionalProps["key3"] = true; - - // Assert - Assert.Multiple(() => - { - Assert.That(additionalProps["key1"], Is.EqualTo("value1")); - Assert.That(additionalProps["key2"], Is.EqualTo(123)); - Assert.That((bool)additionalProps["key3"]!, Is.True); - Assert.That(additionalProps.Count, Is.EqualTo(3)); - }); - } - - [Test] - public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - var jsonObject = additionalProps.ToJsonObject(); - - Assert.Multiple(() => - { - // Assert - Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); - Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); - }); - } - - [Test] - public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - var record = JsonUtils.Deserialize(json); - - // Act - record.AdditionalProperties["category"] = "non-fiction"; - - // Assert - Assert.Multiple(() => - { - Assert.That(record, Is.Not.Null); - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); - Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); - Assert.That( - ((JsonElement)record.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": 42, - "extra2": 99 - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithInts - { - AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": { "key1": true, "key2": false }, - "extra2": { "key3": true } - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithDictionaries - { - AdditionalProperties = - { - ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, - ["extra2"] = new Dictionary { { "key3", true } }, - }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - private record Record : IJsonOnDeserialized - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithInts : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithDictionaries : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties< - Dictionary - > AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties> AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs deleted file mode 100644 index c0f258680b78..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateOnlyJsonTests -{ - [Test] - public void SerializeDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly? dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly? expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateOnlyKey() - { - var key = new DateOnly(2023, 10, 5); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateOnlyKey() - { - var json = """ - { - "2023-10-05": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateOnly(2023, 10, 5); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs deleted file mode 100644 index 1dde45a8e939..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateTimeJsonTests -{ - [Test] - public void SerializeDateTime_ShouldMatchExpectedFormat() - { - (DateTime dateTime, string expected)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - foreach (var (dateTime, expected) in testCases) - { - var json = JsonUtils.Serialize(dateTime); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateTime_ShouldMatchExpectedDateTime() - { - (DateTime expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateTime_ShouldMatchExpectedFormat() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateTimeKey() - { - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateTimeKey() - { - var json = """ - { - "2023-10-05T14:30:00.000Z": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs deleted file mode 100644 index 969acd620998..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class JsonAccessAttributeTests -{ - private class MyClass - { - [JsonPropertyName("read_only_prop")] - [JsonAccess(JsonAccessType.ReadOnly)] - public string? ReadOnlyProp { get; set; } - - [JsonPropertyName("write_only_prop")] - [JsonAccess(JsonAccessType.WriteOnly)] - public string? WriteOnlyProp { get; set; } - - [JsonPropertyName("normal_prop")] - public string? NormalProp { get; set; } - - [JsonPropertyName("read_only_nullable_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable? ReadOnlyNullableList { get; set; } - - [JsonPropertyName("read_only_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable ReadOnlyList { get; set; } = []; - - [JsonPropertyName("write_only_nullable_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable? WriteOnlyNullableList { get; set; } - - [JsonPropertyName("write_only_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable WriteOnlyList { get; set; } = []; - - [JsonPropertyName("normal_list")] - public IEnumerable NormalList { get; set; } = []; - - [JsonPropertyName("normal_nullable_list")] - public IEnumerable? NullableNormalList { get; set; } - } - - [Test] - public void JsonAccessAttribute_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "write_only_prop": "write", - "normal_prop": "normal_prop", - "read_only_nullable_list": ["item1", "item2"], - "read_only_list": ["item3", "item4"], - "write_only_nullable_list": ["item5", "item6"], - "write_only_list": ["item7", "item8"], - "normal_list": ["normal1", "normal2"], - "normal_nullable_list": ["normal1", "normal2"] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // String properties - Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); - Assert.That(obj.WriteOnlyProp, Is.Null); - Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); - - // List properties - read only - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Not.Null); - Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); - Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); - Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); - - var readOnlyList = obj.ReadOnlyList.ToArray(); - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Has.Length.EqualTo(2)); - Assert.That(readOnlyList[0], Is.EqualTo("item3")); - Assert.That(readOnlyList[1], Is.EqualTo("item4")); - - // List properties - write only - Assert.That(obj.WriteOnlyNullableList, Is.Null); - Assert.That(obj.WriteOnlyList, Is.Not.Null); - Assert.That(obj.WriteOnlyList, Is.Empty); - - // Normal list property - var normalList = obj.NormalList.ToArray(); - Assert.That(normalList, Is.Not.Null); - Assert.That(normalList, Has.Length.EqualTo(2)); - Assert.That(normalList[0], Is.EqualTo("normal1")); - Assert.That(normalList[1], Is.EqualTo("normal2")); - }); - - // Set up values for serialization - obj.WriteOnlyProp = "write"; - obj.NormalProp = "new_value"; - obj.WriteOnlyNullableList = new List { "write1", "write2" }; - obj.WriteOnlyList = new List { "write3", "write4" }; - obj.NormalList = new List { "new_normal" }; - obj.NullableNormalList = new List { "new_normal" }; - - var serializedJson = JsonUtils.Serialize(obj); - const string expectedJson = """ - { - "write_only_prop": "write", - "normal_prop": "new_value", - "write_only_nullable_list": [ - "write1", - "write2" - ], - "write_only_list": [ - "write3", - "write4" - ], - "normal_list": [ - "new_normal" - ], - "normal_nullable_list": [ - "new_normal" - ] - } - """; - Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); - } - - [Test] - public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "normal_prop": "normal_prop", - "read_only_nullable_list": null, - "read_only_list": [] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // Read-only nullable list should be null when JSON contains null - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Null); - - // Read-only non-nullable list should never be null, but empty when JSON contains null - var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Is.Empty); - }); - - // Serialize and verify read-only lists are not included - var serializedJson = JsonUtils.Serialize(obj); - Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); - Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); - Assert.That(serializedJson, Does.Not.Contain("read_only_list")); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs b/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs deleted file mode 100644 index 42d165830baf..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Core/Json/OneOfSerializerTests.cs +++ /dev/null @@ -1,314 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using OneOf; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -[Parallelizable(ParallelScope.All)] -public class OneOfSerializerTests -{ - private class Foo - { - [JsonPropertyName("string_prop")] - public required string StringProp { get; set; } - } - - private class Bar - { - [JsonPropertyName("int_prop")] - public required int IntProp { get; set; } - } - - private static readonly OneOf OneOf1 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT2(new { }); - private const string OneOf1String = "{}"; - - private static readonly OneOf OneOf2 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT0("test"); - private const string OneOf2String = "\"test\""; - - private static readonly OneOf OneOf3 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT1(123); - private const string OneOf3String = "123"; - - private static readonly OneOf OneOf4 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT3(new Foo { StringProp = "test" }); - private const string OneOf4String = "{\"string_prop\": \"test\"}"; - - private static readonly OneOf OneOf5 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT4(new Bar { IntProp = 5 }); - private const string OneOf5String = "{\"int_prop\": 5}"; - - [Test] - public void Serialize_OneOfs_Should_Return_Expected_String() - { - (OneOf, string)[] testData = - [ - (OneOf1, OneOf1String), - (OneOf2, OneOf2String), - (OneOf3, OneOf3String), - (OneOf4, OneOf4String), - (OneOf5, OneOf5String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, expected) in testData) - { - var result = JsonUtils.Serialize(oneOf); - Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void OneOfs_Should_Deserialize_From_String() - { - (OneOf, string)[] testData = - [ - (OneOf1, OneOf1String), - (OneOf2, OneOf2String), - (OneOf3, OneOf3String), - (OneOf4, OneOf4String), - (OneOf5, OneOf5String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, json) in testData) - { - var result = JsonUtils.Deserialize>(json); - Assert.That(result.Index, Is.EqualTo(oneOf.Index)); - Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); - } - }); - } - - private static readonly OneOf? NullableOneOf1 = null; - private const string NullableOneOf1String = "null"; - - private static readonly OneOf? NullableOneOf2 = OneOf< - string, - int, - object, - Foo, - Bar - >.FromT4(new Bar { IntProp = 5 }); - private const string NullableOneOf2String = "{\"int_prop\": 5}"; - - [Test] - public void Serialize_NullableOneOfs_Should_Return_Expected_String() - { - (OneOf?, string)[] testData = - [ - (NullableOneOf1, NullableOneOf1String), - (NullableOneOf2, NullableOneOf2String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, expected) in testData) - { - var result = JsonUtils.Serialize(oneOf); - Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void NullableOneOfs_Should_Deserialize_From_String() - { - (OneOf?, string)[] testData = - [ - (NullableOneOf1, NullableOneOf1String), - (NullableOneOf2, NullableOneOf2String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, json) in testData) - { - var result = JsonUtils.Deserialize?>(json); - Assert.That(result?.Index, Is.EqualTo(oneOf?.Index)); - Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result?.Value)).IgnoreWhiteSpace); - } - }); - } - - private static readonly OneOf OneOfWithNullable1 = OneOf< - string, - int, - Foo? - >.FromT2(null); - private const string OneOfWithNullable1String = "null"; - - private static readonly OneOf OneOfWithNullable2 = OneOf< - string, - int, - Foo? - >.FromT2(new Foo { StringProp = "test" }); - private const string OneOfWithNullable2String = "{\"string_prop\": \"test\"}"; - - private static readonly OneOf OneOfWithNullable3 = OneOf< - string, - int, - Foo? - >.FromT0("test"); - private const string OneOfWithNullable3String = "\"test\""; - - [Test] - public void Serialize_OneOfWithNullables_Should_Return_Expected_String() - { - (OneOf, string)[] testData = - [ - (OneOfWithNullable1, OneOfWithNullable1String), - (OneOfWithNullable2, OneOfWithNullable2String), - (OneOfWithNullable3, OneOfWithNullable3String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, expected) in testData) - { - var result = JsonUtils.Serialize(oneOf); - Assert.That(result, Is.EqualTo(expected).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void OneOfWithNullables_Should_Deserialize_From_String() - { - (OneOf, string)[] testData = - [ - // (OneOfWithNullable1, OneOfWithNullable1String), // not possible with .NET's JSON serializer - (OneOfWithNullable2, OneOfWithNullable2String), - (OneOfWithNullable3, OneOfWithNullable3String), - ]; - Assert.Multiple(() => - { - foreach (var (oneOf, json) in testData) - { - var result = JsonUtils.Deserialize>(json); - Assert.That(result.Index, Is.EqualTo(oneOf.Index)); - Assert.That(json, Is.EqualTo(JsonUtils.Serialize(result.Value)).IgnoreWhiteSpace); - } - }); - } - - [Test] - public void Serialize_OneOfWithObjectLast_Should_Return_Expected_String() - { - var oneOfWithObjectLast = OneOf.FromT4( - new { random = "data" } - ); - const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; - - var result = JsonUtils.Serialize(oneOfWithObjectLast); - Assert.That(result, Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace); - } - - [Test] - public void OneOfWithObjectLast_Should_Deserialize_From_String() - { - const string oneOfWithObjectLastString = "{\"random\": \"data\"}"; - var result = JsonUtils.Deserialize>( - oneOfWithObjectLastString - ); - Assert.Multiple(() => - { - Assert.That(result.Index, Is.EqualTo(4)); - Assert.That(result.Value, Is.InstanceOf()); - Assert.That( - JsonUtils.Serialize(result.Value), - Is.EqualTo(oneOfWithObjectLastString).IgnoreWhiteSpace - ); - }); - } - - [Test] - public void Serialize_OneOfWithObjectNotLast_Should_Return_Expected_String() - { - var oneOfWithObjectNotLast = OneOf.FromT1( - new { random = "data" } - ); - const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; - - var result = JsonUtils.Serialize(oneOfWithObjectNotLast); - Assert.That(result, Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace); - } - - [Test] - public void OneOfWithObjectNotLast_Should_Deserialize_From_String() - { - const string oneOfWithObjectNotLastString = "{\"random\": \"data\"}"; - var result = JsonUtils.Deserialize>( - oneOfWithObjectNotLastString - ); - Assert.Multiple(() => - { - Assert.That(result.Index, Is.EqualTo(1)); - Assert.That(result.Value, Is.InstanceOf()); - Assert.That( - JsonUtils.Serialize(result.Value), - Is.EqualTo(oneOfWithObjectNotLastString).IgnoreWhiteSpace - ); - }); - } - - [Test] - public void Serialize_OneOfSingleType_Should_Return_Expected_String() - { - var oneOfSingle = OneOf.FromT0("single"); - const string oneOfSingleString = "\"single\""; - - var result = JsonUtils.Serialize(oneOfSingle); - Assert.That(result, Is.EqualTo(oneOfSingleString).IgnoreWhiteSpace); - } - - [Test] - public void OneOfSingleType_Should_Deserialize_From_String() - { - const string oneOfSingleString = "\"single\""; - var result = JsonUtils.Deserialize>(oneOfSingleString); - Assert.Multiple(() => - { - Assert.That(result.Index, Is.EqualTo(0)); - Assert.That(result.Value, Is.EqualTo("single")); - }); - } - - [Test] - public void Deserialize_InvalidData_Should_Throw_Exception() - { - const string invalidJson = "{\"invalid\": \"data\"}"; - - Assert.Throws(() => - { - JsonUtils.Deserialize>(invalidJson); - }); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props deleted file mode 100644 index aac9b5020d80..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.Custom.props +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj deleted file mode 100644 index 77e1a9943739..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/SeedApi.Test.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - net9.0 - 12 - enable - enable - false - true - true - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs deleted file mode 100644 index 3ac7e5310f95..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs +++ /dev/null @@ -1,219 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; -using SeedApi; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle AdditionalProperties values. -/// -public static class AdditionalPropertiesComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their - /// serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) - { - constraint.Using( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - /// - /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing - /// their serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) - { - constraint.Using>( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => - JsonElementsAreEqual(x, y); - - private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) - { - if (x.ValueKind != y.ValueKind) - { - return false; - } - - return x.ValueKind switch - { - JsonValueKind.Object => CompareJsonObjects(x, y), - JsonValueKind.Array => CompareJsonArrays(x, y), - JsonValueKind.String => x.GetString() == y.GetString(), - JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), - JsonValueKind.True => true, - JsonValueKind.False => true, - JsonValueKind.Null => true, - _ => false, - }; - } - - private static bool CompareJsonObjects(JsonElement x, JsonElement y) - { - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - if (xProps.Count != yProps.Count) - { - return false; - } - - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - return false; - } - - if (!JsonElementsAreEqual(xProps[key], yProps[key])) - { - return false; - } - } - - return true; - } - - private static bool CompareJsonArrays(JsonElement x, JsonElement y) - { - var xArray = x.EnumerateArray().ToList(); - var yArray = y.EnumerateArray().ToList(); - - if (xArray.Count != yArray.Count) - { - return false; - } - - for (var i = 0; i < xArray.Count; i++) - { - if (!JsonElementsAreEqual(xArray[i], yArray[i])) - { - return false; - } - } - - return true; - } - - /// - /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. - /// When UsingPropertiesComparer() walks object properties and encounters a property typed as - /// 'object', the expected side may be a Dictionary<object, object?> while the actual - /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing - /// the non-JsonElement side and comparing JSON representations. - /// - /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic - /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only - /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all - /// same-type comparisons normally. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) - { - // Handle: expected is non-JsonElement, actual is JsonElement - constraint.Using( - (actualJsonElement, expectedObj) => - { - try - { - var expectedElement = JsonUtils.SerializeToElement(expectedObj); - return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); - } - catch - { - return false; - } - } - ); - // Handle reverse: expected is JsonElement, actual is non-JsonElement - constraint.Using( - (actualObj, expectedJsonElement) => - { - try - { - var actualElement = JsonUtils.SerializeToElement(actualObj); - return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); - } - catch - { - return false; - } - } - ); - return constraint; - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs deleted file mode 100644 index 3f4b5eb602b2..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonAssert.cs +++ /dev/null @@ -1,29 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Utils; - -internal static class JsonAssert -{ - /// - /// Asserts that the serialized JSON of an object equals the expected JSON string. - /// Uses JsonElement comparison for reliable deep equality of collections and union types. - /// - internal static void AreEqual(object actual, string expectedJson) - { - var actualElement = JsonUtils.SerializeToElement(actual); - var expectedElement = JsonUtils.Deserialize(expectedJson); - Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); - } - - /// - /// Asserts that the given JSON string survives a deserialization/serialization round-trip - /// intact: deserializes to T then re-serializes and compares to the original JSON. - /// - internal static void Roundtrips(string json) - { - var deserialized = JsonUtils.Deserialize(json); - AreEqual(deserialized!, json); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs deleted file mode 100644 index a37ef402c1ac..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs +++ /dev/null @@ -1,236 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle JsonElement objects. -/// -public static class JsonElementComparerExtensions -{ - /// - /// Extension method for comparing JsonElement objects in NUnit tests. - /// Property order doesn't matter, but array order does matter. - /// Includes special handling for DateTime string formats. - /// - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare JsonElements with detailed diffs. - public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) - { - return constraint.Using(new JsonElementComparer()); - } -} - -/// -/// Equality comparer for JsonElement with detailed reporting. -/// Property order doesn't matter, but array order does matter. -/// Now includes special handling for DateTime string formats with improved null handling. -/// -public class JsonElementComparer : IEqualityComparer -{ - private string _failurePath = string.Empty; - - /// - public bool Equals(JsonElement x, JsonElement y) - { - _failurePath = string.Empty; - return CompareJsonElements(x, y, string.Empty); - } - - /// - public int GetHashCode(JsonElement obj) - { - return JsonSerializer.Serialize(obj).GetHashCode(); - } - - private bool CompareJsonElements(JsonElement x, JsonElement y, string path) - { - // If value kinds don't match, they're not equivalent - if (x.ValueKind != y.ValueKind) - { - _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; - return false; - } - - switch (x.ValueKind) - { - case JsonValueKind.Object: - return CompareJsonObjects(x, y, path); - - case JsonValueKind.Array: - return CompareJsonArraysInOrder(x, y, path); - - case JsonValueKind.String: - string? xStr = x.GetString(); - string? yStr = y.GetString(); - - // Handle null strings - if (xStr is null && yStr is null) - return true; - - if (xStr is null || yStr is null) - { - _failurePath = - $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; - return false; - } - - // Check if they are identical strings - if (xStr == yStr) - return true; - - // Try to handle DateTime strings - if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) - { - if (AreEquivalentDateTimeStrings(xStr, yStr)) - return true; - } - - _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; - return false; - - case JsonValueKind.Number: - if (x.GetDecimal() != y.GetDecimal()) - { - _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; - return false; - } - - return true; - - case JsonValueKind.True: - case JsonValueKind.False: - if (x.GetBoolean() != y.GetBoolean()) - { - _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; - return false; - } - - return true; - - case JsonValueKind.Null: - return true; - - default: - _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; - return false; - } - } - - private bool IsLikelyDateTimeString(string? str) - { - // Simple heuristic to identify likely ISO date time strings - return str is not null - && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); - } - - private bool AreEquivalentDateTimeStrings(string str1, string str2) - { - // Try to parse both as DateTime - if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) - { - return dt1 == dt2; - } - - return false; - } - - private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) - { - // Create dictionaries for both JSON objects - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - // Check if all properties in x exist in y - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - _failurePath = $"{path}: Missing property '{key}'"; - return false; - } - } - - // Check if y has extra properties - foreach (var key in yProps.Keys) - { - if (!xProps.ContainsKey(key)) - { - _failurePath = $"{path}: Unexpected property '{key}'"; - return false; - } - } - - // Compare each property value - foreach (var key in xProps.Keys) - { - var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; - if (!CompareJsonElements(xProps[key], yProps[key], propPath)) - { - return false; - } - } - - return true; - } - - private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) - { - var xArray = x.EnumerateArray(); - var yArray = y.EnumerateArray(); - - // Count x elements - var xCount = 0; - var xElements = new List(); - foreach (var item in xArray) - { - xElements.Add(item); - xCount++; - } - - // Count y elements - var yCount = 0; - var yElements = new List(); - foreach (var item in yArray) - { - yElements.Add(item); - yCount++; - } - - // Check if counts match - if (xCount != yCount) - { - _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; - return false; - } - - // Compare elements in order - for (var i = 0; i < xCount; i++) - { - var itemPath = $"{path}[{i}]"; - if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) - { - return false; - } - } - - return true; - } - - /// - public override string ToString() - { - if (!string.IsNullOrEmpty(_failurePath)) - { - return $"JSON comparison failed at {_failurePath}"; - } - - return "JsonElementEqualityComparer"; - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs deleted file mode 100644 index 816f4c010e6e..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class NUnitExtensions -{ - /// - /// Modifies the EqualConstraint to use our own set of default comparers. - /// - /// - /// - public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => - constraint - .UsingPropertiesComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingOneOfComparer() - .UsingJsonElementComparer() - .UsingOptionalComparer() - .UsingObjectDictionaryComparer() - .UsingAdditionalPropertiesComparer() - .UsingJsonSerializationComparer(); -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs deleted file mode 100644 index 767439174363..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Utils/OneOfComparer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle OneOf values. -/// -public static class EqualConstraintExtensions -{ - /// - /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOneOf types - constraint.Using( - (x, y) => - { - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (x.Value is null && y.Value is null) - { - return true; - } - - if (x.Value is null) - { - return false; - } - - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs deleted file mode 100644 index 98bfcac477b8..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Utils/OptionalComparer.cs +++ /dev/null @@ -1,104 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle Optional values. -/// -public static class OptionalComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOptional types - constraint.Using( - (x, y) => - { - // Both must have the same IsDefined state - if (x.IsDefined != y.IsDefined) - { - return false; - } - - // If both are undefined, they're equal - if (!x.IsDefined) - { - return true; - } - - // Both are defined, compare their boxed values - var xValue = x.GetBoxedValue(); - var yValue = y.GetBoxedValue(); - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (xValue is null && yValue is null) - { - return true; - } - - if (xValue is null || yValue is null) - { - return false; - } - - // Use NUnit's property comparer for the inner values - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values within Optional types. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs deleted file mode 100644 index fc0b595a5e54..000000000000 --- a/seed/csharp-model/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class ReadOnlyMemoryComparerExtensions -{ - /// - /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. - /// - /// The type of elements in the ReadOnlyMemory. - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare ReadOnlyMemory<T>. - public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) - where T : IComparable - { - return constraint.Using(new ReadOnlyMemoryComparer()); - } -} - -/// -/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. -/// -/// -/// The type of elements in the ReadOnlyMemory. -/// -public class ReadOnlyMemoryComparer : IComparer> - where T : IComparable -{ - /// - public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) - { - // Check if sequences are equal - var xSpan = x.Span; - var ySpan = y.Span; - - // Optimized case for IEquatable implementations - if (typeof(IEquatable).IsAssignableFrom(typeof(T))) - { - var areEqual = xSpan.SequenceEqual(ySpan); - if (areEqual) - { - return 0; // Sequences are equal - } - } - else - { - // Manual equality check for non-IEquatable types - if (xSpan.Length == ySpan.Length) - { - var areEqual = true; - for (var i = 0; i < xSpan.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - areEqual = false; - break; - } - } - - if (areEqual) - { - return 0; // Sequences are equal - } - } - } - - // For non-equal sequences, we need to return a consistent ordering - // First compare lengths - if (x.Length != y.Length) - return x.Length.CompareTo(y.Length); - - // Same length but different content - compare first differing element - for (var i = 0; i < x.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - return xSpan[i].CompareTo(ySpan[i]); - } - } - - // Should never reach here if not equal - return 0; - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/AuditInfo.cs b/seed/csharp-model/allof/src/SeedApi/AuditInfo.cs deleted file mode 100644 index c0d843281678..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/AuditInfo.cs +++ /dev/null @@ -1,56 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -/// -/// Common audit metadata. -/// -[Serializable] -public record AuditInfo : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/BaseOrg.cs b/seed/csharp-model/allof/src/SeedApi/BaseOrg.cs deleted file mode 100644 index eb944d0030da..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/BaseOrg.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public BaseOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs b/seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs deleted file mode 100644 index fcb0efec5fea..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/BaseOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from BaseOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Subscription tier. - /// - [JsonPropertyName("tier")] - public string? Tier { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs b/seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs deleted file mode 100644 index 757711bdb703..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/CombinedEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record CombinedEntity : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("status")] - public required CombinedEntityStatus Status { get; set; } - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Identifiable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs b/seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs deleted file mode 100644 index 0ab2467f6bd7..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/CombinedEntityStatus.cs +++ /dev/null @@ -1,115 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] -[Serializable] -public readonly record struct CombinedEntityStatus : IStringEnum -{ - public static readonly CombinedEntityStatus Active = new(Values.Active); - - public static readonly CombinedEntityStatus Archived = new(Values.Archived); - - public CombinedEntityStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static CombinedEntityStatus FromCustom(string value) - { - return new CombinedEntityStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(CombinedEntityStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(CombinedEntityStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(CombinedEntityStatus value) => value.Value; - - public static explicit operator CombinedEntityStatus(string value) => new(value); - - internal class CombinedEntityStatusSerializer : JsonConverter - { - public override CombinedEntityStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override CombinedEntityStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Archived = "archived"; - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs deleted file mode 100644 index b684f33d750e..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/CollectionItemSerializer.cs +++ /dev/null @@ -1,91 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Json collection converter. -/// -/// Type of item to convert. -/// Converter to use for individual items. -internal class CollectionItemSerializer - : JsonConverter> - where TConverterType : JsonConverter, new() -{ - private static readonly TConverterType _converter = new TConverterType(); - - /// - /// Reads a json string and deserializes it into an object. - /// - /// Json reader. - /// Type to convert. - /// Serializer options. - /// Created object. - public override IEnumerable? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - var returnValue = new List(); - - while (reader.TokenType != JsonTokenType.EndArray) - { - if (reader.TokenType != JsonTokenType.StartArray) - { - var item = (TDatatype)( - JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) - ?? throw new global::System.Exception( - $"Failed to deserialize collection item of type {typeof(TDatatype)}" - ) - ); - returnValue.Add(item); - } - - reader.Read(); - } - - return returnValue; - } - - /// - /// Writes a json string. - /// - /// Json writer. - /// Value to write. - /// Serializer options. - public override void Write( - Utf8JsonWriter writer, - IEnumerable? value, - JsonSerializerOptions options - ) - { - if (value is null) - { - writer.WriteNullValue(); - return; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - writer.WriteStartArray(); - - foreach (var data in value) - { - JsonSerializer.Serialize(writer, data, jsonSerializerOptions); - } - - writer.WriteEndArray(); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Constants.cs b/seed/csharp-model/allof/src/SeedApi/Core/Constants.cs deleted file mode 100644 index ccf4e963cc89..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi.Core; - -internal static class Constants -{ - public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; - public const string DateFormat = "yyyy-MM-dd"; -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs deleted file mode 100644 index af61cc061ae5..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/DateOnlyConverter.cs +++ /dev/null @@ -1,747 +0,0 @@ -// ReSharper disable All -#pragma warning disable - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using global::System.Diagnostics; -using global::System.Diagnostics.CodeAnalysis; -using global::System.Globalization; -using global::System.Runtime.CompilerServices; -using global::System.Runtime.InteropServices; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -// ReSharper disable SuggestVarOrType_SimpleTypes -// ReSharper disable SuggestVarOrType_BuiltInTypes - -namespace SeedApi.Core -{ - /// - /// Custom converter for handling the data type with the System.Text.Json library. - /// - /// - /// This class backported from: - /// - /// System.Text.Json.Serialization.Converters.DateOnlyConverter - /// - public sealed class DateOnlyConverter : JsonConverter - { - private const int FormatLength = 10; // YYYY-MM-DD - - private const int MaxEscapedFormatLength = - FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; - - /// - public override DateOnly Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType != JsonTokenType.String) - { - ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); - } - - return ReadCore(ref reader); - } - - /// - public override DateOnly ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - return ReadCore(ref reader); - } - - private static DateOnly ReadCore(ref Utf8JsonReader reader) - { - if ( - !JsonHelpers.IsInRangeInclusive( - reader.ValueLength(), - FormatLength, - MaxEscapedFormatLength - ) - ) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - scoped ReadOnlySpan source; - if (!reader.HasValueSequence && !reader.ValueIsEscaped) - { - source = reader.ValueSpan; - } - else - { - Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; - int bytesWritten = reader.CopyString(stackSpan); - source = stackSpan.Slice(0, bytesWritten); - } - - if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - return value; - } - - /// - public override void Write( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WriteStringValue(buffer); - } - - /// - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WritePropertyName(buffer); - } - } - - internal static class JsonConstants - { - // The maximum number of fraction digits the Json DateTime parser allows - public const int DateTimeParseNumFractionDigits = 16; - - // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. - public const int MaxExpansionFactorWhileEscaping = 6; - - // The largest fraction expressible by TimeSpan and DateTime formats - public const int MaxDateTimeFraction = 9_999_999; - - // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. - public const int DateTimeNumFractionDigits = 7; - - public const byte UtcOffsetToken = (byte)'Z'; - - public const byte TimePrefix = (byte)'T'; - - public const byte Period = (byte)'.'; - - public const byte Hyphen = (byte)'-'; - - public const byte Colon = (byte)':'; - - public const byte Plus = (byte)'+'; - } - - // ReSharper disable SuggestVarOrType_Elsewhere - // ReSharper disable SuggestVarOrType_SimpleTypes - // ReSharper disable SuggestVarOrType_BuiltInTypes - - internal static class JsonHelpers - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => - (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); - - public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; - - [StructLayout(LayoutKind.Auto)] - private struct DateTimeParseData - { - public int Year; - public int Month; - public int Day; - public bool IsCalendarDateOnly; - public int Hour; - public int Minute; - public int Second; - public int Fraction; // This value should never be greater than 9_999_999. - public int OffsetHours; - public int OffsetMinutes; - - // ReSharper disable once NotAccessedField.Local - public byte OffsetToken; - } - - public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) - { - if ( - TryParseDateTimeOffset(source, out DateTimeParseData parseData) - && parseData.IsCalendarDateOnly - && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) - ) - { - value = DateOnly.FromDateTime(dateTime); - return true; - } - - value = default; - return false; - } - - /// - /// ISO 8601 date time parser (ISO 8601-1:2019). - /// - /// The date/time to parse in UTF-8 format. - /// The parsed for the given . - /// - /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day - /// representations with optional specification of seconds and fractional seconds. - /// - /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). - /// If unspecified they are considered to be local per spec. - /// - /// Examples: (TZD is either "Z" or hh:mm offset from UTC) - /// - /// YYYY-MM-DD (e.g. 1997-07-16) - /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) - /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) - /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) - /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) - /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) - /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) - /// - /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). - /// The extended variants have separator characters between components ('-', ':', '.', etc.). - /// Spaces are not permitted. - /// - /// "true" if successfully parsed. - private static bool TryParseDateTimeOffset( - ReadOnlySpan source, - out DateTimeParseData parseData - ) - { - parseData = default; - - // too short datetime - Debug.Assert(source.Length >= 10); - - // Parse the calendar date - // ----------------------- - // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" - // [dateX] = [year]["-"][month]["-"][day] - // [year] = [YYYY] [0000 - 9999] (4.3.2) - // [month] = [MM] [01 - 12] (4.3.3) - // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) - // - // Note: 5.2.2.2 "Representations with reduced precision" allows for - // just [year]["-"][month] (a) and just [year] (b), but we currently - // don't permit it. - - { - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - uint digit3 = source[2] - (uint)'0'; - uint digit4 = source[3] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) - { - return false; - } - - parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); - } - - if ( - source[4] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) - || source[7] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) - ) - { - return false; - } - - // We now have YYYY-MM-DD [dateX] - // ReSharper disable once ConvertIfStatementToSwitchStatement - if (source.Length == 10) - { - parseData.IsCalendarDateOnly = true; - return true; - } - - // Parse the time of day - // --------------------- - // - // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" - // [timeX] = ["T"][hour][":"][min][":"][sec] - // [hour] = [hh] [00 - 23] (4.3.8a) - // [minute] = [mm] [00 - 59] (4.3.9a) - // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) - // - // ISO 8601-1:2019 5.3.3 "UTC of day" - // [timeX]["Z"] - // - // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between - // local timescale and UTC" (Extended format) - // - // [shiftX] = ["+"|"-"][hour][":"][min] - // - // Notes: - // - // "T" is optional per spec, but _only_ when times are used alone. In our - // case, we're reading out a complete date & time and as such require "T". - // (5.4.2.1b). - // - // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations - // with reduced precision". 5.3.1.3b allows just specifying the hour, but - // we currently don't permit this. - // - // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). - // We only allow fractions for seconds currently. Lower order components - // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be - // one digit, but the max number of digits is implementation defined. We - // currently allow up to 16 digits of fractional seconds only. While we - // support 16 fractional digits we only parse the first seven, anything - // past that is considered a zero. This is to stay compatible with the - // DateTime implementation which is limited to this resolution. - - if (source.Length < 16) - { - // Source does not have enough characters for YYYY-MM-DDThh:mm - return false; - } - - // Parse THH:MM (e.g. "T10:32") - if ( - source[10] != JsonConstants.TimePrefix - || source[13] != JsonConstants.Colon - || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) - || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm - Debug.Assert(source.Length >= 16); - if (source.Length == 16) - { - return true; - } - - byte curByte = source[16]; - int sourceIndex = 17; - - // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Colon: - break; - default: - return false; - } - - // Try reading the seconds - if ( - source.Length < 19 - || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss - Debug.Assert(source.Length >= 19); - if (source.Length == 19) - { - return true; - } - - curByte = source[19]; - sourceIndex = 20; - - // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Period: - break; - default: - return false; - } - - // Source does not have enough characters for second fractions (i.e. ".s") - // YYYY-MM-DDThh:mm:ss.s - if (source.Length < 21) - { - return false; - } - - // Parse fraction. This value should never be greater than 9_999_999 - int numDigitsRead = 0; - int fractionEnd = Math.Min( - sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, - source.Length - ); - - while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) - { - if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); - numDigitsRead++; - } - - sourceIndex++; - } - - if (parseData.Fraction != 0) - { - while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction *= 10; - numDigitsRead++; - } - } - - // We now have YYYY-MM-DDThh:mm:ss.s - Debug.Assert(sourceIndex <= source.Length); - if (sourceIndex == source.Length) - { - return true; - } - - curByte = source[sourceIndex++]; - - // TZD ['Z'|'+'|'-'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - default: - return false; - } - - static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) - { - // Parse the hours for the offset - if ( - offsetData.Length < 2 - || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss.s+|-hh - - if (offsetData.Length == 2) - { - // Just hours offset specified - return true; - } - - // Ensure we have enough for ":mm" - return offsetData.Length == 5 - && offsetData[2] == JsonConstants.Colon - && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - // ReSharper disable once RedundantAssignment - private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) - { - Debug.Assert(source.Length == 2); - - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9) - { - value = 0; - return false; - } - - value = (int)(digit1 * 10 + digit2); - return true; - } - - // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs - - /// - /// Overflow-safe DateTime factory. - /// - private static bool TryCreateDateTime( - DateTimeParseData parseData, - DateTimeKind kind, - out DateTime value - ) - { - if (parseData.Year == 0) - { - value = default; - return false; - } - - Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. - - if ((uint)parseData.Month - 1 >= 12) - { - value = default; - return false; - } - - uint dayMinusOne = (uint)parseData.Day - 1; - if ( - dayMinusOne >= 28 - && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) - ) - { - value = default; - return false; - } - - if ((uint)parseData.Hour > 23) - { - value = default; - return false; - } - - if ((uint)parseData.Minute > 59) - { - value = default; - return false; - } - - // This needs to allow leap seconds when appropriate. - // See https://github.com/dotnet/runtime/issues/30135. - if ((uint)parseData.Second > 59) - { - value = default; - return false; - } - - Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. - - ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) - ? DaysToMonth366 - : DaysToMonth365; - int yearMinusOne = parseData.Year - 1; - int totalDays = - yearMinusOne * 365 - + yearMinusOne / 4 - - yearMinusOne / 100 - + yearMinusOne / 400 - + days[parseData.Month - 1] - + parseData.Day - - 1; - long ticks = totalDays * TimeSpan.TicksPerDay; - int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; - ticks += totalSeconds * TimeSpan.TicksPerSecond; - ticks += parseData.Fraction; - value = new DateTime(ticks: ticks, kind: kind); - return true; - } - - private static ReadOnlySpan DaysToMonth365 => - [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; - private static ReadOnlySpan DaysToMonth366 => - [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; - } - - internal static class ThrowHelper - { - private const string ExceptionSourceValueToRethrowAsJsonException = - "System.Text.Json.Rethrowable"; - - [DoesNotReturn] - public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) - { - throw GetInvalidOperationException("string", tokenType); - } - - public static void ThrowFormatException(DataType dataType) - { - throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - - private static global::System.Exception GetInvalidOperationException( - string message, - JsonTokenType tokenType - ) - { - return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); - } - - private static InvalidOperationException GetInvalidOperationException(string message) - { - return new InvalidOperationException(message) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - } - - internal static class Utf8JsonReaderExtensions - { - internal static int ValueLength(this Utf8JsonReader reader) => - reader.HasValueSequence - ? checked((int)reader.ValueSequence.Length) - : reader.ValueSpan.Length; - } - - internal enum DataType - { - TimeOnly, - DateOnly, - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - internal static class SR - { - private static readonly bool s_usingResourceKeys = - AppContext.TryGetSwitch( - "System.Resources.UseSystemResourceKeys", - out bool usingResourceKeys - ) && usingResourceKeys; - - public static string UnsupportedFormat => Strings.UnsupportedFormat; - - public static string InvalidCast => Strings.InvalidCast; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1) - : string.Format(resourceFormat, p1); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1, object? p2) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1, p2) - : string.Format(resourceFormat, p1, p2); - } - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute( - "System.Resources.Tools.StronglyTypedResourceBuilder", - "17.0.0.0" - )] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings - { - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( - "Microsoft.Performance", - "CA1811:AvoidUncalledPrivateCode" - )] - internal Strings() { } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = - new global::System.Resources.ResourceManager( - "System.Text.Json.Resources.Strings", - typeof(Strings).Assembly - ); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Globalization.CultureInfo Culture - { - get { return resourceCulture; } - set { resourceCulture = value; } - } - - /// - /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. - /// - internal static string InvalidCast - { - get { return ResourceManager.GetString("InvalidCast", resourceCulture); } - } - - /// - /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. - /// - internal static string UnsupportedFormat - { - get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } - } - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs deleted file mode 100644 index d7dedc7f165b..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/DateTimeSerializer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using global::System.Globalization; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -internal class DateTimeSerializer : JsonConverter -{ - public override DateTime Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); - } - - public override DateTime ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateTime value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs deleted file mode 100644 index 93dcc6dd6bca..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/JsonAccessAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace SeedApi.Core; - -[global::System.AttributeUsage( - global::System.AttributeTargets.Property | global::System.AttributeTargets.Field -)] -internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute -{ - internal JsonAccessType AccessType { get; init; } = accessType; -} - -internal enum JsonAccessType -{ - ReadOnly, - WriteOnly, -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs deleted file mode 100644 index 2fa8cfb6ad8c..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/JsonConfiguration.cs +++ /dev/null @@ -1,275 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Encodings.Web; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using global::System.Text.Json.Serialization; -using global::System.Text.Json.Serialization.Metadata; - -namespace SeedApi.Core; - -internal static partial class JsonOptions -{ - internal static readonly JsonSerializerOptions JsonSerializerOptions; - internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; - - static JsonOptions() - { - var options = new JsonSerializerOptions - { - Converters = - { - new DateTimeSerializer(), -#if USE_PORTABLE_DATE_ONLY - new DateOnlyConverter(), -#endif - new OneOfSerializer(), - new OptionalJsonConverterFactory(), - }, -#if DEBUG - WriteIndented = true, -#endif - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - TypeInfoResolver = new DefaultJsonTypeInfoResolver - { - Modifiers = - { - NullableOptionalModifier, - JsonAccessAndIgnoreModifier, - HandleExtensionDataFields, - }, - }, - }; - ConfigureJsonSerializerOptions(options); - JsonSerializerOptions = options; - - var relaxedOptions = new JsonSerializerOptions(options) - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; - JsonSerializerOptionsRelaxedEscaping = relaxedOptions; - } - - private static void NullableOptionalModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var property in typeInfo.Properties) - { - var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; - - if (propertyInfo is null) - continue; - - // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior - var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); - if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) - { - // ReadOnly means "never serialize", which completely overrides Optional/Nullable. - // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier - // will set ShouldSerialize = false anyway. - continue; - } - // Note: WriteOnly doesn't conflict with Optional/Nullable since it only - // affects deserialization (Set), not serialization (ShouldSerialize) - - var isOptionalType = - property.PropertyType.IsGenericType - && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); - - var hasOptionalAttribute = - propertyInfo.GetCustomAttribute() is not null; - var hasNullableAttribute = - propertyInfo.GetCustomAttribute() is not null; - - if (isOptionalType && hasOptionalAttribute) - { - var originalGetter = property.Get; - if (originalGetter is not null) - { - var capturedIsNullable = hasNullableAttribute; - - property.ShouldSerialize = (obj, value) => - { - var optionalValue = originalGetter(obj); - if (optionalValue is not IOptional optional) - return false; - - if (!optional.IsDefined) - return false; - - if (!capturedIsNullable) - { - var innerValue = optional.GetBoxedValue(); - if (innerValue is null) - return false; - } - - return true; - }; - } - } - else if (hasNullableAttribute) - { - // Force serialization of nullable properties even when null - property.ShouldSerialize = (obj, value) => true; - } - } - } - - private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var propertyInfo in typeInfo.Properties) - { - var jsonAccessAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonAccessAttribute is not null) - { - propertyInfo.IsRequired = false; - switch (jsonAccessAttribute.AccessType) - { - case JsonAccessType.ReadOnly: - propertyInfo.ShouldSerialize = (_, _) => false; - break; - case JsonAccessType.WriteOnly: - propertyInfo.Set = null; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - var jsonIgnoreAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonIgnoreAttribute is not null) - { - propertyInfo.IsRequired = false; - } - } - } - - private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) - { - if ( - typeInfo.Kind == JsonTypeInfoKind.Object - && typeInfo.Properties.All(prop => !prop.IsExtensionData) - ) - { - var extensionProp = typeInfo - .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) - .FirstOrDefault(prop => - prop.GetCustomAttribute() is not null - ); - - if (extensionProp is not null) - { - var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( - extensionProp.FieldType, - extensionProp.Name - ); - jsonPropertyInfo.Get = extensionProp.GetValue; - jsonPropertyInfo.Set = extensionProp.SetValue; - jsonPropertyInfo.IsExtensionData = true; - typeInfo.Properties.Add(jsonPropertyInfo); - } - } - } - - static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); -} - -internal static class JsonUtils -{ - internal static string Serialize(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); - - internal static string Serialize(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); - - internal static string SerializeRelaxedEscaping(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static JsonElement SerializeToElement(T obj) => - JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonElement SerializeToElement(object obj, global::System.Type type) => - JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); - - internal static JsonDocument SerializeToDocument(T obj) => - JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonNode? SerializeToNode(T obj) => - JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); - - internal static byte[] SerializeToUtf8Bytes(T obj) => - JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); - - internal static string SerializeWithAdditionalProperties( - T obj, - object? additionalProperties = null - ) - { - if (additionalProperties is null) - { - return Serialize(obj); - } - var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); - if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) - { - throw new InvalidOperationException( - "The additional properties must serialize to a JSON object." - ); - } - var jsonNode = SerializeToNode(obj); - if (jsonNode is not JsonObject jsonObject) - { - throw new InvalidOperationException( - "The serialized object must be a JSON object to add properties." - ); - } - MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); - return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); - } - - private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) - { - foreach (var property in overrideObject) - { - if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) - { - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - continue; - } - if ( - existingValue is JsonObject nestedBaseObject - && property.Value is JsonObject nestedOverrideObject - ) - { - // If both values are objects, recursively merge them. - MergeJsonObjects(nestedBaseObject, nestedOverrideObject); - continue; - } - // Otherwise, the overrideObject takes precedence. - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - } - } - - internal static T Deserialize(string json) => - JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs deleted file mode 100644 index a1d30328bf9a..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/NullableAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as nullable in the OpenAPI specification. -/// When applied to Optional properties, this indicates that null values should be -/// written to JSON when the optional is defined with null. -/// -/// -/// For regular (required) properties: -/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) -/// - With [Nullable]: null values are written to JSON -/// -/// For Optional properties (also marked with [Optional]): -/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) -/// - With [Nullable]: Optional.Of(null) → write null to JSON -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs deleted file mode 100644 index 6eeb68fcba46..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/OneOfSerializer.cs +++ /dev/null @@ -1,145 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using OneOf; - -namespace SeedApi.Core; - -internal class OneOfSerializer : JsonConverter -{ - public override IOneOf? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType is JsonTokenType.Null) - return default; - - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - var readerCopy = reader; - var result = JsonSerializer.Deserialize(ref readerCopy, type, options); - reader.Skip(); - return (IOneOf)cast.Invoke(null, [result])!; - } - catch (JsonException) { } - } - - throw new JsonException( - $"Cannot deserialize into one of the supported types for {typeToConvert}" - ); - } - - public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value.Value, options); - } - - public override IOneOf ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = reader.GetString(); - if (stringValue == null) - throw new JsonException("Cannot deserialize null property name into OneOf type"); - - // Try to deserialize the string value into one of the supported types - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - // For primitive types, try direct conversion - if (type == typeof(string)) - { - return (IOneOf)cast.Invoke(null, [stringValue])!; - } - - // For other types, try to deserialize from JSON string - var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); - if (result != null) - { - return (IOneOf)cast.Invoke(null, [result])!; - } - } - catch { } - } - - // If no type-specific deserialization worked, default to string if available - var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); - if (stringType != default) - { - return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; - } - - throw new JsonException( - $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" - ); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - IOneOf value, - JsonSerializerOptions options - ) - { - // Serialize the underlying value to a string suitable for use as a dictionary key - var stringValue = value.Value?.ToString() ?? "null"; - writer.WritePropertyName(stringValue); - } - - private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( - global::System.Type typeToConvert - ) - { - var type = typeToConvert; - if (Nullable.GetUnderlyingType(type) is { } underlyingType) - { - type = underlyingType; - } - - var casts = type.GetRuntimeMethods() - .Where(m => m.IsSpecialName && m.Name == "op_Implicit") - .ToArray(); - while (type is not null) - { - if ( - type.IsGenericType - && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) - ) - { - var genericArguments = type.GetGenericArguments(); - if (genericArguments.Length == 1) - { - return [(genericArguments[0], casts[0])]; - } - - // if object type is present, make sure it is last - var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); - if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) - { - genericArguments = genericArguments - .OrderBy(t => t == typeof(object) ? 1 : 0) - .ToArray(); - } - - return genericArguments - .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) - .ToArray(); - } - - type = type.BaseType; - } - - throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); - } - - public override bool CanConvert(global::System.Type typeToConvert) - { - return typeof(IOneOf).IsAssignableFrom(typeToConvert); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Optional.cs b/seed/csharp-model/allof/src/SeedApi/Core/Optional.cs deleted file mode 100644 index d174943cb2cf..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/Optional.cs +++ /dev/null @@ -1,474 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Non-generic interface for Optional types to enable reflection-free checks. -/// -public interface IOptional -{ - /// - /// Returns true if the value is defined (set), even if the value is null. - /// - bool IsDefined { get; } - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - object? GetBoxedValue(); -} - -/// -/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). -/// Use this for HTTP PATCH requests where you need to distinguish between: -/// -/// Undefined: Don't send this field (leave it unchanged on the server) -/// Defined with null: Send null (clear the field on the server) -/// Defined with value: Send the value (update the field on the server) -/// -/// -/// The type of the value. Use nullable types (T?) for fields that can be null. -/// -/// For nullable string fields, use Optional<string?>: -/// -/// public class UpdateUserRequest -/// { -/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; -/// } -/// -/// var request = new UpdateUserRequest -/// { -/// Name = "John" // Will send: { "name": "John" } -/// }; -/// -/// var request2 = new UpdateUserRequest -/// { -/// Name = Optional<string?>.Of(null) // Will send: { "name": null } -/// }; -/// -/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) -/// -/// -public readonly struct Optional : IOptional, IEquatable> -{ - private readonly T _value; - private readonly bool _isDefined; - - private Optional(T value, bool isDefined) - { - _value = value; - _isDefined = isDefined; - } - - /// - /// Creates an undefined value - the field will not be included in the HTTP request. - /// Use this as the default value for optional fields. - /// - /// - /// - /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; - /// - /// - public static Optional Undefined => new(default!, false); - - /// - /// Creates a defined value - the field will be included in the HTTP request. - /// The value can be null if T is a nullable type. - /// - /// The value to set. Can be null if T is nullable (e.g., string?, int?). - /// - /// - /// // Set to a value - /// request.Name = Optional<string?>.Of("John"); - /// - /// // Set to null (clears the field) - /// request.Email = Optional<string?>.Of(null); - /// - /// // Or use implicit conversion - /// request.Name = "John"; // Same as Of("John") - /// request.Email = null; // Same as Of(null) - /// - /// - public static Optional Of(T value) => new(value, true); - - /// - /// Returns true if the field is defined (set), even if the value is null. - /// Use this to determine if the field should be included in the HTTP request. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// requestBody["name"] = request.Name.Value; // Include in request (can be null) - /// } - /// - /// - public bool IsDefined => _isDefined; - - /// - /// Returns true if the field is undefined (not set). - /// Use this to check if the field should be excluded from the HTTP request. - /// - /// - /// - /// if (request.Email.IsUndefined) - /// { - /// // Don't include email in the request - leave it unchanged - /// } - /// - /// - public bool IsUndefined => !_isDefined; - - /// - /// Gets the value. The value may be null if T is a nullable type. - /// - /// Thrown if the value is undefined. - /// - /// Always check before accessing Value, or use instead. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> - /// } - /// - /// // Or check for null explicitly - /// if (request.Email.IsDefined && request.Email.Value is null) - /// { - /// // Email is explicitly set to null (clear it) - /// } - /// - /// - public T Value - { - get - { - if (!_isDefined) - throw new InvalidOperationException("Optional value is undefined"); - return _value; - } - } - - /// - /// Gets the value if defined, otherwise returns the specified default value. - /// Note: If the value is defined as null, this returns null (not the default). - /// - /// The value to return if undefined. - /// The actual value if defined (can be null), otherwise the default value. - /// - /// - /// string name = request.Name.GetValueOrDefault("Anonymous"); - /// // If Name is undefined: returns "Anonymous" - /// // If Name is Of(null): returns null - /// // If Name is Of("John"): returns "John" - /// - /// - public T GetValueOrDefault(T defaultValue = default!) - { - return _isDefined ? _value : defaultValue; - } - - /// - /// Tries to get the value. Returns true if the value is defined (even if null). - /// - /// - /// When this method returns, contains the value if defined, or default(T) if undefined. - /// The value may be null if T is nullable. - /// - /// True if the value is defined; otherwise, false. - /// - /// - /// if (request.Email.TryGetValue(out var email)) - /// { - /// requestBody["email"] = email; // email can be null - /// } - /// else - /// { - /// // Email is undefined - don't include in request - /// } - /// - /// - public bool TryGetValue(out T value) - { - if (_isDefined) - { - value = _value; - return true; - } - value = default!; - return false; - } - - /// - /// Implicitly converts a value to Optional<T>.Of(value). - /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). - /// - /// The value to convert (can be null if T is nullable). - public static implicit operator Optional(T value) => Of(value); - - /// - /// Returns a string representation of this Optional value. - /// - /// "Undefined" if not set, or "Defined(value)" if set. - public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - public object? GetBoxedValue() - { - if (!_isDefined) - return null; - return _value; - } - - /// - public bool Equals(Optional other) => - _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is Optional other && Equals(other); - - /// - public override int GetHashCode() - { - if (!_isDefined) - return 0; - unchecked - { - int hash = 17; - hash = hash * 31 + 1; // _isDefined = true - hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); - return hash; - } - } - - /// - /// Determines whether two Optional values are equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are equal; otherwise, false. - public static bool operator ==(Optional left, Optional right) => left.Equals(right); - - /// - /// Determines whether two Optional values are not equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are not equal; otherwise, false. - public static bool operator !=(Optional left, Optional right) => !left.Equals(right); -} - -/// -/// Extension methods for Optional to simplify common operations. -/// -public static class OptionalExtensions -{ - /// - /// Adds the value to a dictionary if the optional is defined (even if the value is null). - /// This is useful for building JSON request payloads where null values should be included. - /// - /// The type of the optional value. - /// The optional value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined - /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined - /// - /// - public static void AddTo( - this Optional optional, - Dictionary dictionary, - string key - ) - { - if (optional.IsDefined) - { - dictionary[key] = optional.Value; - } - } - - /// - /// Executes an action if the optional is defined. - /// - /// The type of the optional value. - /// The optional value. - /// The action to execute with the value. - /// - /// - /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); - /// - /// - public static void IfDefined(this Optional optional, Action action) - { - if (optional.IsDefined) - { - action(optional.Value); - } - } - - /// - /// Maps the value to a new type if the optional is defined, otherwise returns undefined. - /// - /// The type of the original value. - /// The type to map to. - /// The optional value to map. - /// The mapping function. - /// An optional containing the mapped value if defined, otherwise undefined. - /// - /// - /// Optional<string?> name = Optional<string?>.Of("John"); - /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) - /// - /// - public static Optional Map( - this Optional optional, - Func mapper - ) - { - return optional.IsDefined - ? Optional.Of(mapper(optional.Value)) - : Optional.Undefined; - } - - /// - /// Adds a nullable value to a dictionary only if it is not null. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The type of the value (must be a reference type or Nullable). - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : class - { - if (value is not null) - { - dictionary[key] = value; - } - } - - /// - /// Adds a nullable value type to a dictionary only if it has a value. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The underlying value type. - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : struct - { - if (value.HasValue) - { - dictionary[key] = value.Value; - } - } -} - -/// -/// JSON converter factory for Optional that handles undefined vs null correctly. -/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. -/// -public class OptionalJsonConverterFactory : JsonConverterFactory -{ - public override bool CanConvert(global::System.Type typeToConvert) - { - if (!typeToConvert.IsGenericType) - return false; - - return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); - } - - public override JsonConverter? CreateConverter( - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var valueType = typeToConvert.GetGenericArguments()[0]; - var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); - return (JsonConverter?)global::System.Activator.CreateInstance(converterType); - } -} - -/// -/// JSON converter for Optional that unwraps the value during serialization. -/// The actual property skipping is handled by the OptionalTypeInfoResolver. -/// -public class OptionalJsonConverter : JsonConverter> -{ - public override Optional Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return Optional.Of(default!); - } - - var value = JsonSerializer.Deserialize(ref reader, options); - return Optional.Of(value!); - } - - public override void Write( - Utf8JsonWriter writer, - Optional value, - JsonSerializerOptions options - ) - { - // This will be called by the serializer - // We need to unwrap and serialize the inner value - // The TypeInfoResolver will handle skipping undefined values - - if (value.IsUndefined) - { - // This shouldn't be called for undefined values due to ShouldSerialize - // But if it is, write null and let the resolver filter it - writer.WriteNullValue(); - return; - } - - // Get the inner value - var innerValue = value.Value; - - // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) - if (innerValue is null) - { - writer.WriteNullValue(); - return; - } - - // Serialize the unwrapped value - JsonSerializer.Serialize(writer, innerValue, options); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs deleted file mode 100644 index 4c4c4073a0ae..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/OptionalAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as optional in the OpenAPI specification. -/// Optional properties use the Optional type and can be undefined (not present in JSON). -/// -/// -/// Properties marked with [Optional] should use the Optional type: -/// - Undefined: Optional.Undefined → omitted from JSON -/// - Defined: Optional.Of(value) → written to JSON -/// -/// Combine with [Nullable] to allow null values: -/// - [Optional, Nullable] Optional → can be undefined, null, or a value -/// - [Optional] Optional → can be undefined or a value (null is invalid) -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs deleted file mode 100644 index 8b43322350bd..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/Public/AdditionalProperties.cs +++ /dev/null @@ -1,353 +0,0 @@ -using global::System.Collections; -using global::System.Collections.ObjectModel; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using SeedApi.Core; - -namespace SeedApi; - -public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties -{ - internal ReadOnlyAdditionalProperties() { } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record ReadOnlyAdditionalProperties : IReadOnlyDictionary -{ - private readonly Dictionary _extensionData = new(); - private readonly Dictionary _convertedCache = new(); - - internal ReadOnlyAdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - if (kvp.Value is JsonElement element) - { - _extensionData.Add(kvp.Key, element); - } - else - { - _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); - } - - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(JsonElement value) - { - if (typeof(T) == typeof(JsonElement)) - { - return (T)(object)value; - } - - return value.Deserialize(JsonOptions.JsonSerializerOptions)!; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var cached)) - { - return cached; - } - - var value = ConvertToT(_extensionData[key]); - _convertedCache[key] = value; - return value; - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public int Count => _extensionData.Count; - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var element)) - { - value = ConvertToT(element); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public T this[string key] => GetCached(key); - - public IEnumerable Keys => _extensionData.Keys; - - public IEnumerable Values => Keys.Select(GetCached); -} - -public record AdditionalProperties : AdditionalProperties -{ - public AdditionalProperties() { } - - public AdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record AdditionalProperties : IDictionary -{ - private readonly Dictionary _extensionData; - private readonly Dictionary _convertedCache; - - public AdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - public AdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - _extensionData[kvp.Key] = kvp.Value; - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(object? extensionDataValue) - { - return extensionDataValue switch - { - T value => value, - JsonElement jsonElement => jsonElement.Deserialize( - JsonOptions.JsonSerializerOptions - )!, - JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, - _ => JsonUtils - .SerializeToElement(extensionDataValue) - .Deserialize(JsonOptions.JsonSerializerOptions)!, - }; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - internal void CopyToExtensionData(IDictionary extensionData) - { - extensionData.Clear(); - foreach (var kvp in _extensionData) - { - extensionData[kvp.Key] = kvp.Value; - } - } - - public JsonObject ToJsonObject() => - ( - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ) - ).AsObject(); - - public JsonNode ToJsonNode() => - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ); - - public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); - - public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); - - public IReadOnlyDictionary ToJsonElementDictionary() - { - return new ReadOnlyDictionary( - _extensionData.ToDictionary( - kvp => kvp.Key, - kvp => - { - if (kvp.Value is JsonElement jsonElement) - { - return jsonElement; - } - - return JsonUtils.SerializeToElement(kvp.Value); - } - ) - ); - } - - public ICollection Keys => _extensionData.Keys; - - public ICollection Values - { - get - { - var values = new T[_extensionData.Count]; - var i = 0; - foreach (var key in Keys) - { - values[i++] = GetCached(key); - } - - return values; - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var value)) - { - return value; - } - - value = ConvertToT(_extensionData[key]); - _convertedCache.Add(key, value); - return value; - } - - private void SetCached(string key, T value) - { - _extensionData[key] = value; - _convertedCache[key] = value; - } - - private void AddCached(string key, T value) - { - _extensionData.Add(key, value); - _convertedCache.Add(key, value); - } - - private bool RemoveCached(string key) - { - var isRemoved = _extensionData.Remove(key); - _convertedCache.Remove(key); - return isRemoved; - } - - public int Count => _extensionData.Count; - public bool IsReadOnly => false; - - public T this[string key] - { - get => GetCached(key); - set => SetCached(key, value); - } - - public void Add(string key, T value) => AddCached(key, value); - - public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); - - public bool Remove(string key) => RemoveCached(key); - - public bool Remove(KeyValuePair item) => RemoveCached(item.Key); - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool Contains(KeyValuePair item) - { - return _extensionData.ContainsKey(item.Key) - && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); - } - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var extensionDataValue)) - { - value = ConvertToT(extensionDataValue); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public void Clear() - { - _extensionData.Clear(); - _convertedCache.Clear(); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array is null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (arrayIndex < 0 || arrayIndex > array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } - - if (array.Length - arrayIndex < _extensionData.Count) - { - throw new ArgumentException( - "The array does not have enough space to copy the elements." - ); - } - - foreach (var kvp in _extensionData) - { - array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); - } - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs deleted file mode 100644 index f33d49028884..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/Public/FileParameter.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace SeedApi; - -/// -/// File parameter for uploading files. -/// -public record FileParameter : IDisposable -#if NET6_0_OR_GREATER - , IAsyncDisposable -#endif -{ - private bool _disposed; - - /// - /// The name of the file to be uploaded. - /// - public string? FileName { get; set; } - - /// - /// The content type of the file to be uploaded. - /// - public string? ContentType { get; set; } - - /// - /// The content of the file to be uploaded. - /// - public required Stream Stream { get; set; } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - if (disposing) - { - Stream.Dispose(); - } - - _disposed = true; - } - -#if NET6_0_OR_GREATER - /// - public async ValueTask DisposeAsync() - { - if (!_disposed) - { - await Stream.DisposeAsync().ConfigureAwait(false); - _disposed = true; - } - - GC.SuppressFinalize(this); - } -#endif - - public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs b/seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs deleted file mode 100644 index 3d210b7e0b4c..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/Public/Version.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -[Serializable] -internal class Version -{ - public const string Current = "0.0.1"; -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs b/seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs deleted file mode 100644 index 9f1f4a1c1181..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/StringEnum.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -public interface IStringEnum : IEquatable -{ - public string Value { get; } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs deleted file mode 100644 index 704cb6836ab8..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Core/StringEnumExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -internal static class StringEnumExtensions -{ - public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; -} diff --git a/seed/csharp-model/allof/src/SeedApi/Describable.cs b/seed/csharp-model/allof/src/SeedApi/Describable.cs deleted file mode 100644 index f521e9082be7..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Describable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Describable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Display name from Describable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs b/seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs deleted file mode 100644 index 7bc064682236..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/DetailedOrg.cs +++ /dev/null @@ -1,28 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("metadata")] - public DetailedOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs b/seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs deleted file mode 100644 index 798903997238..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/DetailedOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from DetailedOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Custom domain name. - /// - [JsonPropertyName("domain")] - public string? Domain { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Identifiable.cs b/seed/csharp-model/allof/src/SeedApi/Identifiable.cs deleted file mode 100644 index 05b3808b610b..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Identifiable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Identifiable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Identifiable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/Organization.cs b/seed/csharp-model/allof/src/SeedApi/Organization.cs deleted file mode 100644 index e90644924f99..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/Organization.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Organization : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public BaseOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs b/seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs deleted file mode 100644 index 9f4b1b5c0820..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/PaginatedResult.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PaginatedResult : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable Results { get; set; } = new List(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/PagingCursors.cs b/seed/csharp-model/allof/src/SeedApi/PagingCursors.cs deleted file mode 100644 index b1f0001a4733..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/PagingCursors.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PagingCursors : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Cursor for the next page of results. - /// - [JsonPropertyName("next")] - public required string Next { get; set; } - - /// - /// Cursor for the previous page of results. - /// - [JsonPropertyName("previous")] - public string? Previous { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs b/seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs deleted file mode 100644 index d22a26079f4e..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/RuleExecutionContext.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] -[Serializable] -public readonly record struct RuleExecutionContext : IStringEnum -{ - public static readonly RuleExecutionContext Prod = new(Values.Prod); - - public static readonly RuleExecutionContext Staging = new(Values.Staging); - - public static readonly RuleExecutionContext Dev = new(Values.Dev); - - public RuleExecutionContext(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleExecutionContext FromCustom(string value) - { - return new RuleExecutionContext(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleExecutionContext value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleExecutionContext value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleExecutionContext value) => value.Value; - - public static explicit operator RuleExecutionContext(string value) => new(value); - - internal class RuleExecutionContextSerializer : JsonConverter - { - public override RuleExecutionContext Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleExecutionContext ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Prod = "prod"; - - public const string Staging = "staging"; - - public const string Dev = "dev"; - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleResponse.cs b/seed/csharp-model/allof/src/SeedApi/RuleResponse.cs deleted file mode 100644 index 6459faa88dfd..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/RuleResponse.cs +++ /dev/null @@ -1,65 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("status")] - public required RuleResponseStatus Status { get; set; } - - [JsonPropertyName("executionContext")] - public RuleExecutionContext? ExecutionContext { get; set; } - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs b/seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs deleted file mode 100644 index 9b587cdbcba0..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/RuleResponseStatus.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] -[Serializable] -public readonly record struct RuleResponseStatus : IStringEnum -{ - public static readonly RuleResponseStatus Active = new(Values.Active); - - public static readonly RuleResponseStatus Inactive = new(Values.Inactive); - - public static readonly RuleResponseStatus Draft = new(Values.Draft); - - public RuleResponseStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleResponseStatus FromCustom(string value) - { - return new RuleResponseStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleResponseStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleResponseStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleResponseStatus value) => value.Value; - - public static explicit operator RuleResponseStatus(string value) => new(value); - - internal class RuleResponseStatusSerializer : JsonConverter - { - public override RuleResponseStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleResponseStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Inactive = "inactive"; - - public const string Draft = "draft"; - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleType.cs b/seed/csharp-model/allof/src/SeedApi/RuleType.cs deleted file mode 100644 index 578b90315dde..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/RuleType.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleType : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("description")] - public string? Description { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs b/seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs deleted file mode 100644 index fa14fff1bde0..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/RuleTypeSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleTypeSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props b/seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props deleted file mode 100644 index 17a84cada530..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/SeedApi.Custom.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/seed/csharp-model/allof/src/SeedApi/SeedApi.csproj b/seed/csharp-model/allof/src/SeedApi/SeedApi.csproj deleted file mode 100644 index 5393dfc14261..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/SeedApi.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - net462;net8.0;net9.0;netstandard2.0 - enable - 12 - enable - 0.0.1 - $(Version) - $(Version) - README.md - https://github.com/allof/fern - true - - - - false - - - $(DefineConstants);USE_PORTABLE_DATE_ONLY - true - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - <_Parameter1>SeedApi.Test - - - - - diff --git a/seed/csharp-model/allof/src/SeedApi/User.cs b/seed/csharp-model/allof/src/SeedApi/User.cs deleted file mode 100644 index abc389c0a6b6..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/User.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record User : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("email")] - public required string Email { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs b/seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs deleted file mode 100644 index 942b822f3dc4..000000000000 --- a/seed/csharp-model/allof/src/SeedApi/UserSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record UserSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/.editorconfig b/seed/csharp-sdk/allof-inline/.editorconfig deleted file mode 100644 index 1e7a0adbac80..000000000000 --- a/seed/csharp-sdk/allof-inline/.editorconfig +++ /dev/null @@ -1,35 +0,0 @@ -root = true - -[*.cs] -resharper_arrange_object_creation_when_type_evident_highlighting = hint -resharper_auto_property_can_be_made_get_only_global_highlighting = hint -resharper_check_namespace_highlighting = hint -resharper_class_never_instantiated_global_highlighting = hint -resharper_class_never_instantiated_local_highlighting = hint -resharper_collection_never_updated_global_highlighting = hint -resharper_convert_type_check_pattern_to_null_check_highlighting = hint -resharper_inconsistent_naming_highlighting = hint -resharper_member_can_be_private_global_highlighting = hint -resharper_member_hides_static_from_outer_class_highlighting = hint -resharper_not_accessed_field_local_highlighting = hint -resharper_nullable_warning_suppression_is_used_highlighting = suggestion -resharper_partial_type_with_single_part_highlighting = hint -resharper_prefer_concrete_value_over_default_highlighting = none -resharper_private_field_can_be_converted_to_local_variable_highlighting = hint -resharper_property_can_be_made_init_only_global_highlighting = hint -resharper_property_can_be_made_init_only_local_highlighting = hint -resharper_redundant_name_qualifier_highlighting = none -resharper_redundant_using_directive_highlighting = hint -resharper_replace_slice_with_range_indexer_highlighting = none -resharper_unused_auto_property_accessor_global_highlighting = hint -resharper_unused_auto_property_accessor_local_highlighting = hint -resharper_unused_member_global_highlighting = hint -resharper_unused_type_global_highlighting = hint -resharper_use_string_interpolation_highlighting = hint -dotnet_diagnostic.CS1591.severity = suggestion - -[src/**/Types/*.cs] -resharper_check_namespace_highlighting = none - -[src/**/Core/Public/*.cs] -resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/.fern/metadata.json b/seed/csharp-sdk/allof-inline/.fern/metadata.json deleted file mode 100644 index d119d1639273..000000000000 --- a/seed/csharp-sdk/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-csharp-sdk", - "generatorVersion": "local", - "generatorConfig": {}, - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/.github/workflows/ci.yml b/seed/csharp-sdk/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 87068349b616..000000000000 --- a/seed/csharp-sdk/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -env: - DOTNET_NOLOGO: true - -jobs: - ci: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.x - - - name: Install tools - run: dotnet tool restore - - - name: Restore dependencies - run: dotnet restore src/SeedApi/SeedApi.csproj - - - name: Build - run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release - - - name: Restore test dependencies - run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj - - - name: Build tests - run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release - - - name: Test - run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release - - - name: Pack - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release - - - name: Publish to NuGet.org - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} - run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" - diff --git a/seed/csharp-sdk/allof-inline/.gitignore b/seed/csharp-sdk/allof-inline/.gitignore deleted file mode 100644 index 11014f2b33d7..000000000000 --- a/seed/csharp-sdk/allof-inline/.gitignore +++ /dev/null @@ -1,484 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -## This is based on `dotnet new gitignore` and customized by Fern - -# dotenv files -.env - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -# [Rr]elease/ (Ignored by Fern) -# [Rr]eleases/ (Ignored by Fern) -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -# [Ll]og/ (Ignored by Fern) -# [Ll]ogs/ (Ignored by Fern) - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET -project.lock.json -project.fragment.lock.json -artifacts/ - -# Tye -.tye/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml -.idea - -## -## Visual studio for Mac -## - - -# globs -Makefile.in -*.userprefs -*.usertasks -config.make -config.status -aclocal.m4 -install-sh -autom4te.cache/ -*.tar.gz -tarballs/ -test-results/ - -# Mac bundle stuff -*.dmg -*.app - -# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Vim temporary swap files -*.swp diff --git a/seed/csharp-sdk/allof-inline/README.md b/seed/csharp-sdk/allof-inline/README.md deleted file mode 100644 index 939b438133db..000000000000 --- a/seed/csharp-sdk/allof-inline/README.md +++ /dev/null @@ -1,216 +0,0 @@ -# Seed C# Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FC%23) -[![nuget shield](https://img.shields.io/nuget/v/Fernallof-inline)](https://nuget.org/packages/Fernallof-inline) - -The Seed C# library provides convenient access to the Seed APIs from C#. - -## Table of Contents - -- [Requirements](#requirements) -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Raw Response](#raw-response) - - [Additional Headers](#additional-headers) - - [Additional Query Parameters](#additional-query-parameters) - - [Forward Compatible Enums](#forward-compatible-enums) -- [Contributing](#contributing) - -## Requirements - -This SDK requires: - -## Installation - -```sh -dotnet add package Fernallof-inline -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```csharp -using SeedApi; - -var client = new SeedApiClient(); -await client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } -); -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```csharp -using SeedApi; - -var client = new SeedApiClient(new ClientOptions -{ - BaseUrl = SeedApiEnvironment.Default -}); -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```csharp -using SeedApi; - -try { - var response = await client.CreateRuleAsync(...); -} catch (SeedApiApiException e) { - System.Console.WriteLine(e.Body); - System.Console.WriteLine(e.StatusCode); -} -``` - -## Advanced - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `MaxRetries` request option to configure this behavior. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - MaxRetries: 0 // Override MaxRetries at the request level - } -); -``` - -### Timeouts - -The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s - } -); -``` - -### Raw Response - -Access raw HTTP response data (status code, headers, URL) alongside parsed response data using the `.WithRawResponse()` method. - -```csharp -using SeedApi; - -// Access raw response data (status code, headers, etc.) alongside the parsed response -var result = await client.CreateRuleAsync(...).WithRawResponse(); - -// Access the parsed data -var data = result.Data; - -// Access raw response metadata -var statusCode = result.RawResponse.StatusCode; -var headers = result.RawResponse.Headers; -var url = result.RawResponse.Url; - -// Access specific headers (case-insensitive) -if (headers.TryGetValue("X-Request-Id", out var requestId)) -{ - System.Console.WriteLine($"Request ID: {requestId}"); -} - -// For the default behavior, simply await without .WithRawResponse() -var data = await client.CreateRuleAsync(...); -``` - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - AdditionalHeaders = new Dictionary - { - { "X-Custom-Header", "custom-value" } - } - } -); -``` - -### Additional Query Parameters - -If you would like to send additional query parameters as part of the request, use the `AdditionalQueryParameters` request option. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - AdditionalQueryParameters = new Dictionary - { - { "custom_param", "custom-value" } - } - } -); -``` - -### Forward Compatible Enums - -This SDK uses forward-compatible enums that can handle unknown values gracefully. - -```csharp -using SeedApi; - -// Using a built-in value -var ruleExecutionContext = RuleExecutionContext.Prod; - -// Using a custom value -var customRuleExecutionContext = RuleExecutionContext.FromCustom("custom-value"); - -// Using in a switch statement -switch (ruleExecutionContext.Value) -{ - case RuleExecutionContext.Values.Prod: - Console.WriteLine("Prod"); - break; - default: - Console.WriteLine($"Unknown value: {ruleExecutionContext.Value}"); - break; -} - -// Explicit casting -string ruleExecutionContextString = (string)RuleExecutionContext.Prod; -RuleExecutionContext ruleExecutionContextFromString = (RuleExecutionContext)"prod"; -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/csharp-sdk/allof-inline/SeedApi.slnx b/seed/csharp-sdk/allof-inline/SeedApi.slnx deleted file mode 100644 index d4c63c241aad..000000000000 --- a/seed/csharp-sdk/allof-inline/SeedApi.slnx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/seed/csharp-sdk/allof-inline/reference.md b/seed/csharp-sdk/allof-inline/reference.md deleted file mode 100644 index 09ba75269a0f..000000000000 --- a/seed/csharp-sdk/allof-inline/reference.md +++ /dev/null @@ -1,158 +0,0 @@ -# Reference -
client.SearchRuleTypesAsync(SearchRuleTypesRequest { ... }) -> WithRawResponseTask<RuleTypeSearchResponse> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `SearchRuleTypesRequest` - -
-
-
-
- - -
-
-
- -
client.CreateRuleAsync(RuleCreateRequest { ... }) -> WithRawResponseTask<RuleResponse> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `RuleCreateRequest` - -
-
-
-
- - -
-
-
- -
client.ListUsersAsync() -> WithRawResponseTask<UserSearchResponse> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.ListUsersAsync(); -``` -
-
-
-
- - -
-
-
- -
client.GetEntityAsync() -> WithRawResponseTask<CombinedEntity> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.GetEntityAsync(); -``` -
-
-
-
- - -
-
-
- -
client.GetOrganizationAsync() -> WithRawResponseTask<Organization> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.GetOrganizationAsync(); -``` -
-
-
-
- - -
-
-
- diff --git a/seed/csharp-sdk/allof-inline/snippet.json b/seed/csharp-sdk/allof-inline/snippet.json deleted file mode 100644 index df8ab96f5bc6..000000000000 --- a/seed/csharp-sdk/allof-inline/snippet.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "types": {}, - "endpoints": [ - { - "example_identifier": null, - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.SearchRuleTypesAsync(new SearchRuleTypesRequest());\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.CreateRuleAsync(\n new RuleCreateRequest { Name = \"name\", ExecutionContext = RuleExecutionContext.Prod }\n);\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.ListUsersAsync();\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetEntityAsync();\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetOrganizationAsync();\n" - } - } - ] -} \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs deleted file mode 100644 index 0c6994903107..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example0.cs +++ /dev/null @@ -1,19 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example0 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.SearchRuleTypesAsync( - new SearchRuleTypesRequest() - ); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs deleted file mode 100644 index 44ebc3965c0d..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example1.cs +++ /dev/null @@ -1,21 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example1 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.SearchRuleTypesAsync( - new SearchRuleTypesRequest { - Query = "query" - } - ); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs deleted file mode 100644 index b63878a26a91..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example2.cs +++ /dev/null @@ -1,22 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example2 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.CreateRuleAsync( - new RuleCreateRequest { - Name = "name", - ExecutionContext = RuleExecutionContext.Prod - } - ); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs deleted file mode 100644 index eb0371508e02..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example3.cs +++ /dev/null @@ -1,22 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example3 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.CreateRuleAsync( - new RuleCreateRequest { - Name = "name", - ExecutionContext = RuleExecutionContext.Prod - } - ); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs deleted file mode 100644 index 440f17e55522..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example4.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example4 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.ListUsersAsync(); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs deleted file mode 100644 index c1d081509932..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example5.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example5 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.ListUsersAsync(); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs deleted file mode 100644 index 8694c56b98a0..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example6.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example6 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetEntityAsync(); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs deleted file mode 100644 index bc47bce361fb..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example7.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example7 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetEntityAsync(); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs deleted file mode 100644 index 897c0c509b2e..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example8.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example8 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetOrganizationAsync(); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs deleted file mode 100644 index 2f7b06bd2b7b..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/Example9.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example9 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetOrganizationAsync(); - } - -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj deleted file mode 100644 index 3417db2e58e2..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net8.0 - 12 - enable - enable - - - - - - \ No newline at end of file diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs deleted file mode 100644 index 7ad11f298637..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/HeadersBuilderTests.cs +++ /dev/null @@ -1,326 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class HeadersBuilderTests -{ - [Test] - public async global::System.Threading.Tasks.Task Add_SimpleHeaders() - { - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") - .Add("Authorization", "Bearer token123") - .Add("X-API-Key", "key456") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); - Assert.That(headers["Authorization"], Is.EqualTo("Bearer token123")); - Assert.That(headers["X-API-Key"], Is.EqualTo("key456")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_NullValuesIgnored() - { - var headers = await new HeadersBuilder.Builder() - .Add("Header1", "value1") - .Add("Header2", null) - .Add("Header3", "value3") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(2)); - Assert.That(headers.ContainsKey("Header1"), Is.True); - Assert.That(headers.ContainsKey("Header2"), Is.False); - Assert.That(headers.ContainsKey("Header3"), Is.True); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_OverwritesExistingHeader() - { - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") - .Add("Content-Type", "application/xml") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_MergesExistingHeaders() - { - var existingHeaders = new Headers( - new Dictionary { { "Header1", "value1" }, { "Header2", "value2" } } - ); - - var result = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(3)); - Assert.That(result["Header1"], Is.EqualTo("value1")); - Assert.That(result["Header2"], Is.EqualTo("value2")); - Assert.That(result["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_OverwritesExistingHeaders() - { - var existingHeaders = new Headers( - new Dictionary { { "Header1", "override" } } - ); - - var result = await new HeadersBuilder.Builder() - .Add("Header1", "original") - .Add("Header2", "keep") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(2)); - Assert.That(result["Header1"], Is.EqualTo("override")); - Assert.That(result["Header2"], Is.EqualTo("keep")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_NullHeadersIgnored() - { - var result = await new HeadersBuilder.Builder() - .Add("Header1", "value1") - .Add((Headers?)null) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(1)); - Assert.That(result["Header1"], Is.EqualTo("value1")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_AddsHeaders() - { - var additionalHeaders = new List> - { - new("Header1", "value1"), - new("Header2", "value2"), - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(additionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - Assert.That(headers["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_IgnoresNullValues() - { - var additionalHeaders = new List> - { - new("Header1", "value1"), - new("Header2", null), // Should be ignored - }; - - var headers = await new HeadersBuilder.Builder() - .Add(additionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers.ContainsKey("Header2"), Is.False); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_DictionaryOverload_AddsHeaders() - { - var dict = new Dictionary - { - { "Header1", "value1" }, - { "Header2", "value2" }, - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(dict) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - Assert.That(headers["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task EmptyBuilder_ReturnsEmptyHeaders() - { - var headers = await new HeadersBuilder.Builder().BuildAsync().ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task OnlyNullValues_ReturnsEmptyHeaders() - { - var headers = await new HeadersBuilder.Builder() - .Add("Header1", null) - .Add("Header2", null) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task ComplexMergingScenario() - { - // Simulates real SDK usage: endpoint headers + client headers + request options - var clientHeaders = new Headers( - new Dictionary - { - { "X-Client-Version", "1.0.0" }, - { "User-Agent", "MyClient/1.0" }, - } - ); - - var clientAdditionalHeaders = new List> - { - new("X-Custom-Header", "custom-value"), - }; - - var requestOptionsHeaders = new Headers( - new Dictionary - { - { "Authorization", "Bearer user-token" }, - { "User-Agent", "MyClient/2.0" }, // Override - } - ); - - var requestAdditionalHeaders = new List> - { - new("X-Request-ID", "req-123"), - new("X-Custom-Header", "overridden-value"), // Override - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") // Endpoint header - .Add("X-Endpoint-ID", "endpoint-1") - .Add(clientHeaders) - .Add(clientAdditionalHeaders) - .Add(requestOptionsHeaders) - .Add(requestAdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - // Verify precedence - Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); - Assert.That(headers["X-Endpoint-ID"], Is.EqualTo("endpoint-1")); - Assert.That(headers["X-Client-Version"], Is.EqualTo("1.0.0")); - Assert.That(headers["User-Agent"], Is.EqualTo("MyClient/2.0")); // Overridden - Assert.That(headers["Authorization"], Is.EqualTo("Bearer user-token")); - Assert.That(headers["X-Request-ID"], Is.EqualTo("req-123")); - Assert.That(headers["X-Custom-Header"], Is.EqualTo("overridden-value")); // Overridden - } - - [Test] - public async global::System.Threading.Tasks.Task Builder_WithCapacity() - { - // Test that capacity constructor works without errors - var headers = await new HeadersBuilder.Builder(capacity: 10) - .Add("Header1", "value1") - .Add("Header2", "value2") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(2)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_ResolvesDynamicHeaderValues() - { - // Test that BuildAsync properly resolves HeaderValue instances - var existingHeaders = new Headers(); - existingHeaders["DynamicHeader"] = - (Func>)( - () => global::System.Threading.Tasks.Task.FromResult("dynamic-value") - ); - - var result = await new HeadersBuilder.Builder() - .Add("StaticHeader", "static-value") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(2)); - Assert.That(result["StaticHeader"], Is.EqualTo("static-value")); - Assert.That(result["DynamicHeader"], Is.EqualTo("dynamic-value")); - } - - [Test] - public async global::System.Threading.Tasks.Task MultipleSyncAdds() - { - var headers1 = new Headers(new Dictionary { { "H1", "v1" } }); - var headers2 = new Headers(new Dictionary { { "H2", "v2" } }); - var headers3 = new Headers(new Dictionary { { "H3", "v3" } }); - - var result = await new HeadersBuilder.Builder() - .Add(headers1) - .Add(headers2) - .Add(headers3) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(3)); - Assert.That(result["H1"], Is.EqualTo("v1")); - Assert.That(result["H2"], Is.EqualTo("v2")); - Assert.That(result["H3"], Is.EqualTo("v3")); - } - - [Test] - public async global::System.Threading.Tasks.Task PrecedenceOrder_LatestWins() - { - // Test that later operations override earlier ones - var headers1 = new Headers(new Dictionary { { "Key", "value1" } }); - var headers2 = new Headers(new Dictionary { { "Key", "value2" } }); - var additional = new List> { new("Key", "value3") }; - - var result = await new HeadersBuilder.Builder() - .Add("Key", "value0") - .Add(headers1) - .Add(headers2) - .Add(additional) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result["Key"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task CaseInsensitiveKeys() - { - // Test that header keys are case-insensitive - var headers = await new HeadersBuilder.Builder() - .Add("content-type", "application/json") - .Add("Content-Type", "application/xml") // Should overwrite - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers["content-type"], Is.EqualTo("application/xml")); - Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); - Assert.That(headers["CONTENT-TYPE"], Is.EqualTo("application/xml")); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs deleted file mode 100644 index a12183113312..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs +++ /dev/null @@ -1,365 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class AdditionalPropertiesTests -{ - [Test] - public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); - Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); - }); - } - - [Test] - public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecord - { - Id = "1", - AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.Id, Is.EqualTo("1")); - Assert.That( - deserializedRecord.AdditionalProperties["category"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), - Is.EqualTo("fiction") - ); - Assert.That( - deserializedRecord.AdditionalProperties["title"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() - { - // Arrange - var extensionData = new Dictionary - { - ["key1"] = JsonUtils.SerializeToElement("value1"), - ["key2"] = JsonUtils.SerializeToElement(123), - }; - var readOnlyProps = new ReadOnlyAdditionalProperties(); - readOnlyProps.CopyFromExtensionData(extensionData); - - // Act & Assert - Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); - Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); - } - - [Test] - public void AdditionalProperties_ShouldBehaveAsDictionary() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - additionalProps["key3"] = true; - - // Assert - Assert.Multiple(() => - { - Assert.That(additionalProps["key1"], Is.EqualTo("value1")); - Assert.That(additionalProps["key2"], Is.EqualTo(123)); - Assert.That((bool)additionalProps["key3"]!, Is.True); - Assert.That(additionalProps.Count, Is.EqualTo(3)); - }); - } - - [Test] - public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - var jsonObject = additionalProps.ToJsonObject(); - - Assert.Multiple(() => - { - // Assert - Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); - Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); - }); - } - - [Test] - public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - var record = JsonUtils.Deserialize(json); - - // Act - record.AdditionalProperties["category"] = "non-fiction"; - - // Assert - Assert.Multiple(() => - { - Assert.That(record, Is.Not.Null); - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); - Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); - Assert.That( - ((JsonElement)record.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": 42, - "extra2": 99 - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithInts - { - AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": { "key1": true, "key2": false }, - "extra2": { "key3": true } - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithDictionaries - { - AdditionalProperties = - { - ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, - ["extra2"] = new Dictionary { { "key3", true } }, - }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - private record Record : IJsonOnDeserialized - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithInts : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithDictionaries : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties< - Dictionary - > AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties> AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs deleted file mode 100644 index c0f258680b78..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateOnlyJsonTests -{ - [Test] - public void SerializeDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly? dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly? expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateOnlyKey() - { - var key = new DateOnly(2023, 10, 5); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateOnlyKey() - { - var json = """ - { - "2023-10-05": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateOnly(2023, 10, 5); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs deleted file mode 100644 index 1dde45a8e939..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateTimeJsonTests -{ - [Test] - public void SerializeDateTime_ShouldMatchExpectedFormat() - { - (DateTime dateTime, string expected)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - foreach (var (dateTime, expected) in testCases) - { - var json = JsonUtils.Serialize(dateTime); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateTime_ShouldMatchExpectedDateTime() - { - (DateTime expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateTime_ShouldMatchExpectedFormat() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateTimeKey() - { - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateTimeKey() - { - var json = """ - { - "2023-10-05T14:30:00.000Z": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs deleted file mode 100644 index 969acd620998..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class JsonAccessAttributeTests -{ - private class MyClass - { - [JsonPropertyName("read_only_prop")] - [JsonAccess(JsonAccessType.ReadOnly)] - public string? ReadOnlyProp { get; set; } - - [JsonPropertyName("write_only_prop")] - [JsonAccess(JsonAccessType.WriteOnly)] - public string? WriteOnlyProp { get; set; } - - [JsonPropertyName("normal_prop")] - public string? NormalProp { get; set; } - - [JsonPropertyName("read_only_nullable_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable? ReadOnlyNullableList { get; set; } - - [JsonPropertyName("read_only_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable ReadOnlyList { get; set; } = []; - - [JsonPropertyName("write_only_nullable_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable? WriteOnlyNullableList { get; set; } - - [JsonPropertyName("write_only_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable WriteOnlyList { get; set; } = []; - - [JsonPropertyName("normal_list")] - public IEnumerable NormalList { get; set; } = []; - - [JsonPropertyName("normal_nullable_list")] - public IEnumerable? NullableNormalList { get; set; } - } - - [Test] - public void JsonAccessAttribute_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "write_only_prop": "write", - "normal_prop": "normal_prop", - "read_only_nullable_list": ["item1", "item2"], - "read_only_list": ["item3", "item4"], - "write_only_nullable_list": ["item5", "item6"], - "write_only_list": ["item7", "item8"], - "normal_list": ["normal1", "normal2"], - "normal_nullable_list": ["normal1", "normal2"] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // String properties - Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); - Assert.That(obj.WriteOnlyProp, Is.Null); - Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); - - // List properties - read only - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Not.Null); - Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); - Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); - Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); - - var readOnlyList = obj.ReadOnlyList.ToArray(); - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Has.Length.EqualTo(2)); - Assert.That(readOnlyList[0], Is.EqualTo("item3")); - Assert.That(readOnlyList[1], Is.EqualTo("item4")); - - // List properties - write only - Assert.That(obj.WriteOnlyNullableList, Is.Null); - Assert.That(obj.WriteOnlyList, Is.Not.Null); - Assert.That(obj.WriteOnlyList, Is.Empty); - - // Normal list property - var normalList = obj.NormalList.ToArray(); - Assert.That(normalList, Is.Not.Null); - Assert.That(normalList, Has.Length.EqualTo(2)); - Assert.That(normalList[0], Is.EqualTo("normal1")); - Assert.That(normalList[1], Is.EqualTo("normal2")); - }); - - // Set up values for serialization - obj.WriteOnlyProp = "write"; - obj.NormalProp = "new_value"; - obj.WriteOnlyNullableList = new List { "write1", "write2" }; - obj.WriteOnlyList = new List { "write3", "write4" }; - obj.NormalList = new List { "new_normal" }; - obj.NullableNormalList = new List { "new_normal" }; - - var serializedJson = JsonUtils.Serialize(obj); - const string expectedJson = """ - { - "write_only_prop": "write", - "normal_prop": "new_value", - "write_only_nullable_list": [ - "write1", - "write2" - ], - "write_only_list": [ - "write3", - "write4" - ], - "normal_list": [ - "new_normal" - ], - "normal_nullable_list": [ - "new_normal" - ] - } - """; - Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); - } - - [Test] - public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "normal_prop": "normal_prop", - "read_only_nullable_list": null, - "read_only_list": [] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // Read-only nullable list should be null when JSON contains null - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Null); - - // Read-only non-nullable list should never be null, but empty when JSON contains null - var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Is.Empty); - }); - - // Serialize and verify read-only lists are not included - var serializedJson = JsonUtils.Serialize(obj); - Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); - Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); - Assert.That(serializedJson, Does.Not.Contain("read_only_list")); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs deleted file mode 100644 index 493d8e99c329..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringBuilderTests.cs +++ /dev/null @@ -1,658 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class QueryStringBuilderTests -{ - [Test] - public void Build_SimpleParameters() - { - var parameters = new List> - { - new("name", "John Doe"), - new("age", "30"), - new("city", "New York"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo("?name=John%20Doe&age=30&city=New%20York")); - } - - [Test] - public void Build_EmptyList_ReturnsEmptyString() - { - var parameters = new List>(); - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Build_SpecialCharacters() - { - var parameters = new List> - { - new("email", "test@example.com"), - new("url", "https://example.com/path?query=value"), - new("special", "a+b=c&d"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That( - result, - Is.EqualTo( - "?email=test@example.com&url=https://example.com/path?query=value&special=a%2Bb=c%26d" - ) - ); - } - - [Test] - public void Build_UnicodeCharacters() - { - var parameters = new List> { new("greeting", "Hello 世界") }; - - var result = QueryStringBuilder.Build(parameters); - - // Verify the Chinese characters are properly UTF-8 encoded - Assert.That(result, Does.StartWith("?greeting=Hello%20")); - Assert.That(result, Does.Contain("%E4%B8%96%E7%95%8C")); // 世界 - } - - [Test] - public void Build_SessionSettings_DeepObject() - { - // Simulate session settings with nested properties - var sessionSettings = new - { - custom_session_id = "my-custom-session-id", - system_prompt = "You are a helpful assistant", - variables = new Dictionary - { - { "userName", "John" }, - { "userAge", 30 }, - { "isPremium", true }, - }, - }; - - // Build query parameters list - var queryParams = new List> { new("api_key", "test_key_123") }; - - // Add session_settings with prefix using the new overload - queryParams.AddRange( - QueryStringConverter.ToDeepObject("session_settings", sessionSettings) - ); - - var result = QueryStringBuilder.Build(queryParams); - - // Verify the result contains properly formatted deep object notation - // Note: Square brackets are URL-encoded as %5B and %5D - Assert.That(result, Does.StartWith("?api_key=test_key_123")); - Assert.That( - result, - Does.Contain("session_settings%5Bcustom_session_id%5D=my-custom-session-id") - ); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20a%20helpful%20assistant") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BisPremium%5D=true")); - - // Verify it's NOT JSON encoded (no braces or quotes in the original format) - Assert.That(result, Does.Not.Contain("%7B%22")); // Not {" sequence - } - - [Test] - public void Build_ChatApiLikeParameters() - { - // Simulate what ChatApi constructor does - var sessionSettings = new - { - system_prompt = "You are helpful", - variables = new Dictionary { { "name", "Alice" } }, - }; - - var queryParams = new List>(); - - // Simple parameters - var simpleParams = new Dictionary - { - { "access_token", "token123" }, - { "config_id", "config456" }, - { "api_key", "key789" }, - }; - queryParams.AddRange(QueryStringConverter.ToExplodedForm(simpleParams)); - - // Session settings as deep object with prefix - queryParams.AddRange( - QueryStringConverter.ToDeepObject("session_settings", sessionSettings) - ); - - var result = QueryStringBuilder.Build(queryParams); - - // Verify structure (square brackets are URL-encoded) - Assert.That(result, Does.StartWith("?")); - Assert.That(result, Does.Contain("access_token=token123")); - Assert.That(result, Does.Contain("config_id=config456")); - Assert.That(result, Does.Contain("api_key=key789")); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); - } - - [Test] - public void Build_ReservedCharacters_NotEncoded() - { - var parameters = new List> - { - new("path", "some-path"), - new("id", "123-456_789.test~value"), - }; - - var result = QueryStringBuilder.Build(parameters); - - // Safe query characters include RFC 3986 unreserved + sub-delimiters (except & = +) + : @ / - Assert.That(result, Is.EqualTo("?path=some-path&id=123-456_789.test~value")); - } - - [Test] - public void Builder_Add_SimpleParameters() - { - var result = new QueryStringBuilder.Builder() - .Add("name", "John Doe") - .Add("age", 30) - .Add("active", true) - .Build(); - - Assert.That(result, Does.Contain("name=John%20Doe")); - Assert.That(result, Does.Contain("age=30")); - Assert.That(result, Does.Contain("active=true")); - } - - [Test] - public void Builder_Add_NullValuesIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("name", "John") - .Add("middle", null) - .Add("age", 30) - .Build(); - - Assert.That(result, Does.Contain("name=John")); - Assert.That(result, Does.Contain("age=30")); - Assert.That(result, Does.Not.Contain("middle")); - } - - [Test] - public void Builder_AddDeepObject_WithPrefix() - { - var settings = new - { - custom_session_id = "id-123", - system_prompt = "You are helpful", - variables = new { name = "Alice", age = 25 }, - }; - - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddDeepObject("session_settings", settings) - .Build(); - - Assert.That(result, Does.Contain("api_key=key123")); - Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=id-123")); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bage%5D=25")); - } - - [Test] - public void Builder_AddDeepObject_NullIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddDeepObject("settings", null) - .Build(); - - Assert.That(result, Is.EqualTo("?api_key=key123")); - Assert.That(result, Does.Not.Contain("settings")); - } - - [Test] - public void Builder_AddExploded_WithPrefix() - { - var filter = new { status = "active", type = "user" }; - - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddExploded("filter", filter) - .Build(); - - Assert.That(result, Does.Contain("api_key=key123")); - Assert.That(result, Does.Contain("filter%5Bstatus%5D=active")); - Assert.That(result, Does.Contain("filter%5Btype%5D=user")); - } - - [Test] - public void Builder_AddExploded_NullIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddExploded("filter", null) - .Build(); - - Assert.That(result, Is.EqualTo("?api_key=key123")); - Assert.That(result, Does.Not.Contain("filter")); - } - - [Test] - public void Builder_WithCapacity() - { - // Test that capacity constructor works without errors - var result = new QueryStringBuilder.Builder(capacity: 10) - .Add("param1", "value1") - .Add("param2", "value2") - .Build(); - - Assert.That(result, Does.Contain("param1=value1")); - Assert.That(result, Does.Contain("param2=value2")); - } - - [Test] - public void Builder_ChatApiLikeUsage() - { - // Simulate real usage from ChatApi - var sessionSettings = new - { - custom_session_id = "session-123", - variables = new Dictionary - { - { "userName", "John" }, - { "userAge", 30 }, - }, - }; - - var result = new QueryStringBuilder.Builder(capacity: 16) - .Add("access_token", "token123") - .Add("allow_connection", true) - .Add("config_id", "config456") - .Add("api_key", "key789") - .AddDeepObject("session_settings", sessionSettings) - .Build(); - - Assert.That(result, Does.StartWith("?")); - Assert.That(result, Does.Contain("access_token=token123")); - Assert.That(result, Does.Contain("allow_connection=true")); - Assert.That(result, Does.Contain("config_id=config456")); - Assert.That(result, Does.Contain("api_key=key789")); - Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=session-123")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); - } - - [Test] - public void Builder_EmptyBuilder_ReturnsEmptyString() - { - var result = new QueryStringBuilder.Builder().Build(); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Builder_OnlyNullValues_ReturnsEmptyString() - { - var result = new QueryStringBuilder.Builder() - .Add("param1", null) - .Add("param2", null) - .AddDeepObject("settings", null) - .Build(); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Builder_Set_OverridesSingleValue() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Set("foo", "override") - .Build(); - - Assert.That(result, Is.EqualTo("?foo=override")); - } - - [Test] - public void Builder_Set_OverridesMultipleValues() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "value1") - .Add("foo", "value2") - .Set("foo", "override") - .Build(); - - Assert.That(result, Is.EqualTo("?foo=override")); - } - - [Test] - public void Builder_Set_WithArray_CreatesMultipleParameters() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Set("foo", new[] { "value1", "value2" }) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value1&foo=value2")); - } - - [Test] - public void Builder_Set_WithNull_RemovesParameter() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Add("bar", "keep") - .Set("foo", null) - .Build(); - - Assert.That(result, Is.EqualTo("?bar=keep")); - } - - [Test] - public void Builder_MergeAdditional_WithSingleValues() - { - var additional = new List> - { - new("foo", "bar"), - new("baz", "qux"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("existing", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("existing=value")); - Assert.That(result, Does.Contain("foo=bar")); - Assert.That(result, Does.Contain("baz=qux")); - } - - [Test] - public void Builder_MergeAdditional_WithDuplicateKeys_CreatesList() - { - var additional = new List> - { - new("foo", "bar1"), - new("foo", "bar2"), - new("baz", "qux"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("existing", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("existing=value")); - Assert.That(result, Does.Contain("foo=bar1")); - Assert.That(result, Does.Contain("foo=bar2")); - Assert.That(result, Does.Contain("baz=qux")); - } - - [Test] - public void Builder_MergeAdditional_OverridesExistingParameters() - { - var additional = new List> { new("foo", "override") }; - - var result = new QueryStringBuilder.Builder() - .Add("foo", "original1") - .Add("foo", "original2") - .Add("bar", "keep") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("bar=keep")); - Assert.That(result, Does.Contain("foo=override")); - Assert.That(result, Does.Not.Contain("original1")); - Assert.That(result, Does.Not.Contain("original2")); - } - - [Test] - public void Builder_MergeAdditional_WithDuplicates_OverridesExisting() - { - var additional = new List> - { - new("foo", "new1"), - new("foo", "new2"), - new("foo", "new3"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("foo", "original1") - .Add("foo", "original2") - .Add("bar", "keep") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("bar=keep")); - Assert.That(result, Does.Contain("foo=new1")); - Assert.That(result, Does.Contain("foo=new2")); - Assert.That(result, Does.Contain("foo=new3")); - Assert.That(result, Does.Not.Contain("original1")); - Assert.That(result, Does.Not.Contain("original2")); - } - - [Test] - public void Builder_MergeAdditional_WithNull_NoOp() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "value") - .MergeAdditional(null) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value")); - } - - [Test] - public void Builder_MergeAdditional_WithEmptyList_NoOp() - { - var additional = new List>(); - - var result = new QueryStringBuilder.Builder() - .Add("foo", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value")); - } - - [Test] - public void Builder_MergeAdditional_RealWorldScenario() - { - // SDK generates foo=foo1&foo=foo2 - var builder = new QueryStringBuilder.Builder() - .Add("foo", "foo1") - .Add("foo", "foo2") - .Add("bar", "baz"); - - // User provides foo=override in AdditionalQueryParameters - var additional = new List> { new("foo", "override") }; - - var result = builder.MergeAdditional(additional).Build(); - - // Result should be foo=override&bar=baz (user overrides SDK) - Assert.That(result, Does.Contain("bar=baz")); - Assert.That(result, Does.Contain("foo=override")); - Assert.That(result, Does.Not.Contain("foo1")); - Assert.That(result, Does.Not.Contain("foo2")); - } - - [Test] - public void Builder_MergeAdditional_UserProvidesMultipleValues() - { - // SDK generates no foo parameter - var builder = new QueryStringBuilder.Builder().Add("bar", "baz"); - - // User provides foo=bar1&foo=bar2 in AdditionalQueryParameters - var additional = new List> - { - new("foo", "bar1"), - new("foo", "bar2"), - }; - - var result = builder.MergeAdditional(additional).Build(); - - // Result should be bar=baz&foo=bar1&foo=bar2 - Assert.That(result, Does.Contain("bar=baz")); - Assert.That(result, Does.Contain("foo=bar1")); - Assert.That(result, Does.Contain("foo=bar2")); - } - - [Test] - public void Builder_Add_WithCollection_CreatesMultipleParameters() - { - var tags = new[] { "tag1", "tag2", "tag3" }; - var result = new QueryStringBuilder.Builder().Add("tag", tags).Build(); - - Assert.That(result, Does.Contain("tag=tag1")); - Assert.That(result, Does.Contain("tag=tag2")); - Assert.That(result, Does.Contain("tag=tag3")); - } - - [Test] - public void Builder_Add_WithList_CreatesMultipleParameters() - { - var ids = new List { 1, 2, 3 }; - var result = new QueryStringBuilder.Builder().Add("id", ids).Build(); - - Assert.That(result, Does.Contain("id=1")); - Assert.That(result, Does.Contain("id=2")); - Assert.That(result, Does.Contain("id=3")); - } - - [Test] - public void Builder_Set_WithCollection_ReplacesAllPreviousValues() - { - var result = new QueryStringBuilder.Builder() - .Add("id", 1) - .Add("id", 2) - .Set("id", new[] { 10, 20, 30 }) - .Build(); - - Assert.That(result, Does.Contain("id=10")); - Assert.That(result, Does.Contain("id=20")); - Assert.That(result, Does.Contain("id=30")); - // Check that old values are not present (use word boundaries to avoid false positives with id=10) - Assert.That(result, Does.Not.Contain("id=1&")); - Assert.That(result, Does.Not.Contain("id=2&")); - Assert.That(result, Does.Not.Contain("id=1?")); - Assert.That(result, Does.Not.Contain("id=2?")); - Assert.That(result, Does.Not.EndWith("id=1")); - Assert.That(result, Does.Not.EndWith("id=2")); - } - - [Test] - public void EncodePathSegment_UnreservedChars_NotEncoded() - { - var result = QueryStringBuilder.EncodePathSegment("hello-world_test.value~123"); - Assert.That(result, Is.EqualTo("hello-world_test.value~123")); - } - - [Test] - public void EncodePathSegment_SubDelimiters_NotEncoded() - { - // All sub-delimiters are safe in path segments per RFC 3986 - var result = QueryStringBuilder.EncodePathSegment("a!b$c&d'e(f)g*h+i,j;k=l"); - Assert.That(result, Is.EqualTo("a!b$c&d'e(f)g*h+i,j;k=l")); - } - - [Test] - public void EncodePathSegment_ColonAndAt_NotEncoded() - { - var result = QueryStringBuilder.EncodePathSegment("user@host:8080"); - Assert.That(result, Is.EqualTo("user@host:8080")); - } - - [Test] - public void EncodePathSegment_SlashAndQuestion_Encoded() - { - // "/" and "?" are NOT part of pchar, so they must be encoded in path segments - var result = QueryStringBuilder.EncodePathSegment("path/with?query"); - Assert.That(result, Is.EqualTo("path%2Fwith%3Fquery")); - } - - [Test] - public void EncodePathSegment_Space_Encoded() - { - var result = QueryStringBuilder.EncodePathSegment("hello world"); - Assert.That(result, Is.EqualTo("hello%20world")); - } - - [Test] - public void EncodePathSegment_EmptyAndNull() - { - Assert.That(QueryStringBuilder.EncodePathSegment(""), Is.EqualTo("")); - Assert.That(QueryStringBuilder.EncodePathSegment(null!), Is.Null); - } - - [Test] - public void Build_QueryKeyVsValue_DifferentEncoding() - { - // "=" is safe in query values but NOT in query keys - var parameters = new List> - { - new("key=with=equals", "value=with=equals"), - }; - - var result = QueryStringBuilder.Build(parameters); - - // Key: "=" must be encoded - // Value: "=" is safe (part of query value safe chars) - Assert.That(result, Is.EqualTo("?key%3Dwith%3Dequals=value=with=equals")); - } - - [Test] - public void Build_QueryValue_QuestionMarkNotEncoded() - { - // "?" is safe in both query keys and query values per RFC 3986 - var parameters = new List> { new("q?key", "is this?") }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo("?q?key=is%20this?")); - } - - [Test] - public void Build_QueryKey_PlusEncoded() - { - // "+" must be encoded in both query keys and query values - var parameters = new List> { new("a+b", "c+d") }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo("?a%2Bb=c%2Bd")); - } - - [Test] - public void Build_ODataFilter_DollarPreserved() - { - // "$" is safe in query keys (sub-delimiter), verifies OData-style parameters work - var parameters = new List> - { - new("$filter", "status eq 'active'"), - new("$top", "10"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Does.Contain("$filter=status%20eq%20'active'")); - Assert.That(result, Does.Contain("$top=10")); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs deleted file mode 100644 index 06293e022863..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/QueryStringConverterTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class QueryStringConverterTests -{ - [Test] - public void ToQueryStringCollection_Form() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToForm(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates]", "39.78172,-89.65015"), - new("Tags", "Developer,Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_ExplodedForm() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToExplodedForm(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates]", "39.78172"), - new("Address[Coordinates]", "-89.65015"), - new("Tags", "Developer"), - new("Tags", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_DeepObject() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToDeepObject(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates][0]", "39.78172"), - new("Address[Coordinates][1]", "-89.65015"), - new("Tags[0]", "Developer"), - new("Tags[1]", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_OnString_ThrowsException() - { - var exception = Assert.Throws(() => - QueryStringConverter.ToForm("invalid") - ); - Assert.That( - exception.Message, - Is.EqualTo( - "Only objects can be converted to query string collections. Given type is String." - ) - ); - } - - [Test] - public void ToQueryStringCollection_OnArray_ThrowsException() - { - var exception = Assert.Throws(() => - QueryStringConverter.ToForm(Array.Empty()) - ); - Assert.That( - exception.Message, - Is.EqualTo( - "Only objects can be converted to query string collections. Given type is Array." - ) - ); - } - - [Test] - public void ToQueryStringCollection_DeepObject_WithPrefix() - { - var obj = new - { - custom_session_id = "my-id", - system_prompt = "You are helpful", - variables = new { name = "Alice", age = 25 }, - }; - var result = QueryStringConverter.ToDeepObject("session_settings", obj); - var expected = new List> - { - new("session_settings[custom_session_id]", "my-id"), - new("session_settings[system_prompt]", "You are helpful"), - new("session_settings[variables][name]", "Alice"), - new("session_settings[variables][age]", "25"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_ExplodedForm_WithPrefix() - { - var obj = new { Name = "John", Tags = new[] { "Developer", "Blogger" } }; - var result = QueryStringConverter.ToExplodedForm("user", obj); - var expected = new List> - { - new("user[Name]", "John"), - new("user[Tags]", "Developer"), - new("user[Tags]", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs deleted file mode 100644 index 8ad25e785bb1..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs +++ /dev/null @@ -1,1121 +0,0 @@ -using global::System.Net.Http; -using global::System.Text; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; -using SystemTask = global::System.Threading.Tasks.Task; - -namespace SeedApi.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class MultipartFormTests -{ - private static SimpleObject _simpleObject = new(); - - private static string _simpleFormEncoded = - "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data,2023-10-01,12:00:00,01:00:00,1a1bb98f-47c6-407b-9481-78476affe52a,true,42,A"; - - private static string _simpleExplodedFormEncoded = - "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data&Values=2023-10-01&Values=12:00:00&Values=01:00:00&Values=1a1bb98f-47c6-407b-9481-78476affe52a&Values=true&Values=42&Values=A"; - - private static ComplexObject _complexObject = new(); - - private static string _complexJson = """ - { - "meta": "data", - "Nested": { - "foo": "value" - }, - "NestedDictionary": { - "key": { - "foo": "value" - } - }, - "ListOfObjects": [ - { - "foo": "value" - }, - { - "foo": "value2" - } - ], - "Date": "2023-10-01", - "Time": "12:00:00", - "Duration": "01:00:00", - "Id": "1a1bb98f-47c6-407b-9481-78476affe52a", - "IsActive": true, - "Count": 42, - "Initial": "A" - } - """; - - [Test] - public async SystemTask ShouldAddStringPart() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, partInput]); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddStringPart() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", null); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithNullsInList() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, null, partInput]); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringPart_WithContentType() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput, "text/xml"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringPart_WithContentTypeAndCharset() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput, "text/xml; charset=utf-8"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithContentType() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, partInput], "text/xml"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithContentTypeAndCharset() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts( - "strings", - [partInput, partInput], - "text/xml; charset=utf-8" - ); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFileName() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithoutFileName() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", partInput); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithContentType() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter - { - Stream = partInput, - FileName = "test.txt", - ContentType = "text/plain", - }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithContentTypeAndCharset() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter - { - Stream = partInput, - FileName = "test.txt", - ContentType = "text/plain; charset=utf-8", - }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain; charset=utf-8 - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFallbackContentType() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "text/plain"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFallbackContentTypeAndCharset() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "text/plain; charset=utf-8"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain; charset=utf-8 - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameters() - { - var (partInput1, partExpectedString1) = GetFileParameterTestData(); - var (partInput2, partExpectedString2) = GetFileParameterTestData(); - var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; - var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterParts("file", [file1, file2]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt - - {partExpectedString1} - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt - - {partExpectedString2} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameters_WithNullsInList() - { - var (partInput1, partExpectedString1) = GetFileParameterTestData(); - var (partInput2, partExpectedString2) = GetFileParameterTestData(); - var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; - var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterParts("file", [file1, null, file2]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt - - {partExpectedString1} - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt - - {partExpectedString2} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddFileParameter() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonPart_WithComplexObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonPart("object", _complexObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=object - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonPart_WithComplexObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [_complexObject, _complexObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddJsonPart() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonPart("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [_complexObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [new { }], "application/json-patch+json"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $$""" - --{{boundary}} - Content-Type: application/json-patch+json - Content-Disposition: form-data; name=objects - - {} - --{{boundary}}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithSimpleObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart("object", _simpleObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=object - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithSimpleObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, _simpleObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddFormEncodedParts_WithNull() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddFormEncodedParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedPart_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedPart_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart("object", _simpleObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=object - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, _simpleObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNull() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - private static string EscapeFormEncodedString(string input) - { - return string.Join( - "&", - input - .Split('&') - .Select(x => x.Split('=')) - .Select(x => $"{Uri.EscapeDataString(x[0])}={Uri.EscapeDataString(x[1])}") - ); - } - - private static string GetBoundary(MultipartFormDataContent content) - { - return content - .Headers.ContentType?.Parameters.Single(p => - p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) - ) - .Value?.Trim('"') - ?? throw new global::System.Exception("Boundary not found"); - } - - private static SeedApi.Core.MultipartFormRequest CreateMultipartFormRequest() - { - return new SeedApi.Core.MultipartFormRequest - { - BaseUrl = "https://localhost", - Method = HttpMethod.Post, - Path = "", - }; - } - - private static (Stream partInput, string partExpectedString) GetFileParameterTestData() - { - const string partExpectedString = "file content"; - var partInput = new MemoryStream(Encoding.Default.GetBytes(partExpectedString)); - return (partInput, partExpectedString); - } - - private class SimpleObject - { - [JsonPropertyName("meta")] - public string Meta { get; set; } = "data"; - public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); - public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); - public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); - public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); - public bool IsActive { get; set; } = true; - public int Count { get; set; } = 42; - public char Initial { get; set; } = 'A'; - public IEnumerable Values { get; set; } = - [ - "data", - DateOnly.Parse("2023-10-01"), - TimeOnly.Parse("12:00:00"), - TimeSpan.FromHours(1), - Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), - true, - 42, - 'A', - ]; - } - - private class ComplexObject - { - [JsonPropertyName("meta")] - public string Meta { get; set; } = "data"; - - public object Nested { get; set; } = new { foo = "value" }; - - public Dictionary NestedDictionary { get; set; } = - new() { { "key", new { foo = "value" } } }; - - public IEnumerable ListOfObjects { get; set; } = - new List { new { foo = "value" }, new { foo = "value2" } }; - - public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); - public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); - public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); - public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); - public bool IsActive { get; set; } = true; - public int Count { get; set; } = 42; - public char Initial { get; set; } = 'A'; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs deleted file mode 100644 index f4edf9ef52a6..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class QueryParameterTests -{ - [Test] - public void QueryParameters_BasicParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .Add("baz", "qux") - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar&baz=qux")); - } - - [Test] - public void QueryParameters_SpecialCharacterEscaping() - { - var queryString = new QueryStringBuilder.Builder() - .Add("email", "bob+test@example.com") - .Add("%Complete", "100") - .Add("space test", "hello world") - .Build(); - - Assert.That(queryString, Does.Contain("email=bob%2Btest@example.com")); - Assert.That(queryString, Does.Contain("%25Complete=100")); - Assert.That(queryString, Does.Contain("space%20test=hello%20world")); - } - - [Test] - public void QueryParameters_MergeAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("sdk", "param") - .MergeAdditional(new List> { new("user", "value") }) - .Build(); - - Assert.That(queryString, Does.Contain("sdk=param")); - Assert.That(queryString, Does.Contain("user=value")); - } - - [Test] - public void QueryParameters_AdditionalOverridesSdk() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "sdk_value") - .MergeAdditional(new List> { new("foo", "user_override") }) - .Build(); - - Assert.That(queryString, Does.Contain("foo=user_override")); - Assert.That(queryString, Does.Not.Contain("sdk_value")); - } - - [Test] - public void QueryParameters_AdditionalMultipleValues() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "sdk_value") - .MergeAdditional( - new List> { new("foo", "user1"), new("foo", "user2") } - ) - .Build(); - - Assert.That(queryString, Does.Contain("foo=user1")); - Assert.That(queryString, Does.Contain("foo=user2")); - Assert.That(queryString, Does.Not.Contain("sdk_value")); - } - - [Test] - public void QueryParameters_OnlyAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .MergeAdditional( - new List> { new("foo", "bar"), new("baz", "qux") } - ) - .Build(); - - Assert.That(queryString, Does.Contain("foo=bar")); - Assert.That(queryString, Does.Contain("baz=qux")); - } - - [Test] - public void QueryParameters_EmptyAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .MergeAdditional(new List>()) - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar")); - } - - [Test] - public void QueryParameters_NullAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .MergeAdditional(null) - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar")); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs deleted file mode 100644 index 22e8103847cb..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs +++ /dev/null @@ -1,406 +0,0 @@ -using global::System.Net.Http; -using global::System.Text.Json; -using NUnit.Framework; -using SeedApi.Core; -using WireMock.Server; -using SystemTask = global::System.Threading.Tasks.Task; -using WireMockRequest = WireMock.RequestBuilders.Request; -using WireMockResponse = WireMock.ResponseBuilders.Response; - -namespace SeedApi.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class RetriesTests -{ - private const int MaxRetries = 3; - private WireMockServer _server; - private HttpClient _httpClient; - private RawClient _rawClient; - private string _baseUrl; - - [SetUp] - public void SetUp() - { - _server = WireMockServer.Start(); - _baseUrl = _server.Url ?? ""; - _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) }; - _rawClient = new RawClient( - new ClientOptions { HttpClient = _httpClient, MaxRetries = MaxRetries } - ) - { - BaseRetryDelay = 0, - }; - } - - [Test] - [TestCase(408)] - [TestCase(429)] - [TestCase(500)] - [TestCase(504)] - public async SystemTask SendRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode) - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WhenStateIs("Server Error") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - - Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); - } - } - - [Test] - [TestCase(400)] - [TestCase(409)] - public async SystemTask SendRequestAsync_ShouldRetry_OnNonRetryableStatusCodes(int statusCode) - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure")); - - var request = new JsonRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - Body = new { }, - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(statusCode)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldNotRetry_WithStreamRequest() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - - var request = new StreamRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - Body = new MemoryStream(), - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(429)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest_WithStream() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - - var request = new SeedApi.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddFileParameterPart("file", new MemoryStream()); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(429)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_WithoutStream() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WhenStateIs("Server Error") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(429)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new SeedApi.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddJsonPart("object", new { }); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithSecondsValue() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfter") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse.Create().WithStatusCode(429).WithHeader("Retry-After", "1") - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfter") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithHttpDateValue() - { - var retryAfterDate = DateTimeOffset.UtcNow.AddSeconds(1).ToString("R"); - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfterDate") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse - .Create() - .WithStatusCode(429) - .WithHeader("Retry-After", retryAfterDate) - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfterDate") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectXRateLimitResetHeader() - { - var resetTime = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds().ToString(); - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RateLimitReset") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse - .Create() - .WithStatusCode(429) - .WithHeader("X-RateLimit-Reset", resetTime) - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RateLimitReset") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldPreserveJsonBody_OnRetry() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryWithBody") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(500)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryWithBody") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new JsonRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - Body = new { key = "value" }, - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - - // Verify the retried request preserved the JSON body (compare parsed to ignore formatting differences) - var retriedEntry = _server.LogEntries.ElementAt(1); - using var actualJson = JsonDocument.Parse(retriedEntry.RequestMessage.Body!); - Assert.That(actualJson.RootElement.GetProperty("key").GetString(), Is.EqualTo("value")); - } - } - - [Test] - public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryMultipart") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(500)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryMultipart") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new SeedApi.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddJsonPart("object", new { key = "value" }); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - - // Verify the retried request preserved the multipart body (check key/value presence to ignore formatting differences) - var retriedEntry = _server.LogEntries.ElementAt(1); - Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"key\"")); - Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"value\"")); - } - } - - [TearDown] - public void TearDown() - { - _server.Dispose(); - _httpClient.Dispose(); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs deleted file mode 100644 index 27337ed343e8..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Core/WithRawResponseTests.cs +++ /dev/null @@ -1,269 +0,0 @@ -using global::System.Net; -using global::System.Net.Http.Headers; -using NUnit.Framework; -using SeedApi; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class WithRawResponseTests -{ - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_DirectAwait_ReturnsData() - { - // Arrange - var expectedData = "test-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - var result = await task; - - // Assert - Assert.That(result, Is.EqualTo(expectedData)); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_WithRawResponse_ReturnsDataAndMetadata() - { - // Arrange - var expectedData = "test-data"; - var expectedStatusCode = HttpStatusCode.Created; - var task = CreateWithRawResponseTask(expectedData, expectedStatusCode); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.Data, Is.EqualTo(expectedData)); - Assert.That(result.RawResponse.StatusCode, Is.EqualTo(expectedStatusCode)); - Assert.That(result.RawResponse.Url, Is.Not.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_CaseInsensitive() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Request-Id", "12345"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act & Assert - Assert.That(headers.TryGetValue("X-Request-Id", out var value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - - Assert.That(headers.TryGetValue("x-request-id", out value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - - Assert.That(headers.TryGetValue("X-REQUEST-ID", out value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_ReturnsMultipleValues() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("Set-Cookie", new[] { "cookie1=value1", "cookie2=value2" }); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValues("Set-Cookie", out var values); - - // Assert - Assert.That(success, Is.True); - Assert.That(values, Is.Not.Null); - Assert.That(values!.Count(), Is.EqualTo(2)); - Assert.That(values, Does.Contain("cookie1=value1")); - Assert.That(values, Does.Contain("cookie2=value2")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_ContentType_ReturnsValue() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Content = new StringContent( - "{}", - global::System.Text.Encoding.UTF8, - "application/json" - ); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var contentType = headers.ContentType; - - // Assert - Assert.That(contentType, Is.Not.Null); - Assert.That(contentType, Does.Contain("application/json")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_ContentLength_ReturnsValue() - { - // Arrange - var content = "test content"; - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Content = new StringContent(content); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var contentLength = headers.ContentLength; - - // Assert - Assert.That(contentLength, Is.Not.Null); - Assert.That(contentLength, Is.GreaterThan(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_Contains_ReturnsTrueForExistingHeader() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Custom-Header", "value"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act & Assert - Assert.That(headers.Contains("X-Custom-Header"), Is.True); - Assert.That(headers.Contains("x-custom-header"), Is.True); - Assert.That(headers.Contains("NonExistent"), Is.False); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_Enumeration_IncludesAllHeaders() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Header-1", "value1"); - response.Headers.Add("X-Header-2", "value2"); - response.Content = new StringContent("test"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var allHeaders = headers.ToList(); - - // Assert - Assert.That(allHeaders.Count, Is.GreaterThan(0)); - Assert.That(allHeaders.Any(h => h.Name == "X-Header-1"), Is.True); - Assert.That(allHeaders.Any(h => h.Name == "X-Header-2"), Is.True); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_ErrorStatusCode_StillReturnsMetadata() - { - // Arrange - var expectedData = "error-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.BadRequest); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.Data, Is.EqualTo(expectedData)); - Assert.That(result.RawResponse.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_Url_IsPreserved() - { - // Arrange - var expectedUrl = new Uri("https://api.example.com/users/123"); - var task = CreateWithRawResponseTask("data", HttpStatusCode.OK, expectedUrl); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.RawResponse.Url, Is.EqualTo(expectedUrl)); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_NonExistentHeader_ReturnsFalse() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValue("X-NonExistent", out var value); - - // Assert - Assert.That(success, Is.False); - Assert.That(value, Is.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_NonExistentHeader_ReturnsFalse() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValues("X-NonExistent", out var values); - - // Assert - Assert.That(success, Is.False); - Assert.That(values, Is.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_ImplicitConversion_ToTask() - { - // Arrange - var expectedData = "test-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - implicitly convert to Task - global::System.Threading.Tasks.Task regularTask = task; - var result = await regularTask; - - // Assert - Assert.That(result, Is.EqualTo(expectedData)); - } - - [Test] - public void WithRawResponseTask_ImplicitConversion_AssignToTaskVariable() - { - // Arrange - var expectedData = "test-data"; - var wrappedTask = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - assign to Task variable - global::System.Threading.Tasks.Task regularTask = wrappedTask; - - // Assert - Assert.That(regularTask, Is.Not.Null); - Assert.That(regularTask, Is.InstanceOf>()); - } - - // Helper methods - - private static WithRawResponseTask CreateWithRawResponseTask( - T data, - HttpStatusCode statusCode, - Uri? url = null - ) - { - url ??= new Uri("https://api.example.com/test"); - using var httpResponse = CreateHttpResponse(statusCode); - httpResponse.RequestMessage = new HttpRequestMessage(HttpMethod.Get, url); - - var rawResponse = new RawResponse - { - StatusCode = statusCode, - Url = url, - Headers = ResponseHeaders.FromHttpResponseMessage(httpResponse), - }; - - var withRawResponse = new WithRawResponse { Data = data, RawResponse = rawResponse }; - - var task = global::System.Threading.Tasks.Task.FromResult(withRawResponse); - return new WithRawResponseTask(task); - } - - private static HttpResponseMessage CreateHttpResponse(HttpStatusCode statusCode) - { - return new HttpResponseMessage(statusCode) { Content = new StringContent("") }; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props deleted file mode 100644 index aac9b5020d80..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.Custom.props +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj deleted file mode 100644 index 77e1a9943739..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/SeedApi.Test.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - net9.0 - 12 - enable - enable - false - true - true - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs deleted file mode 100644 index 18aaa67904d8..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/TestClient.cs +++ /dev/null @@ -1,6 +0,0 @@ -using NUnit.Framework; - -namespace SeedApi.Test; - -[TestFixture] -public class TestClient; diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs deleted file mode 100644 index 3f4ed8503ec1..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using NUnit.Framework; -using SeedApi; -using WireMock.Logging; -using WireMock.Server; -using WireMock.Settings; - -namespace SeedApi.Test.Unit.MockServer; - -public class BaseMockServerTest -{ - protected WireMockServer Server { get; set; } = null!; - - protected SeedApiClient Client { get; set; } = null!; - - protected RequestOptions RequestOptions { get; set; } = new(); - - [OneTimeSetUp] - public void GlobalSetup() - { - // Start the WireMock server - Server = WireMockServer.Start( - new WireMockServerSettings { Logger = new WireMockConsoleLogger() } - ); - - // Initialize the Client - Client = new SeedApiClient( - clientOptions: new ClientOptions { BaseUrl = Server.Urls[0], MaxRetries = 0 } - ); - } - - [OneTimeTearDown] - public void GlobalTeardown() - { - Server.Stop(); - Server.Dispose(); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs deleted file mode 100644 index 053c0fd711a1..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs +++ /dev/null @@ -1,92 +0,0 @@ -using NUnit.Framework; -using SeedApi; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class CreateRuleTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string requestJson = """ - { - "name": "name", - "executionContext": "prod" - } - """; - - const string mockResponse = """ - { - "id": "id", - "name": "name", - "status": "active", - "executionContext": "prod" - } - """; - - Server - .Given( - WireMock - .RequestBuilders.Request.Create() - .WithPath("/rules") - .WithHeader("Content-Type", "application/json") - .UsingPost() - .WithBodyAsJson(requestJson) - ) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } - ); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string requestJson = """ - { - "name": "name", - "executionContext": "prod" - } - """; - - const string mockResponse = """ - { - "id": "id", - "name": "name", - "status": "active", - "executionContext": "prod" - } - """; - - Server - .Given( - WireMock - .RequestBuilders.Request.Create() - .WithPath("/rules") - .WithHeader("Content-Type", "application/json") - .UsingPost() - .WithBodyAsJson(requestJson) - ) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } - ); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs deleted file mode 100644 index cec77fca473f..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs +++ /dev/null @@ -1,59 +0,0 @@ -using NUnit.Framework; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class GetEntityTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "id": "id", - "name": "name", - "summary": "summary", - "status": "active" - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetEntityAsync(); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "id": "id", - "name": "name", - "summary": "summary", - "status": "active" - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetEntityAsync(); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs deleted file mode 100644 index 384bd8be8464..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -using NUnit.Framework; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class GetOrganizationTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "id": "id", - "metadata": { - "region": "region", - "domain": "domain" - }, - "name": "name" - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetOrganizationAsync(); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "id": "id", - "metadata": { - "region": "region", - "domain": "domain" - }, - "name": "name" - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetOrganizationAsync(); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs deleted file mode 100644 index 631e8034f03a..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs +++ /dev/null @@ -1,75 +0,0 @@ -using NUnit.Framework; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class ListUsersTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "paging": { - "next": "next", - "previous": "previous" - }, - "results": [ - { - "id": "id", - "email": "email" - }, - { - "id": "id", - "email": "email" - } - ] - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.ListUsersAsync(); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "paging": { - "next": "next", - "previous": "previous" - }, - "results": [ - { - "id": "id", - "email": "email" - } - ] - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.ListUsersAsync(); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs deleted file mode 100644 index afc1fceaf710..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework; -using SeedApi; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class SearchRuleTypesTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "paging": { - "next": "next", - "previous": "previous" - }, - "results": [ - { - "id": "id", - "name": "name", - "description": "description" - }, - { - "id": "id", - "name": "name", - "description": "description" - } - ] - } - """; - - Server - .Given( - WireMock - .RequestBuilders.Request.Create() - .WithPath("/rule-types") - .WithParam("query", "query") - .UsingGet() - ) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.SearchRuleTypesAsync( - new SearchRuleTypesRequest { Query = "query" } - ); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "paging": { - "next": "next", - "previous": "previous" - }, - "results": [ - { - "id": "id", - "name": "name", - "description": "description" - } - ] - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/rule-types").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs deleted file mode 100644 index 3ac7e5310f95..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs +++ /dev/null @@ -1,219 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; -using SeedApi; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle AdditionalProperties values. -/// -public static class AdditionalPropertiesComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their - /// serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) - { - constraint.Using( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - /// - /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing - /// their serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) - { - constraint.Using>( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => - JsonElementsAreEqual(x, y); - - private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) - { - if (x.ValueKind != y.ValueKind) - { - return false; - } - - return x.ValueKind switch - { - JsonValueKind.Object => CompareJsonObjects(x, y), - JsonValueKind.Array => CompareJsonArrays(x, y), - JsonValueKind.String => x.GetString() == y.GetString(), - JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), - JsonValueKind.True => true, - JsonValueKind.False => true, - JsonValueKind.Null => true, - _ => false, - }; - } - - private static bool CompareJsonObjects(JsonElement x, JsonElement y) - { - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - if (xProps.Count != yProps.Count) - { - return false; - } - - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - return false; - } - - if (!JsonElementsAreEqual(xProps[key], yProps[key])) - { - return false; - } - } - - return true; - } - - private static bool CompareJsonArrays(JsonElement x, JsonElement y) - { - var xArray = x.EnumerateArray().ToList(); - var yArray = y.EnumerateArray().ToList(); - - if (xArray.Count != yArray.Count) - { - return false; - } - - for (var i = 0; i < xArray.Count; i++) - { - if (!JsonElementsAreEqual(xArray[i], yArray[i])) - { - return false; - } - } - - return true; - } - - /// - /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. - /// When UsingPropertiesComparer() walks object properties and encounters a property typed as - /// 'object', the expected side may be a Dictionary<object, object?> while the actual - /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing - /// the non-JsonElement side and comparing JSON representations. - /// - /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic - /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only - /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all - /// same-type comparisons normally. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) - { - // Handle: expected is non-JsonElement, actual is JsonElement - constraint.Using( - (actualJsonElement, expectedObj) => - { - try - { - var expectedElement = JsonUtils.SerializeToElement(expectedObj); - return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); - } - catch - { - return false; - } - } - ); - // Handle reverse: expected is JsonElement, actual is non-JsonElement - constraint.Using( - (actualObj, expectedJsonElement) => - { - try - { - var actualElement = JsonUtils.SerializeToElement(actualObj); - return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); - } - catch - { - return false; - } - } - ); - return constraint; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs deleted file mode 100644 index 3f4b5eb602b2..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonAssert.cs +++ /dev/null @@ -1,29 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Utils; - -internal static class JsonAssert -{ - /// - /// Asserts that the serialized JSON of an object equals the expected JSON string. - /// Uses JsonElement comparison for reliable deep equality of collections and union types. - /// - internal static void AreEqual(object actual, string expectedJson) - { - var actualElement = JsonUtils.SerializeToElement(actual); - var expectedElement = JsonUtils.Deserialize(expectedJson); - Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); - } - - /// - /// Asserts that the given JSON string survives a deserialization/serialization round-trip - /// intact: deserializes to T then re-serializes and compares to the original JSON. - /// - internal static void Roundtrips(string json) - { - var deserialized = JsonUtils.Deserialize(json); - AreEqual(deserialized!, json); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs deleted file mode 100644 index a37ef402c1ac..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/JsonElementComparer.cs +++ /dev/null @@ -1,236 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle JsonElement objects. -/// -public static class JsonElementComparerExtensions -{ - /// - /// Extension method for comparing JsonElement objects in NUnit tests. - /// Property order doesn't matter, but array order does matter. - /// Includes special handling for DateTime string formats. - /// - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare JsonElements with detailed diffs. - public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) - { - return constraint.Using(new JsonElementComparer()); - } -} - -/// -/// Equality comparer for JsonElement with detailed reporting. -/// Property order doesn't matter, but array order does matter. -/// Now includes special handling for DateTime string formats with improved null handling. -/// -public class JsonElementComparer : IEqualityComparer -{ - private string _failurePath = string.Empty; - - /// - public bool Equals(JsonElement x, JsonElement y) - { - _failurePath = string.Empty; - return CompareJsonElements(x, y, string.Empty); - } - - /// - public int GetHashCode(JsonElement obj) - { - return JsonSerializer.Serialize(obj).GetHashCode(); - } - - private bool CompareJsonElements(JsonElement x, JsonElement y, string path) - { - // If value kinds don't match, they're not equivalent - if (x.ValueKind != y.ValueKind) - { - _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; - return false; - } - - switch (x.ValueKind) - { - case JsonValueKind.Object: - return CompareJsonObjects(x, y, path); - - case JsonValueKind.Array: - return CompareJsonArraysInOrder(x, y, path); - - case JsonValueKind.String: - string? xStr = x.GetString(); - string? yStr = y.GetString(); - - // Handle null strings - if (xStr is null && yStr is null) - return true; - - if (xStr is null || yStr is null) - { - _failurePath = - $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; - return false; - } - - // Check if they are identical strings - if (xStr == yStr) - return true; - - // Try to handle DateTime strings - if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) - { - if (AreEquivalentDateTimeStrings(xStr, yStr)) - return true; - } - - _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; - return false; - - case JsonValueKind.Number: - if (x.GetDecimal() != y.GetDecimal()) - { - _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; - return false; - } - - return true; - - case JsonValueKind.True: - case JsonValueKind.False: - if (x.GetBoolean() != y.GetBoolean()) - { - _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; - return false; - } - - return true; - - case JsonValueKind.Null: - return true; - - default: - _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; - return false; - } - } - - private bool IsLikelyDateTimeString(string? str) - { - // Simple heuristic to identify likely ISO date time strings - return str is not null - && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); - } - - private bool AreEquivalentDateTimeStrings(string str1, string str2) - { - // Try to parse both as DateTime - if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) - { - return dt1 == dt2; - } - - return false; - } - - private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) - { - // Create dictionaries for both JSON objects - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - // Check if all properties in x exist in y - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - _failurePath = $"{path}: Missing property '{key}'"; - return false; - } - } - - // Check if y has extra properties - foreach (var key in yProps.Keys) - { - if (!xProps.ContainsKey(key)) - { - _failurePath = $"{path}: Unexpected property '{key}'"; - return false; - } - } - - // Compare each property value - foreach (var key in xProps.Keys) - { - var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; - if (!CompareJsonElements(xProps[key], yProps[key], propPath)) - { - return false; - } - } - - return true; - } - - private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) - { - var xArray = x.EnumerateArray(); - var yArray = y.EnumerateArray(); - - // Count x elements - var xCount = 0; - var xElements = new List(); - foreach (var item in xArray) - { - xElements.Add(item); - xCount++; - } - - // Count y elements - var yCount = 0; - var yElements = new List(); - foreach (var item in yArray) - { - yElements.Add(item); - yCount++; - } - - // Check if counts match - if (xCount != yCount) - { - _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; - return false; - } - - // Compare elements in order - for (var i = 0; i < xCount; i++) - { - var itemPath = $"{path}[{i}]"; - if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) - { - return false; - } - } - - return true; - } - - /// - public override string ToString() - { - if (!string.IsNullOrEmpty(_failurePath)) - { - return $"JSON comparison failed at {_failurePath}"; - } - - return "JsonElementEqualityComparer"; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs deleted file mode 100644 index 816f4c010e6e..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/NUnitExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class NUnitExtensions -{ - /// - /// Modifies the EqualConstraint to use our own set of default comparers. - /// - /// - /// - public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => - constraint - .UsingPropertiesComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingOneOfComparer() - .UsingJsonElementComparer() - .UsingOptionalComparer() - .UsingObjectDictionaryComparer() - .UsingAdditionalPropertiesComparer() - .UsingJsonSerializationComparer(); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs deleted file mode 100644 index 767439174363..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OneOfComparer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle OneOf values. -/// -public static class EqualConstraintExtensions -{ - /// - /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOneOf types - constraint.Using( - (x, y) => - { - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (x.Value is null && y.Value is null) - { - return true; - } - - if (x.Value is null) - { - return false; - } - - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs deleted file mode 100644 index 98bfcac477b8..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/OptionalComparer.cs +++ /dev/null @@ -1,104 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle Optional values. -/// -public static class OptionalComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOptional types - constraint.Using( - (x, y) => - { - // Both must have the same IsDefined state - if (x.IsDefined != y.IsDefined) - { - return false; - } - - // If both are undefined, they're equal - if (!x.IsDefined) - { - return true; - } - - // Both are defined, compare their boxed values - var xValue = x.GetBoxedValue(); - var yValue = y.GetBoxedValue(); - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (xValue is null && yValue is null) - { - return true; - } - - if (xValue is null || yValue is null) - { - return false; - } - - // Use NUnit's property comparer for the inner values - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values within Optional types. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs deleted file mode 100644 index fc0b595a5e54..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class ReadOnlyMemoryComparerExtensions -{ - /// - /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. - /// - /// The type of elements in the ReadOnlyMemory. - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare ReadOnlyMemory<T>. - public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) - where T : IComparable - { - return constraint.Using(new ReadOnlyMemoryComparer()); - } -} - -/// -/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. -/// -/// -/// The type of elements in the ReadOnlyMemory. -/// -public class ReadOnlyMemoryComparer : IComparer> - where T : IComparable -{ - /// - public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) - { - // Check if sequences are equal - var xSpan = x.Span; - var ySpan = y.Span; - - // Optimized case for IEquatable implementations - if (typeof(IEquatable).IsAssignableFrom(typeof(T))) - { - var areEqual = xSpan.SequenceEqual(ySpan); - if (areEqual) - { - return 0; // Sequences are equal - } - } - else - { - // Manual equality check for non-IEquatable types - if (xSpan.Length == ySpan.Length) - { - var areEqual = true; - for (var i = 0; i < xSpan.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - areEqual = false; - break; - } - } - - if (areEqual) - { - return 0; // Sequences are equal - } - } - } - - // For non-equal sequences, we need to return a consistent ordering - // First compare lengths - if (x.Length != y.Length) - return x.Length.CompareTo(y.Length); - - // Same length but different content - compare first differing element - for (var i = 0; i < x.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - return xSpan[i].CompareTo(ySpan[i]); - } - } - - // Should never reach here if not equal - return 0; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs deleted file mode 100644 index 838d0c00b960..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ApiResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// The response object returned from the API. -/// -internal record ApiResponse -{ - internal required int StatusCode { get; init; } - - internal required HttpResponseMessage Raw { get; init; } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs deleted file mode 100644 index 9a3cabb806a3..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/BaseRequest.cs +++ /dev/null @@ -1,67 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; -using global::System.Text; - -namespace SeedApi.Core; - -internal abstract record BaseRequest -{ - internal string? BaseUrl { get; init; } - - internal required HttpMethod Method { get; init; } - - internal required string Path { get; init; } - - internal string? ContentType { get; init; } - - /// - /// The query string for this request (including the leading '?' if non-empty). - /// - internal string? QueryString { get; init; } - - internal Dictionary Headers { get; init; } = - new(StringComparer.OrdinalIgnoreCase); - - internal IRequestOptions? Options { get; init; } - - internal abstract HttpContent? CreateContent(); - - protected static ( - Encoding encoding, - string? charset, - string mediaType - ) ParseContentTypeOrDefault( - string? contentType, - Encoding encodingFallback, - string mediaTypeFallback - ) - { - var encoding = encodingFallback; - var mediaType = mediaTypeFallback; - string? charset = null; - if (string.IsNullOrEmpty(contentType)) - { - return (encoding, charset, mediaType); - } - - if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) - { - return (encoding, charset, mediaType); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) - { - charset = mediaTypeHeaderValue.CharSet; - encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) - { - mediaType = mediaTypeHeaderValue.MediaType; - } - - return (encoding, charset, mediaType); - } - - protected static Encoding Utf8NoBom => EncodingCache.Utf8NoBom; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs deleted file mode 100644 index b684f33d750e..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/CollectionItemSerializer.cs +++ /dev/null @@ -1,91 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Json collection converter. -/// -/// Type of item to convert. -/// Converter to use for individual items. -internal class CollectionItemSerializer - : JsonConverter> - where TConverterType : JsonConverter, new() -{ - private static readonly TConverterType _converter = new TConverterType(); - - /// - /// Reads a json string and deserializes it into an object. - /// - /// Json reader. - /// Type to convert. - /// Serializer options. - /// Created object. - public override IEnumerable? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - var returnValue = new List(); - - while (reader.TokenType != JsonTokenType.EndArray) - { - if (reader.TokenType != JsonTokenType.StartArray) - { - var item = (TDatatype)( - JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) - ?? throw new global::System.Exception( - $"Failed to deserialize collection item of type {typeof(TDatatype)}" - ) - ); - returnValue.Add(item); - } - - reader.Read(); - } - - return returnValue; - } - - /// - /// Writes a json string. - /// - /// Json writer. - /// Value to write. - /// Serializer options. - public override void Write( - Utf8JsonWriter writer, - IEnumerable? value, - JsonSerializerOptions options - ) - { - if (value is null) - { - writer.WriteNullValue(); - return; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - writer.WriteStartArray(); - - foreach (var data in value) - { - JsonSerializer.Serialize(writer, data, jsonSerializerOptions); - } - - writer.WriteEndArray(); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs deleted file mode 100644 index ccf4e963cc89..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi.Core; - -internal static class Constants -{ - public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; - public const string DateFormat = "yyyy-MM-dd"; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs deleted file mode 100644 index af61cc061ae5..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateOnlyConverter.cs +++ /dev/null @@ -1,747 +0,0 @@ -// ReSharper disable All -#pragma warning disable - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using global::System.Diagnostics; -using global::System.Diagnostics.CodeAnalysis; -using global::System.Globalization; -using global::System.Runtime.CompilerServices; -using global::System.Runtime.InteropServices; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -// ReSharper disable SuggestVarOrType_SimpleTypes -// ReSharper disable SuggestVarOrType_BuiltInTypes - -namespace SeedApi.Core -{ - /// - /// Custom converter for handling the data type with the System.Text.Json library. - /// - /// - /// This class backported from: - /// - /// System.Text.Json.Serialization.Converters.DateOnlyConverter - /// - public sealed class DateOnlyConverter : JsonConverter - { - private const int FormatLength = 10; // YYYY-MM-DD - - private const int MaxEscapedFormatLength = - FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; - - /// - public override DateOnly Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType != JsonTokenType.String) - { - ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); - } - - return ReadCore(ref reader); - } - - /// - public override DateOnly ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - return ReadCore(ref reader); - } - - private static DateOnly ReadCore(ref Utf8JsonReader reader) - { - if ( - !JsonHelpers.IsInRangeInclusive( - reader.ValueLength(), - FormatLength, - MaxEscapedFormatLength - ) - ) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - scoped ReadOnlySpan source; - if (!reader.HasValueSequence && !reader.ValueIsEscaped) - { - source = reader.ValueSpan; - } - else - { - Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; - int bytesWritten = reader.CopyString(stackSpan); - source = stackSpan.Slice(0, bytesWritten); - } - - if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - return value; - } - - /// - public override void Write( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WriteStringValue(buffer); - } - - /// - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WritePropertyName(buffer); - } - } - - internal static class JsonConstants - { - // The maximum number of fraction digits the Json DateTime parser allows - public const int DateTimeParseNumFractionDigits = 16; - - // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. - public const int MaxExpansionFactorWhileEscaping = 6; - - // The largest fraction expressible by TimeSpan and DateTime formats - public const int MaxDateTimeFraction = 9_999_999; - - // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. - public const int DateTimeNumFractionDigits = 7; - - public const byte UtcOffsetToken = (byte)'Z'; - - public const byte TimePrefix = (byte)'T'; - - public const byte Period = (byte)'.'; - - public const byte Hyphen = (byte)'-'; - - public const byte Colon = (byte)':'; - - public const byte Plus = (byte)'+'; - } - - // ReSharper disable SuggestVarOrType_Elsewhere - // ReSharper disable SuggestVarOrType_SimpleTypes - // ReSharper disable SuggestVarOrType_BuiltInTypes - - internal static class JsonHelpers - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => - (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); - - public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; - - [StructLayout(LayoutKind.Auto)] - private struct DateTimeParseData - { - public int Year; - public int Month; - public int Day; - public bool IsCalendarDateOnly; - public int Hour; - public int Minute; - public int Second; - public int Fraction; // This value should never be greater than 9_999_999. - public int OffsetHours; - public int OffsetMinutes; - - // ReSharper disable once NotAccessedField.Local - public byte OffsetToken; - } - - public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) - { - if ( - TryParseDateTimeOffset(source, out DateTimeParseData parseData) - && parseData.IsCalendarDateOnly - && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) - ) - { - value = DateOnly.FromDateTime(dateTime); - return true; - } - - value = default; - return false; - } - - /// - /// ISO 8601 date time parser (ISO 8601-1:2019). - /// - /// The date/time to parse in UTF-8 format. - /// The parsed for the given . - /// - /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day - /// representations with optional specification of seconds and fractional seconds. - /// - /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). - /// If unspecified they are considered to be local per spec. - /// - /// Examples: (TZD is either "Z" or hh:mm offset from UTC) - /// - /// YYYY-MM-DD (e.g. 1997-07-16) - /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) - /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) - /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) - /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) - /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) - /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) - /// - /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). - /// The extended variants have separator characters between components ('-', ':', '.', etc.). - /// Spaces are not permitted. - /// - /// "true" if successfully parsed. - private static bool TryParseDateTimeOffset( - ReadOnlySpan source, - out DateTimeParseData parseData - ) - { - parseData = default; - - // too short datetime - Debug.Assert(source.Length >= 10); - - // Parse the calendar date - // ----------------------- - // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" - // [dateX] = [year]["-"][month]["-"][day] - // [year] = [YYYY] [0000 - 9999] (4.3.2) - // [month] = [MM] [01 - 12] (4.3.3) - // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) - // - // Note: 5.2.2.2 "Representations with reduced precision" allows for - // just [year]["-"][month] (a) and just [year] (b), but we currently - // don't permit it. - - { - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - uint digit3 = source[2] - (uint)'0'; - uint digit4 = source[3] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) - { - return false; - } - - parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); - } - - if ( - source[4] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) - || source[7] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) - ) - { - return false; - } - - // We now have YYYY-MM-DD [dateX] - // ReSharper disable once ConvertIfStatementToSwitchStatement - if (source.Length == 10) - { - parseData.IsCalendarDateOnly = true; - return true; - } - - // Parse the time of day - // --------------------- - // - // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" - // [timeX] = ["T"][hour][":"][min][":"][sec] - // [hour] = [hh] [00 - 23] (4.3.8a) - // [minute] = [mm] [00 - 59] (4.3.9a) - // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) - // - // ISO 8601-1:2019 5.3.3 "UTC of day" - // [timeX]["Z"] - // - // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between - // local timescale and UTC" (Extended format) - // - // [shiftX] = ["+"|"-"][hour][":"][min] - // - // Notes: - // - // "T" is optional per spec, but _only_ when times are used alone. In our - // case, we're reading out a complete date & time and as such require "T". - // (5.4.2.1b). - // - // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations - // with reduced precision". 5.3.1.3b allows just specifying the hour, but - // we currently don't permit this. - // - // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). - // We only allow fractions for seconds currently. Lower order components - // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be - // one digit, but the max number of digits is implementation defined. We - // currently allow up to 16 digits of fractional seconds only. While we - // support 16 fractional digits we only parse the first seven, anything - // past that is considered a zero. This is to stay compatible with the - // DateTime implementation which is limited to this resolution. - - if (source.Length < 16) - { - // Source does not have enough characters for YYYY-MM-DDThh:mm - return false; - } - - // Parse THH:MM (e.g. "T10:32") - if ( - source[10] != JsonConstants.TimePrefix - || source[13] != JsonConstants.Colon - || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) - || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm - Debug.Assert(source.Length >= 16); - if (source.Length == 16) - { - return true; - } - - byte curByte = source[16]; - int sourceIndex = 17; - - // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Colon: - break; - default: - return false; - } - - // Try reading the seconds - if ( - source.Length < 19 - || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss - Debug.Assert(source.Length >= 19); - if (source.Length == 19) - { - return true; - } - - curByte = source[19]; - sourceIndex = 20; - - // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Period: - break; - default: - return false; - } - - // Source does not have enough characters for second fractions (i.e. ".s") - // YYYY-MM-DDThh:mm:ss.s - if (source.Length < 21) - { - return false; - } - - // Parse fraction. This value should never be greater than 9_999_999 - int numDigitsRead = 0; - int fractionEnd = Math.Min( - sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, - source.Length - ); - - while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) - { - if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); - numDigitsRead++; - } - - sourceIndex++; - } - - if (parseData.Fraction != 0) - { - while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction *= 10; - numDigitsRead++; - } - } - - // We now have YYYY-MM-DDThh:mm:ss.s - Debug.Assert(sourceIndex <= source.Length); - if (sourceIndex == source.Length) - { - return true; - } - - curByte = source[sourceIndex++]; - - // TZD ['Z'|'+'|'-'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - default: - return false; - } - - static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) - { - // Parse the hours for the offset - if ( - offsetData.Length < 2 - || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss.s+|-hh - - if (offsetData.Length == 2) - { - // Just hours offset specified - return true; - } - - // Ensure we have enough for ":mm" - return offsetData.Length == 5 - && offsetData[2] == JsonConstants.Colon - && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - // ReSharper disable once RedundantAssignment - private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) - { - Debug.Assert(source.Length == 2); - - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9) - { - value = 0; - return false; - } - - value = (int)(digit1 * 10 + digit2); - return true; - } - - // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs - - /// - /// Overflow-safe DateTime factory. - /// - private static bool TryCreateDateTime( - DateTimeParseData parseData, - DateTimeKind kind, - out DateTime value - ) - { - if (parseData.Year == 0) - { - value = default; - return false; - } - - Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. - - if ((uint)parseData.Month - 1 >= 12) - { - value = default; - return false; - } - - uint dayMinusOne = (uint)parseData.Day - 1; - if ( - dayMinusOne >= 28 - && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) - ) - { - value = default; - return false; - } - - if ((uint)parseData.Hour > 23) - { - value = default; - return false; - } - - if ((uint)parseData.Minute > 59) - { - value = default; - return false; - } - - // This needs to allow leap seconds when appropriate. - // See https://github.com/dotnet/runtime/issues/30135. - if ((uint)parseData.Second > 59) - { - value = default; - return false; - } - - Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. - - ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) - ? DaysToMonth366 - : DaysToMonth365; - int yearMinusOne = parseData.Year - 1; - int totalDays = - yearMinusOne * 365 - + yearMinusOne / 4 - - yearMinusOne / 100 - + yearMinusOne / 400 - + days[parseData.Month - 1] - + parseData.Day - - 1; - long ticks = totalDays * TimeSpan.TicksPerDay; - int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; - ticks += totalSeconds * TimeSpan.TicksPerSecond; - ticks += parseData.Fraction; - value = new DateTime(ticks: ticks, kind: kind); - return true; - } - - private static ReadOnlySpan DaysToMonth365 => - [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; - private static ReadOnlySpan DaysToMonth366 => - [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; - } - - internal static class ThrowHelper - { - private const string ExceptionSourceValueToRethrowAsJsonException = - "System.Text.Json.Rethrowable"; - - [DoesNotReturn] - public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) - { - throw GetInvalidOperationException("string", tokenType); - } - - public static void ThrowFormatException(DataType dataType) - { - throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - - private static global::System.Exception GetInvalidOperationException( - string message, - JsonTokenType tokenType - ) - { - return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); - } - - private static InvalidOperationException GetInvalidOperationException(string message) - { - return new InvalidOperationException(message) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - } - - internal static class Utf8JsonReaderExtensions - { - internal static int ValueLength(this Utf8JsonReader reader) => - reader.HasValueSequence - ? checked((int)reader.ValueSequence.Length) - : reader.ValueSpan.Length; - } - - internal enum DataType - { - TimeOnly, - DateOnly, - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - internal static class SR - { - private static readonly bool s_usingResourceKeys = - AppContext.TryGetSwitch( - "System.Resources.UseSystemResourceKeys", - out bool usingResourceKeys - ) && usingResourceKeys; - - public static string UnsupportedFormat => Strings.UnsupportedFormat; - - public static string InvalidCast => Strings.InvalidCast; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1) - : string.Format(resourceFormat, p1); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1, object? p2) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1, p2) - : string.Format(resourceFormat, p1, p2); - } - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute( - "System.Resources.Tools.StronglyTypedResourceBuilder", - "17.0.0.0" - )] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings - { - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( - "Microsoft.Performance", - "CA1811:AvoidUncalledPrivateCode" - )] - internal Strings() { } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = - new global::System.Resources.ResourceManager( - "System.Text.Json.Resources.Strings", - typeof(Strings).Assembly - ); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Globalization.CultureInfo Culture - { - get { return resourceCulture; } - set { resourceCulture = value; } - } - - /// - /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. - /// - internal static string InvalidCast - { - get { return ResourceManager.GetString("InvalidCast", resourceCulture); } - } - - /// - /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. - /// - internal static string UnsupportedFormat - { - get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } - } - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs deleted file mode 100644 index d7dedc7f165b..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/DateTimeSerializer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using global::System.Globalization; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -internal class DateTimeSerializer : JsonConverter -{ - public override DateTime Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); - } - - public override DateTime ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateTime value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs deleted file mode 100644 index d14fc3bfa37e..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EmptyRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// The request object to send without a request body. -/// -internal record EmptyRequest : BaseRequest -{ - internal override HttpContent? CreateContent() => null; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs deleted file mode 100644 index 2dae8b535a18..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/EncodingCache.cs +++ /dev/null @@ -1,11 +0,0 @@ -using global::System.Text; - -namespace SeedApi.Core; - -internal static class EncodingCache -{ - internal static readonly Encoding Utf8NoBom = new UTF8Encoding( - encoderShouldEmitUTF8Identifier: false, - throwOnInvalidBytes: true - ); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs deleted file mode 100644 index 7338b20e748c..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Extensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using global::System.Diagnostics.CodeAnalysis; -using global::System.Runtime.Serialization; - -namespace SeedApi.Core; - -internal static class Extensions -{ - public static string Stringify(this Enum value) - { - var field = value.GetType().GetField(value.ToString()); - if (field is not null) - { - var attribute = (EnumMemberAttribute?) - global::System.Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); - return attribute?.Value ?? value.ToString(); - } - return value.ToString(); - } - - /// - /// Asserts that a condition is true, throwing an exception with the specified message if it is false. - /// - /// The condition to assert. - /// The exception message if the assertion fails. - /// Thrown when the condition is false. - internal static void Assert(this object value, bool condition, string message) - { - if (!condition) - { - throw new global::System.Exception(message); - } - } - - /// - /// Asserts that a value is not null, throwing an exception with the specified message if it is null. - /// - /// The type of the value to assert. - /// The value to assert is not null. - /// The exception message if the assertion fails. - /// The non-null value. - /// Thrown when the value is null. - internal static TValue Assert( - this object _unused, - [NotNull] TValue? value, - string message - ) - where TValue : class - { - if (value is null) - { - throw new global::System.Exception(message); - } - return value; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs deleted file mode 100644 index 343c13716c24..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/FormUrlEncoder.cs +++ /dev/null @@ -1,33 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// Encodes an object into a form URL-encoded content. -/// -public static class FormUrlEncoder -{ - /// - /// Encodes an object into a form URL-encoded content using Deep Object notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsDeepObject(object value) => - new(QueryStringConverter.ToDeepObject(value)); - - /// - /// Encodes an object into a form URL-encoded content using Exploded Form notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsExplodedForm(object value) => - new(QueryStringConverter.ToExplodedForm(value)); - - /// - /// Encodes an object into a form URL-encoded content using Form notation without exploding parameters. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsForm(object value) => - new(QueryStringConverter.ToForm(value)); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs deleted file mode 100644 index e908825e31f1..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeaderValue.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace SeedApi.Core; - -internal sealed class HeaderValue -{ - private readonly Func> _resolver; - - public HeaderValue(string value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value); - } - - public HeaderValue(Func value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); - } - - public HeaderValue(Func> value) - { - _resolver = value; - } - - public HeaderValue(Func> value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); - } - - public static implicit operator HeaderValue(string value) => new(value); - - public static implicit operator HeaderValue(Func value) => new(value); - - public static implicit operator HeaderValue( - Func> value - ) => new(value); - - public static implicit operator HeaderValue( - Func> value - ) => new(value); - - public static HeaderValue FromString(string value) => new(value); - - public static HeaderValue FromFunc(Func value) => new(value); - - public static HeaderValue FromValueTaskFunc( - Func> value - ) => new(value); - - public static HeaderValue FromTaskFunc( - Func> value - ) => new(value); - - internal global::System.Threading.Tasks.ValueTask ResolveAsync() => _resolver(); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs deleted file mode 100644 index 5b2bfc62f423..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Headers.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Represents the headers sent with the request. -/// -internal sealed class Headers : Dictionary -{ - internal Headers() { } - - /// - /// Initializes a new instance of the Headers class with the specified value. - /// - /// - internal Headers(Dictionary value) - { - foreach (var kvp in value) - { - this[kvp.Key] = kvp.Value; - } - } - - /// - /// Initializes a new instance of the Headers class with the specified value. - /// - /// - internal Headers(IEnumerable> value) - : base(value.ToDictionary(e => e.Key, e => e.Value)) { } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs deleted file mode 100644 index 734b7ff41065..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HeadersBuilder.cs +++ /dev/null @@ -1,197 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Fluent builder for constructing HTTP headers with support for merging from multiple sources. -/// Provides a clean API for building headers with proper precedence handling. -/// -internal static class HeadersBuilder -{ - /// - /// Fluent builder for constructing HTTP headers. - /// - public sealed class Builder - { - private readonly Dictionary _headers; - - /// - /// Initializes a new instance with default capacity. - /// Uses case-insensitive header name comparison. - /// - public Builder() - { - _headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - /// - /// Initializes a new instance with the specified initial capacity. - /// Uses case-insensitive header name comparison. - /// - public Builder(int capacity) - { - _headers = new Dictionary( - capacity, - StringComparer.OrdinalIgnoreCase - ); - } - - /// - /// Adds a header with the specified key and value. - /// If a header with the same key already exists, it will be overwritten. - /// Null values are ignored. - /// - /// The header name. - /// The header value. Null values are ignored. - /// This builder instance for method chaining. - public Builder Add(string key, string? value) - { - if (value is not null) - { - _headers[key] = (value); - } - return this; - } - - /// - /// Adds a header with the specified key and object value. - /// The value will be converted to string using ValueConvert for consistent serialization. - /// If a header with the same key already exists, it will be overwritten. - /// Null values are ignored. - /// - /// The header name. - /// The header value. Null values are ignored. - /// This builder instance for method chaining. - public Builder Add(string key, object? value) - { - if (value is null) - { - return this; - } - - // Use ValueConvert for consistent serialization across headers, query params, and path params - var stringValue = ValueConvert.ToString(value); - if (stringValue is not null) - { - _headers[key] = (stringValue); - } - return this; - } - - /// - /// Adds multiple headers from a Headers dictionary. - /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. - /// Overwrites any existing headers with the same key. - /// Null entries are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(Headers? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - _headers[header.Key] = header.Value; - } - - return this; - } - - /// - /// Adds multiple headers from a Headers dictionary, excluding the Authorization header. - /// This is useful for endpoints that don't require authentication, to avoid triggering - /// lazy auth token resolution. - /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. - /// Overwrites any existing headers with the same key. - /// Null entries are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder AddWithoutAuth(Headers? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - _headers[header.Key] = header.Value; - } - - return this; - } - - /// - /// Adds multiple headers from a key-value pair collection. - /// Overwrites any existing headers with the same key. - /// Null values are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(IEnumerable>? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - if (header.Value is not null) - { - _headers[header.Key] = (header.Value); - } - } - - return this; - } - - /// - /// Adds multiple headers from a dictionary. - /// Overwrites any existing headers with the same key. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(Dictionary? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - _headers[header.Key] = (header.Value); - } - - return this; - } - - /// - /// Asynchronously builds the final headers dictionary containing all merged headers. - /// Resolves all HeaderValue instances that may contain async operations. - /// Returns a case-insensitive dictionary. - /// - /// A task that represents the asynchronous operation, containing a case-insensitive dictionary of headers. - public async global::System.Threading.Tasks.Task> BuildAsync() - { - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var kvp in _headers) - { - var value = await kvp.Value.ResolveAsync().ConfigureAwait(false); - if (value is not null) - { - headers[kvp.Key] = value; - } - } - return headers; - } - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs deleted file mode 100644 index 4295da097dc9..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpContentExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -#if !NET5_0_OR_GREATER -namespace SeedApi.Core; - -/// -/// Polyfill extension providing a ReadAsStringAsync(CancellationToken) overload -/// for target frameworks older than .NET 5, where only the parameterless -/// ReadAsStringAsync() is available. -/// -internal static class HttpContentExtensions -{ - internal static Task ReadAsStringAsync( - this HttpContent httpContent, - CancellationToken cancellationToken - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return httpContent.ReadAsStringAsync(); - } -} -#endif diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs deleted file mode 100644 index cedb977973d1..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/HttpMethodExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -internal static class HttpMethodExtensions -{ - public static readonly HttpMethod Patch = new("PATCH"); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs deleted file mode 100644 index 1a5d48064427..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IIsRetryableContent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -public interface IIsRetryableContent -{ - public bool IsRetryable { get; } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs deleted file mode 100644 index 4562c1723cf9..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/IRequestOptions.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace SeedApi.Core; - -internal interface IRequestOptions -{ - /// - /// The Base URL for the API. - /// - public string? BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The http client used to make requests. - /// - public HttpClient? HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional headers to be sent with the request. - /// Headers previously set with matching keys will be overwritten. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The max number of retries to attempt. - /// - public int? MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The timeout for the request. - /// - public TimeSpan? Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional query parameters sent with the request. - /// - public IEnumerable> AdditionalQueryParameters { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional body properties sent with the request. - /// This is only applied to JSON requests. - /// - public object? AdditionalBodyProperties { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs deleted file mode 100644 index 93dcc6dd6bca..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonAccessAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace SeedApi.Core; - -[global::System.AttributeUsage( - global::System.AttributeTargets.Property | global::System.AttributeTargets.Field -)] -internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute -{ - internal JsonAccessType AccessType { get; init; } = accessType; -} - -internal enum JsonAccessType -{ - ReadOnly, - WriteOnly, -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs deleted file mode 100644 index 2fa8cfb6ad8c..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonConfiguration.cs +++ /dev/null @@ -1,275 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Encodings.Web; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using global::System.Text.Json.Serialization; -using global::System.Text.Json.Serialization.Metadata; - -namespace SeedApi.Core; - -internal static partial class JsonOptions -{ - internal static readonly JsonSerializerOptions JsonSerializerOptions; - internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; - - static JsonOptions() - { - var options = new JsonSerializerOptions - { - Converters = - { - new DateTimeSerializer(), -#if USE_PORTABLE_DATE_ONLY - new DateOnlyConverter(), -#endif - new OneOfSerializer(), - new OptionalJsonConverterFactory(), - }, -#if DEBUG - WriteIndented = true, -#endif - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - TypeInfoResolver = new DefaultJsonTypeInfoResolver - { - Modifiers = - { - NullableOptionalModifier, - JsonAccessAndIgnoreModifier, - HandleExtensionDataFields, - }, - }, - }; - ConfigureJsonSerializerOptions(options); - JsonSerializerOptions = options; - - var relaxedOptions = new JsonSerializerOptions(options) - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; - JsonSerializerOptionsRelaxedEscaping = relaxedOptions; - } - - private static void NullableOptionalModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var property in typeInfo.Properties) - { - var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; - - if (propertyInfo is null) - continue; - - // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior - var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); - if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) - { - // ReadOnly means "never serialize", which completely overrides Optional/Nullable. - // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier - // will set ShouldSerialize = false anyway. - continue; - } - // Note: WriteOnly doesn't conflict with Optional/Nullable since it only - // affects deserialization (Set), not serialization (ShouldSerialize) - - var isOptionalType = - property.PropertyType.IsGenericType - && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); - - var hasOptionalAttribute = - propertyInfo.GetCustomAttribute() is not null; - var hasNullableAttribute = - propertyInfo.GetCustomAttribute() is not null; - - if (isOptionalType && hasOptionalAttribute) - { - var originalGetter = property.Get; - if (originalGetter is not null) - { - var capturedIsNullable = hasNullableAttribute; - - property.ShouldSerialize = (obj, value) => - { - var optionalValue = originalGetter(obj); - if (optionalValue is not IOptional optional) - return false; - - if (!optional.IsDefined) - return false; - - if (!capturedIsNullable) - { - var innerValue = optional.GetBoxedValue(); - if (innerValue is null) - return false; - } - - return true; - }; - } - } - else if (hasNullableAttribute) - { - // Force serialization of nullable properties even when null - property.ShouldSerialize = (obj, value) => true; - } - } - } - - private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var propertyInfo in typeInfo.Properties) - { - var jsonAccessAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonAccessAttribute is not null) - { - propertyInfo.IsRequired = false; - switch (jsonAccessAttribute.AccessType) - { - case JsonAccessType.ReadOnly: - propertyInfo.ShouldSerialize = (_, _) => false; - break; - case JsonAccessType.WriteOnly: - propertyInfo.Set = null; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - var jsonIgnoreAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonIgnoreAttribute is not null) - { - propertyInfo.IsRequired = false; - } - } - } - - private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) - { - if ( - typeInfo.Kind == JsonTypeInfoKind.Object - && typeInfo.Properties.All(prop => !prop.IsExtensionData) - ) - { - var extensionProp = typeInfo - .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) - .FirstOrDefault(prop => - prop.GetCustomAttribute() is not null - ); - - if (extensionProp is not null) - { - var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( - extensionProp.FieldType, - extensionProp.Name - ); - jsonPropertyInfo.Get = extensionProp.GetValue; - jsonPropertyInfo.Set = extensionProp.SetValue; - jsonPropertyInfo.IsExtensionData = true; - typeInfo.Properties.Add(jsonPropertyInfo); - } - } - } - - static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); -} - -internal static class JsonUtils -{ - internal static string Serialize(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); - - internal static string Serialize(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); - - internal static string SerializeRelaxedEscaping(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static JsonElement SerializeToElement(T obj) => - JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonElement SerializeToElement(object obj, global::System.Type type) => - JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); - - internal static JsonDocument SerializeToDocument(T obj) => - JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonNode? SerializeToNode(T obj) => - JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); - - internal static byte[] SerializeToUtf8Bytes(T obj) => - JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); - - internal static string SerializeWithAdditionalProperties( - T obj, - object? additionalProperties = null - ) - { - if (additionalProperties is null) - { - return Serialize(obj); - } - var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); - if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) - { - throw new InvalidOperationException( - "The additional properties must serialize to a JSON object." - ); - } - var jsonNode = SerializeToNode(obj); - if (jsonNode is not JsonObject jsonObject) - { - throw new InvalidOperationException( - "The serialized object must be a JSON object to add properties." - ); - } - MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); - return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); - } - - private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) - { - foreach (var property in overrideObject) - { - if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) - { - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - continue; - } - if ( - existingValue is JsonObject nestedBaseObject - && property.Value is JsonObject nestedOverrideObject - ) - { - // If both values are objects, recursively merge them. - MergeJsonObjects(nestedBaseObject, nestedOverrideObject); - continue; - } - // Otherwise, the overrideObject takes precedence. - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - } - } - - internal static T Deserialize(string json) => - JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs deleted file mode 100644 index 0a85891304f1..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/JsonRequest.cs +++ /dev/null @@ -1,36 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// The request object to be sent for JSON APIs. -/// -internal record JsonRequest : BaseRequest -{ - internal object? Body { get; init; } - - internal override HttpContent? CreateContent() - { - if (Body is null && Options?.AdditionalBodyProperties is null) - { - return null; - } - - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - ContentType, - Utf8NoBom, - "application/json" - ); - var content = new StringContent( - JsonUtils.SerializeWithAdditionalProperties(Body, Options?.AdditionalBodyProperties), - encoding, - mediaType - ); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - return content; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs deleted file mode 100644 index bf72225d461b..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/MultipartFormRequest.cs +++ /dev/null @@ -1,294 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; - -namespace SeedApi.Core; - -/// -/// The request object to be sent for multipart form data. -/// -internal record MultipartFormRequest : BaseRequest -{ - private readonly List> _partAdders = []; - - internal void AddJsonPart(string name, object? value) => AddJsonPart(name, value, null); - - internal void AddJsonPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - contentType, - Utf8NoBom, - "application/json" - ); - var content = new StringContent(JsonUtils.Serialize(value), encoding, mediaType); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - form.Add(content, name); - }); - } - - internal void AddJsonParts(string name, IEnumerable? value) => - AddJsonParts(name, value, null); - - internal void AddJsonParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddJsonPart(name, item, contentType); - } - } - - internal void AddJsonParts(string name, IEnumerable? value) => - AddJsonParts(name, value, null); - - internal void AddJsonParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddJsonPart(name, item, contentType); - } - } - - internal void AddStringPart(string name, object? value) => AddStringPart(name, value, null); - - internal void AddStringPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - AddStringPart(name, ValueConvert.ToString(value), contentType); - } - - internal void AddStringPart(string name, string? value) => AddStringPart(name, value, null); - - internal void AddStringPart(string name, string? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - contentType, - Utf8NoBom, - "text/plain" - ); - var content = new StringContent(value, encoding, mediaType); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - form.Add(content, name); - }); - } - - internal void AddStringParts(string name, IEnumerable? value) => - AddStringParts(name, value, null); - - internal void AddStringParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - AddStringPart(name, ValueConvert.ToString(value), contentType); - } - - internal void AddStringParts(string name, IEnumerable? value) => - AddStringParts(name, value, null); - - internal void AddStringParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddStringPart(name, item, contentType); - } - } - - internal void AddStreamPart(string name, Stream? stream, string? fileName) => - AddStreamPart(name, stream, fileName, null); - - internal void AddStreamPart(string name, Stream? stream, string? fileName, string? contentType) - { - if (stream is null) - { - return; - } - - _partAdders.Add(form => - { - var content = new StreamContent(stream) - { - Headers = - { - ContentType = MediaTypeHeaderValue.Parse( - contentType ?? "application/octet-stream" - ), - }, - }; - - if (fileName is not null) - { - form.Add(content, name, fileName); - } - else - { - form.Add(content, name); - } - }); - } - - internal void AddFileParameterPart(string name, Stream? stream) => - AddStreamPart(name, stream, null, null); - - internal void AddFileParameterPart(string name, FileParameter? file) => - AddFileParameterPart(name, file, null); - - internal void AddFileParameterPart( - string name, - FileParameter? file, - string? fallbackContentType - ) => - AddStreamPart(name, file?.Stream, file?.FileName, file?.ContentType ?? fallbackContentType); - - internal void AddFileParameterParts(string name, IEnumerable? files) => - AddFileParameterParts(name, files, null); - - internal void AddFileParameterParts( - string name, - IEnumerable? files, - string? fallbackContentType - ) - { - if (files is null) - { - return; - } - - foreach (var file in files) - { - AddFileParameterPart(name, file, fallbackContentType); - } - } - - internal void AddFormEncodedPart(string name, object? value) => - AddFormEncodedPart(name, value, null); - - internal void AddFormEncodedPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var content = FormUrlEncoder.EncodeAsForm(value); - if (!string.IsNullOrEmpty(contentType)) - { - content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); - } - - form.Add(content, name); - }); - } - - internal void AddFormEncodedParts(string name, IEnumerable? value) => - AddFormEncodedParts(name, value, null); - - internal void AddFormEncodedParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddFormEncodedPart(name, item, contentType); - } - } - - internal void AddExplodedFormEncodedPart(string name, object? value) => - AddExplodedFormEncodedPart(name, value, null); - - internal void AddExplodedFormEncodedPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var content = FormUrlEncoder.EncodeAsExplodedForm(value); - if (!string.IsNullOrEmpty(contentType)) - { - content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); - } - - form.Add(content, name); - }); - } - - internal void AddExplodedFormEncodedParts(string name, IEnumerable? value) => - AddExplodedFormEncodedParts(name, value, null); - - internal void AddExplodedFormEncodedParts( - string name, - IEnumerable? value, - string? contentType - ) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddExplodedFormEncodedPart(name, item, contentType); - } - } - - internal override HttpContent CreateContent() - { - var form = new MultipartFormDataContent(); - foreach (var adder in _partAdders) - { - adder(form); - } - - return form; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs deleted file mode 100644 index a1d30328bf9a..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/NullableAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as nullable in the OpenAPI specification. -/// When applied to Optional properties, this indicates that null values should be -/// written to JSON when the optional is defined with null. -/// -/// -/// For regular (required) properties: -/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) -/// - With [Nullable]: null values are written to JSON -/// -/// For Optional properties (also marked with [Optional]): -/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) -/// - With [Nullable]: Optional.Of(null) → write null to JSON -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs deleted file mode 100644 index 6eeb68fcba46..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OneOfSerializer.cs +++ /dev/null @@ -1,145 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using OneOf; - -namespace SeedApi.Core; - -internal class OneOfSerializer : JsonConverter -{ - public override IOneOf? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType is JsonTokenType.Null) - return default; - - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - var readerCopy = reader; - var result = JsonSerializer.Deserialize(ref readerCopy, type, options); - reader.Skip(); - return (IOneOf)cast.Invoke(null, [result])!; - } - catch (JsonException) { } - } - - throw new JsonException( - $"Cannot deserialize into one of the supported types for {typeToConvert}" - ); - } - - public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value.Value, options); - } - - public override IOneOf ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = reader.GetString(); - if (stringValue == null) - throw new JsonException("Cannot deserialize null property name into OneOf type"); - - // Try to deserialize the string value into one of the supported types - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - // For primitive types, try direct conversion - if (type == typeof(string)) - { - return (IOneOf)cast.Invoke(null, [stringValue])!; - } - - // For other types, try to deserialize from JSON string - var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); - if (result != null) - { - return (IOneOf)cast.Invoke(null, [result])!; - } - } - catch { } - } - - // If no type-specific deserialization worked, default to string if available - var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); - if (stringType != default) - { - return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; - } - - throw new JsonException( - $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" - ); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - IOneOf value, - JsonSerializerOptions options - ) - { - // Serialize the underlying value to a string suitable for use as a dictionary key - var stringValue = value.Value?.ToString() ?? "null"; - writer.WritePropertyName(stringValue); - } - - private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( - global::System.Type typeToConvert - ) - { - var type = typeToConvert; - if (Nullable.GetUnderlyingType(type) is { } underlyingType) - { - type = underlyingType; - } - - var casts = type.GetRuntimeMethods() - .Where(m => m.IsSpecialName && m.Name == "op_Implicit") - .ToArray(); - while (type is not null) - { - if ( - type.IsGenericType - && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) - ) - { - var genericArguments = type.GetGenericArguments(); - if (genericArguments.Length == 1) - { - return [(genericArguments[0], casts[0])]; - } - - // if object type is present, make sure it is last - var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); - if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) - { - genericArguments = genericArguments - .OrderBy(t => t == typeof(object) ? 1 : 0) - .ToArray(); - } - - return genericArguments - .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) - .ToArray(); - } - - type = type.BaseType; - } - - throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); - } - - public override bool CanConvert(global::System.Type typeToConvert) - { - return typeof(IOneOf).IsAssignableFrom(typeToConvert); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs deleted file mode 100644 index d174943cb2cf..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Optional.cs +++ /dev/null @@ -1,474 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Non-generic interface for Optional types to enable reflection-free checks. -/// -public interface IOptional -{ - /// - /// Returns true if the value is defined (set), even if the value is null. - /// - bool IsDefined { get; } - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - object? GetBoxedValue(); -} - -/// -/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). -/// Use this for HTTP PATCH requests where you need to distinguish between: -/// -/// Undefined: Don't send this field (leave it unchanged on the server) -/// Defined with null: Send null (clear the field on the server) -/// Defined with value: Send the value (update the field on the server) -/// -/// -/// The type of the value. Use nullable types (T?) for fields that can be null. -/// -/// For nullable string fields, use Optional<string?>: -/// -/// public class UpdateUserRequest -/// { -/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; -/// } -/// -/// var request = new UpdateUserRequest -/// { -/// Name = "John" // Will send: { "name": "John" } -/// }; -/// -/// var request2 = new UpdateUserRequest -/// { -/// Name = Optional<string?>.Of(null) // Will send: { "name": null } -/// }; -/// -/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) -/// -/// -public readonly struct Optional : IOptional, IEquatable> -{ - private readonly T _value; - private readonly bool _isDefined; - - private Optional(T value, bool isDefined) - { - _value = value; - _isDefined = isDefined; - } - - /// - /// Creates an undefined value - the field will not be included in the HTTP request. - /// Use this as the default value for optional fields. - /// - /// - /// - /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; - /// - /// - public static Optional Undefined => new(default!, false); - - /// - /// Creates a defined value - the field will be included in the HTTP request. - /// The value can be null if T is a nullable type. - /// - /// The value to set. Can be null if T is nullable (e.g., string?, int?). - /// - /// - /// // Set to a value - /// request.Name = Optional<string?>.Of("John"); - /// - /// // Set to null (clears the field) - /// request.Email = Optional<string?>.Of(null); - /// - /// // Or use implicit conversion - /// request.Name = "John"; // Same as Of("John") - /// request.Email = null; // Same as Of(null) - /// - /// - public static Optional Of(T value) => new(value, true); - - /// - /// Returns true if the field is defined (set), even if the value is null. - /// Use this to determine if the field should be included in the HTTP request. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// requestBody["name"] = request.Name.Value; // Include in request (can be null) - /// } - /// - /// - public bool IsDefined => _isDefined; - - /// - /// Returns true if the field is undefined (not set). - /// Use this to check if the field should be excluded from the HTTP request. - /// - /// - /// - /// if (request.Email.IsUndefined) - /// { - /// // Don't include email in the request - leave it unchanged - /// } - /// - /// - public bool IsUndefined => !_isDefined; - - /// - /// Gets the value. The value may be null if T is a nullable type. - /// - /// Thrown if the value is undefined. - /// - /// Always check before accessing Value, or use instead. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> - /// } - /// - /// // Or check for null explicitly - /// if (request.Email.IsDefined && request.Email.Value is null) - /// { - /// // Email is explicitly set to null (clear it) - /// } - /// - /// - public T Value - { - get - { - if (!_isDefined) - throw new InvalidOperationException("Optional value is undefined"); - return _value; - } - } - - /// - /// Gets the value if defined, otherwise returns the specified default value. - /// Note: If the value is defined as null, this returns null (not the default). - /// - /// The value to return if undefined. - /// The actual value if defined (can be null), otherwise the default value. - /// - /// - /// string name = request.Name.GetValueOrDefault("Anonymous"); - /// // If Name is undefined: returns "Anonymous" - /// // If Name is Of(null): returns null - /// // If Name is Of("John"): returns "John" - /// - /// - public T GetValueOrDefault(T defaultValue = default!) - { - return _isDefined ? _value : defaultValue; - } - - /// - /// Tries to get the value. Returns true if the value is defined (even if null). - /// - /// - /// When this method returns, contains the value if defined, or default(T) if undefined. - /// The value may be null if T is nullable. - /// - /// True if the value is defined; otherwise, false. - /// - /// - /// if (request.Email.TryGetValue(out var email)) - /// { - /// requestBody["email"] = email; // email can be null - /// } - /// else - /// { - /// // Email is undefined - don't include in request - /// } - /// - /// - public bool TryGetValue(out T value) - { - if (_isDefined) - { - value = _value; - return true; - } - value = default!; - return false; - } - - /// - /// Implicitly converts a value to Optional<T>.Of(value). - /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). - /// - /// The value to convert (can be null if T is nullable). - public static implicit operator Optional(T value) => Of(value); - - /// - /// Returns a string representation of this Optional value. - /// - /// "Undefined" if not set, or "Defined(value)" if set. - public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - public object? GetBoxedValue() - { - if (!_isDefined) - return null; - return _value; - } - - /// - public bool Equals(Optional other) => - _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is Optional other && Equals(other); - - /// - public override int GetHashCode() - { - if (!_isDefined) - return 0; - unchecked - { - int hash = 17; - hash = hash * 31 + 1; // _isDefined = true - hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); - return hash; - } - } - - /// - /// Determines whether two Optional values are equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are equal; otherwise, false. - public static bool operator ==(Optional left, Optional right) => left.Equals(right); - - /// - /// Determines whether two Optional values are not equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are not equal; otherwise, false. - public static bool operator !=(Optional left, Optional right) => !left.Equals(right); -} - -/// -/// Extension methods for Optional to simplify common operations. -/// -public static class OptionalExtensions -{ - /// - /// Adds the value to a dictionary if the optional is defined (even if the value is null). - /// This is useful for building JSON request payloads where null values should be included. - /// - /// The type of the optional value. - /// The optional value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined - /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined - /// - /// - public static void AddTo( - this Optional optional, - Dictionary dictionary, - string key - ) - { - if (optional.IsDefined) - { - dictionary[key] = optional.Value; - } - } - - /// - /// Executes an action if the optional is defined. - /// - /// The type of the optional value. - /// The optional value. - /// The action to execute with the value. - /// - /// - /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); - /// - /// - public static void IfDefined(this Optional optional, Action action) - { - if (optional.IsDefined) - { - action(optional.Value); - } - } - - /// - /// Maps the value to a new type if the optional is defined, otherwise returns undefined. - /// - /// The type of the original value. - /// The type to map to. - /// The optional value to map. - /// The mapping function. - /// An optional containing the mapped value if defined, otherwise undefined. - /// - /// - /// Optional<string?> name = Optional<string?>.Of("John"); - /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) - /// - /// - public static Optional Map( - this Optional optional, - Func mapper - ) - { - return optional.IsDefined - ? Optional.Of(mapper(optional.Value)) - : Optional.Undefined; - } - - /// - /// Adds a nullable value to a dictionary only if it is not null. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The type of the value (must be a reference type or Nullable). - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : class - { - if (value is not null) - { - dictionary[key] = value; - } - } - - /// - /// Adds a nullable value type to a dictionary only if it has a value. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The underlying value type. - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : struct - { - if (value.HasValue) - { - dictionary[key] = value.Value; - } - } -} - -/// -/// JSON converter factory for Optional that handles undefined vs null correctly. -/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. -/// -public class OptionalJsonConverterFactory : JsonConverterFactory -{ - public override bool CanConvert(global::System.Type typeToConvert) - { - if (!typeToConvert.IsGenericType) - return false; - - return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); - } - - public override JsonConverter? CreateConverter( - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var valueType = typeToConvert.GetGenericArguments()[0]; - var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); - return (JsonConverter?)global::System.Activator.CreateInstance(converterType); - } -} - -/// -/// JSON converter for Optional that unwraps the value during serialization. -/// The actual property skipping is handled by the OptionalTypeInfoResolver. -/// -public class OptionalJsonConverter : JsonConverter> -{ - public override Optional Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return Optional.Of(default!); - } - - var value = JsonSerializer.Deserialize(ref reader, options); - return Optional.Of(value!); - } - - public override void Write( - Utf8JsonWriter writer, - Optional value, - JsonSerializerOptions options - ) - { - // This will be called by the serializer - // We need to unwrap and serialize the inner value - // The TypeInfoResolver will handle skipping undefined values - - if (value.IsUndefined) - { - // This shouldn't be called for undefined values due to ShouldSerialize - // But if it is, write null and let the resolver filter it - writer.WriteNullValue(); - return; - } - - // Get the inner value - var innerValue = value.Value; - - // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) - if (innerValue is null) - { - writer.WriteNullValue(); - return; - } - - // Serialize the unwrapped value - JsonSerializer.Serialize(writer, innerValue, options); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs deleted file mode 100644 index 4c4c4073a0ae..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/OptionalAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as optional in the OpenAPI specification. -/// Optional properties use the Optional type and can be undefined (not present in JSON). -/// -/// -/// Properties marked with [Optional] should use the Optional type: -/// - Undefined: Optional.Undefined → omitted from JSON -/// - Defined: Optional.Of(value) → written to JSON -/// -/// Combine with [Nullable] to allow null values: -/// - [Optional, Nullable] Optional → can be undefined, null, or a value -/// - [Optional] Optional → can be undefined or a value (null is invalid) -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs deleted file mode 100644 index 8b43322350bd..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/AdditionalProperties.cs +++ /dev/null @@ -1,353 +0,0 @@ -using global::System.Collections; -using global::System.Collections.ObjectModel; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using SeedApi.Core; - -namespace SeedApi; - -public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties -{ - internal ReadOnlyAdditionalProperties() { } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record ReadOnlyAdditionalProperties : IReadOnlyDictionary -{ - private readonly Dictionary _extensionData = new(); - private readonly Dictionary _convertedCache = new(); - - internal ReadOnlyAdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - if (kvp.Value is JsonElement element) - { - _extensionData.Add(kvp.Key, element); - } - else - { - _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); - } - - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(JsonElement value) - { - if (typeof(T) == typeof(JsonElement)) - { - return (T)(object)value; - } - - return value.Deserialize(JsonOptions.JsonSerializerOptions)!; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var cached)) - { - return cached; - } - - var value = ConvertToT(_extensionData[key]); - _convertedCache[key] = value; - return value; - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public int Count => _extensionData.Count; - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var element)) - { - value = ConvertToT(element); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public T this[string key] => GetCached(key); - - public IEnumerable Keys => _extensionData.Keys; - - public IEnumerable Values => Keys.Select(GetCached); -} - -public record AdditionalProperties : AdditionalProperties -{ - public AdditionalProperties() { } - - public AdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record AdditionalProperties : IDictionary -{ - private readonly Dictionary _extensionData; - private readonly Dictionary _convertedCache; - - public AdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - public AdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - _extensionData[kvp.Key] = kvp.Value; - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(object? extensionDataValue) - { - return extensionDataValue switch - { - T value => value, - JsonElement jsonElement => jsonElement.Deserialize( - JsonOptions.JsonSerializerOptions - )!, - JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, - _ => JsonUtils - .SerializeToElement(extensionDataValue) - .Deserialize(JsonOptions.JsonSerializerOptions)!, - }; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - internal void CopyToExtensionData(IDictionary extensionData) - { - extensionData.Clear(); - foreach (var kvp in _extensionData) - { - extensionData[kvp.Key] = kvp.Value; - } - } - - public JsonObject ToJsonObject() => - ( - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ) - ).AsObject(); - - public JsonNode ToJsonNode() => - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ); - - public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); - - public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); - - public IReadOnlyDictionary ToJsonElementDictionary() - { - return new ReadOnlyDictionary( - _extensionData.ToDictionary( - kvp => kvp.Key, - kvp => - { - if (kvp.Value is JsonElement jsonElement) - { - return jsonElement; - } - - return JsonUtils.SerializeToElement(kvp.Value); - } - ) - ); - } - - public ICollection Keys => _extensionData.Keys; - - public ICollection Values - { - get - { - var values = new T[_extensionData.Count]; - var i = 0; - foreach (var key in Keys) - { - values[i++] = GetCached(key); - } - - return values; - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var value)) - { - return value; - } - - value = ConvertToT(_extensionData[key]); - _convertedCache.Add(key, value); - return value; - } - - private void SetCached(string key, T value) - { - _extensionData[key] = value; - _convertedCache[key] = value; - } - - private void AddCached(string key, T value) - { - _extensionData.Add(key, value); - _convertedCache.Add(key, value); - } - - private bool RemoveCached(string key) - { - var isRemoved = _extensionData.Remove(key); - _convertedCache.Remove(key); - return isRemoved; - } - - public int Count => _extensionData.Count; - public bool IsReadOnly => false; - - public T this[string key] - { - get => GetCached(key); - set => SetCached(key, value); - } - - public void Add(string key, T value) => AddCached(key, value); - - public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); - - public bool Remove(string key) => RemoveCached(key); - - public bool Remove(KeyValuePair item) => RemoveCached(item.Key); - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool Contains(KeyValuePair item) - { - return _extensionData.ContainsKey(item.Key) - && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); - } - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var extensionDataValue)) - { - value = ConvertToT(extensionDataValue); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public void Clear() - { - _extensionData.Clear(); - _convertedCache.Clear(); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array is null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (arrayIndex < 0 || arrayIndex > array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } - - if (array.Length - arrayIndex < _extensionData.Count) - { - throw new ArgumentException( - "The array does not have enough space to copy the elements." - ); - } - - foreach (var kvp in _extensionData) - { - array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); - } - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs deleted file mode 100644 index 837716a987f2..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/ClientOptions.cs +++ /dev/null @@ -1,84 +0,0 @@ -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public partial class ClientOptions -{ - /// - /// The http headers sent with the request. - /// - internal Headers Headers { get; init; } = new(); - - /// - /// The Base URL for the API. - /// - public string BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = SeedApiEnvironment.Default; - - /// - /// The http client used to make requests. - /// - public HttpClient HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = new HttpClient(); - - /// - /// Additional headers to be sent with HTTP requests. - /// Headers with matching keys will be overwritten by headers set on the request. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = []; - - /// - /// The max number of retries to attempt. - /// - public int MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = 2; - - /// - /// The timeout for the request. - /// - public TimeSpan Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = TimeSpan.FromSeconds(30); - - /// - /// Clones this and returns a new instance - /// - internal ClientOptions Clone() - { - return new ClientOptions - { - BaseUrl = BaseUrl, - HttpClient = HttpClient, - MaxRetries = MaxRetries, - Timeout = Timeout, - Headers = new Headers(new Dictionary(Headers)), - AdditionalHeaders = AdditionalHeaders, - }; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs deleted file mode 100644 index f33d49028884..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/FileParameter.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace SeedApi; - -/// -/// File parameter for uploading files. -/// -public record FileParameter : IDisposable -#if NET6_0_OR_GREATER - , IAsyncDisposable -#endif -{ - private bool _disposed; - - /// - /// The name of the file to be uploaded. - /// - public string? FileName { get; set; } - - /// - /// The content type of the file to be uploaded. - /// - public string? ContentType { get; set; } - - /// - /// The content of the file to be uploaded. - /// - public required Stream Stream { get; set; } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - if (disposing) - { - Stream.Dispose(); - } - - _disposed = true; - } - -#if NET6_0_OR_GREATER - /// - public async ValueTask DisposeAsync() - { - if (!_disposed) - { - await Stream.DisposeAsync().ConfigureAwait(false); - _disposed = true; - } - - GC.SuppressFinalize(this); - } -#endif - - public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs deleted file mode 100644 index f711c7f774d2..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RawResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using global::System.Net; - -namespace SeedApi; - -/// -/// Contains HTTP response metadata including status code, URL, and headers. -/// -public record RawResponse -{ - /// - /// The HTTP status code of the response. - /// - public required HttpStatusCode StatusCode { get; init; } - - /// - /// The request URL that generated this response. - /// - public required Uri Url { get; init; } - - /// - /// The HTTP response headers. - /// - public required Core.ResponseHeaders Headers { get; init; } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs deleted file mode 100644 index ea7db76b032f..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/RequestOptions.cs +++ /dev/null @@ -1,86 +0,0 @@ -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public partial class RequestOptions : IRequestOptions -{ - /// - /// The Base URL for the API. - /// - public string? BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The http client used to make requests. - /// - public HttpClient? HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional headers to be sent with the request. - /// Headers previously set with matching keys will be overwritten. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = []; - - /// - /// The max number of retries to attempt. - /// - public int? MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The timeout for the request. - /// - public TimeSpan? Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional query parameters sent with the request. - /// - public IEnumerable> AdditionalQueryParameters { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = Enumerable.Empty>(); - - /// - /// Additional body properties sent with the request. - /// This is only applied to JSON requests. - /// - public object? AdditionalBodyProperties { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs deleted file mode 100644 index 94afb43fa0a3..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiApiException.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace SeedApi; - -/// -/// This exception type will be thrown for any non-2XX API responses. -/// -public class SeedApiApiException( - string message, - int statusCode, - object body, - Exception? innerException = null -) : SeedApiException(message, innerException) -{ - /// - /// The error code of the response that triggered the exception. - /// - public int StatusCode => statusCode; - - /// - /// The body of the response that triggered the exception. - /// - public object Body => body; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs deleted file mode 100644 index d30f17ce0829..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiEnvironment.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -[Serializable] -public class SeedApiEnvironment -{ - public const string Default = "https://api.example.com"; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs deleted file mode 100644 index 90e03e71e695..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/SeedApiException.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -/// -/// Base exception class for all exceptions thrown by the SDK. -/// -public class SeedApiException(string message, Exception? innerException = null) - : Exception(message, innerException); diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs deleted file mode 100644 index 3d210b7e0b4c..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/Version.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -[Serializable] -internal class Version -{ - public const string Current = "0.0.1"; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs deleted file mode 100644 index 4c173fb2c115..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponse.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedApi; - -/// -/// Wraps a parsed response value with its raw HTTP response metadata. -/// -/// The type of the parsed response data. -public readonly struct WithRawResponse -{ - /// - /// The parsed response data. - /// - public required T Data { get; init; } - - /// - /// The raw HTTP response metadata. - /// - public required RawResponse RawResponse { get; init; } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs deleted file mode 100644 index 7c939859cb9f..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/Public/WithRawResponseTask.cs +++ /dev/null @@ -1,144 +0,0 @@ -using global::System.Runtime.CompilerServices; - -namespace SeedApi; - -/// -/// A task-like type that wraps Task<WithRawResponse<T>> and provides dual-mode awaiting: -/// - Direct await yields just T (zero-allocation path for common case) -/// - .WithRawResponse() yields WithRawResponse<T> (when raw response metadata is needed) -/// -/// The type of the parsed response data. -public readonly struct WithRawResponseTask -{ - private readonly global::System.Threading.Tasks.Task> _task; - - /// - /// Creates a new WithRawResponseTask wrapping the given task. - /// - public WithRawResponseTask(global::System.Threading.Tasks.Task> task) - { - _task = task; - } - - /// - /// Returns the underlying task that yields both the data and raw response metadata. - /// - public global::System.Threading.Tasks.Task> WithRawResponse() => _task; - - /// - /// Gets the custom awaiter that unwraps to just T when awaited. - /// - public Awaiter GetAwaiter() => new(_task.GetAwaiter()); - - /// - /// Configures the awaiter to continue on the captured context or not. - /// - public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => - new(_task.ConfigureAwait(continueOnCapturedContext)); - - /// - /// Implicitly converts WithRawResponseTask<T> to global::System.Threading.Tasks.Task<T> for backward compatibility. - /// The resulting task will yield just the data when awaited. - /// - public static implicit operator global::System.Threading.Tasks.Task( - WithRawResponseTask task - ) - { - return task._task.ContinueWith( - t => t.Result.Data, - TaskContinuationOptions.ExecuteSynchronously - ); - } - - /// - /// Custom awaiter that unwraps WithRawResponse<T> to just T. - /// - public readonly struct Awaiter : ICriticalNotifyCompletion - { - private readonly TaskAwaiter> _awaiter; - - internal Awaiter(TaskAwaiter> awaiter) - { - _awaiter = awaiter; - } - - /// - /// Gets whether the underlying task has completed. - /// - public bool IsCompleted => _awaiter.IsCompleted; - - /// - /// Gets the result, unwrapping to just the data. - /// - public T GetResult() => _awaiter.GetResult().Data; - - /// - /// Schedules the continuation action. - /// - public void OnCompleted(global::System.Action continuation) => - _awaiter.OnCompleted(continuation); - - /// - /// Schedules the continuation action without capturing the execution context. - /// - public void UnsafeOnCompleted(global::System.Action continuation) => - _awaiter.UnsafeOnCompleted(continuation); - } - - /// - /// Awaitable type returned by ConfigureAwait that unwraps to just T. - /// - public readonly struct ConfiguredTaskAwaitable - { - private readonly ConfiguredTaskAwaitable> _configuredTask; - - internal ConfiguredTaskAwaitable(ConfiguredTaskAwaitable> configuredTask) - { - _configuredTask = configuredTask; - } - - /// - /// Gets the configured awaiter that unwraps to just T. - /// - public ConfiguredAwaiter GetAwaiter() => new(_configuredTask.GetAwaiter()); - - /// - /// Custom configured awaiter that unwraps WithRawResponse<T> to just T. - /// - public readonly struct ConfiguredAwaiter : ICriticalNotifyCompletion - { - private readonly ConfiguredTaskAwaitable< - WithRawResponse - >.ConfiguredTaskAwaiter _awaiter; - - internal ConfiguredAwaiter( - ConfiguredTaskAwaitable>.ConfiguredTaskAwaiter awaiter - ) - { - _awaiter = awaiter; - } - - /// - /// Gets whether the underlying task has completed. - /// - public bool IsCompleted => _awaiter.IsCompleted; - - /// - /// Gets the result, unwrapping to just the data. - /// - public T GetResult() => _awaiter.GetResult().Data; - - /// - /// Schedules the continuation action. - /// - public void OnCompleted(global::System.Action continuation) => - _awaiter.OnCompleted(continuation); - - /// - /// Schedules the continuation action without capturing the execution context. - /// - public void UnsafeOnCompleted(global::System.Action continuation) => - _awaiter.UnsafeOnCompleted(continuation); - } - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs deleted file mode 100644 index 9498488a311b..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringBuilder.cs +++ /dev/null @@ -1,654 +0,0 @@ -using global::System.Buffers; -using global::System.Runtime.CompilerServices; -#if !NET6_0_OR_GREATER -using global::System.Text; -#endif - -namespace SeedApi.Core; - -/// -/// High-performance query string builder with RFC 3986 compliant percent-encoding. -/// Uses span-based APIs on .NET 6+ and StringBuilder fallback for older targets. -/// -/// RFC 3986 defines the following relevant productions: -/// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -/// query = *( pchar / "/" / "?" ) -/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -/// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" -/// -/// Three encoding contexts are distinguished: -/// Path segment (pchar): unreserved + sub-delims + ":" + "@" -/// Query key: query chars minus "&", "=", "+", "#" -/// Query value: query chars minus "&", "+", "#" -/// -internal static class QueryStringBuilder -{ - // ────────────────────────────────────────────────────────────────────── - // RFC 3986 character sets - // - // Query key safe: unreserved + (sub-delims \ {& = +}) + : @ / ? - // Query value safe: unreserved + (sub-delims \ {& +}) + : @ / ? - // Path segment safe: unreserved + sub-delims + : @ - // ────────────────────────────────────────────────────────────────────── - -#if NET8_0_OR_GREATER - private static readonly SearchValues SafeQueryKeyChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?" - ); - - private static readonly SearchValues SafeQueryValueChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?" - ); - - private static readonly SearchValues SafePathChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@" - ); -#else - private const string SafeQueryKeyChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?"; - - private const string SafeQueryValueChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?"; - - private const string SafePathChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@"; -#endif - -#if NET7_0_OR_GREATER - private static ReadOnlySpan UpperHexChars => "0123456789ABCDEF"u8; -#else - private static readonly byte[] UpperHexChars = - { - (byte)'0', - (byte)'1', - (byte)'2', - (byte)'3', - (byte)'4', - (byte)'5', - (byte)'6', - (byte)'7', - (byte)'8', - (byte)'9', - (byte)'A', - (byte)'B', - (byte)'C', - (byte)'D', - (byte)'E', - (byte)'F', - }; -#endif - - private enum EncodingContext - { - QueryKey, - QueryValue, - Path, - } - - /// - /// Percent-encodes a path segment value per RFC 3986 section 3.3 (pchar). - /// Allowed unencoded: unreserved / sub-delims / ":" / "@" - /// - public static string EncodePathSegment(string value) - { - if (string.IsNullOrEmpty(value)) - return value; - -#if NET6_0_OR_GREATER - if (!NeedsEncoding(value.AsSpan(), EncodingContext.Path)) - return value; - - var buffer = ArrayPool.Shared.Rent(value.Length * 3); - try - { - var written = EncodeSlow(value.AsSpan(), buffer.AsSpan(), EncodingContext.Path); - return new string(buffer.AsSpan(0, written)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } -#else - var sb = new StringBuilder(value.Length); - AppendEncoded(sb, value, EncodingContext.Path); - return sb.ToString(); -#endif - } - - /// - /// Builds a query string from the provided parameters. - /// -#if NET6_0_OR_GREATER - public static string Build(ReadOnlySpan> parameters) - { - if (parameters.IsEmpty) - return string.Empty; - - var estimatedLength = EstimateLength(parameters); - if (estimatedLength == 0) - return string.Empty; - - var bufferSize = Math.Min(estimatedLength * 3, 8192); - var buffer = ArrayPool.Shared.Rent(bufferSize); - - try - { - var written = BuildCore(parameters, buffer); - return new string(buffer.AsSpan(0, written)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - private static int EstimateLength(ReadOnlySpan> parameters) - { - var estimatedLength = 0; - foreach (var kvp in parameters) - { - estimatedLength += kvp.Key.Length + kvp.Value.Length + 2; - } - return estimatedLength; - } -#endif - - /// - /// Builds a query string from the provided parameters. - /// - public static string Build(IEnumerable> parameters) - { -#if NET6_0_OR_GREATER - // Try to get span access for collections that support it - if (parameters is ICollection> collection) - { - if (collection.Count == 0) - return string.Empty; - - var array = ArrayPool>.Shared.Rent(collection.Count); - try - { - collection.CopyTo(array, 0); - return Build(array.AsSpan(0, collection.Count)); - } - finally - { - ArrayPool>.Shared.Return(array); - } - } - - // Fallback for non-collection enumerables - using var enumerator = parameters.GetEnumerator(); - if (!enumerator.MoveNext()) - return string.Empty; - - var buffer = ArrayPool.Shared.Rent(4096); - try - { - var position = 0; - var first = true; - - do - { - var kvp = enumerator.Current; - - // Ensure capacity (worst case: 3x for encoding + separators) - var required = (kvp.Key.Length + kvp.Value.Length + 2) * 3; - if (position + required > buffer.Length) - { - var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); - buffer.AsSpan(0, position).CopyTo(newBuffer); - ArrayPool.Shared.Return(buffer); - buffer = newBuffer; - } - - buffer[position++] = first ? '?' : '&'; - first = false; - - position += EncodeWithCharSet( - kvp.Key.AsSpan(), - buffer.AsSpan(position), - EncodingContext.QueryKey - ); - buffer[position++] = '='; - position += EncodeWithCharSet( - kvp.Value.AsSpan(), - buffer.AsSpan(position), - EncodingContext.QueryValue - ); - } while (enumerator.MoveNext()); - - return first ? string.Empty : new string(buffer.AsSpan(0, position)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } -#else - // netstandard2.0 / net462 fallback using StringBuilder - var sb = new StringBuilder(); - var first = true; - - foreach (var kvp in parameters) - { - sb.Append(first ? '?' : '&'); - first = false; - - AppendEncoded(sb, kvp.Key, EncodingContext.QueryKey); - sb.Append('='); - AppendEncoded(sb, kvp.Value, EncodingContext.QueryValue); - } - - return sb.ToString(); -#endif - } - -#if NET6_0_OR_GREATER - private static int BuildCore( - ReadOnlySpan> parameters, - Span buffer - ) - { - var position = 0; - var first = true; - - foreach (var kvp in parameters) - { - buffer[position++] = first ? '?' : '&'; - first = false; - - position += EncodeWithCharSet( - kvp.Key.AsSpan(), - buffer.Slice(position), - EncodingContext.QueryKey - ); - buffer[position++] = '='; - position += EncodeWithCharSet( - kvp.Value.AsSpan(), - buffer.Slice(position), - EncodingContext.QueryValue - ); - } - - return position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int EncodeWithCharSet( - ReadOnlySpan input, - Span output, - EncodingContext context - ) - { - if (!NeedsEncoding(input, context)) - { - input.CopyTo(output); - return input.Length; - } - - return EncodeSlow(input, output, context); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool NeedsEncoding(ReadOnlySpan value, EncodingContext context) - { - return context switch - { - EncodingContext.QueryKey => value.ContainsAnyExcept(SafeQueryKeyChars), - EncodingContext.QueryValue => value.ContainsAnyExcept(SafeQueryValueChars), - EncodingContext.Path => value.ContainsAnyExcept(SafePathChars), - _ => true, - }; - } - - private static int EncodeSlow( - ReadOnlySpan input, - Span output, - EncodingContext context - ) - { - var position = 0; - - foreach (var c in input) - { - if (IsSafeChar(c, context)) - { - output[position++] = c; - } - else if (c == ' ') - { - output[position++] = '%'; - output[position++] = '2'; - output[position++] = '0'; - } - else if (char.IsAscii(c)) - { - position += EncodeAscii((byte)c, output.Slice(position)); - } - else - { - position += EncodeUtf8(c, output.Slice(position)); - } - } - - return position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int EncodeAscii(byte value, Span output) - { - output[0] = '%'; - output[1] = (char)UpperHexChars[value >> 4]; - output[2] = (char)UpperHexChars[value & 0xF]; - return 3; - } - - private static int EncodeUtf8(char c, Span output) - { - Span utf8Bytes = stackalloc byte[4]; - Span singleChar = stackalloc char[1] { c }; - var byteCount = global::System.Text.Encoding.UTF8.GetBytes(singleChar, utf8Bytes); - - var position = 0; - for (var i = 0; i < byteCount; i++) - { - output[position++] = '%'; - output[position++] = (char)UpperHexChars[utf8Bytes[i] >> 4]; - output[position++] = (char)UpperHexChars[utf8Bytes[i] & 0xF]; - } - - return position; - } -#else - // netstandard2.0 / net462 StringBuilder-based encoding - private static void AppendEncoded(StringBuilder sb, string value, EncodingContext context) - { - foreach (var c in value) - { - if (IsSafeChar(c, context)) - { - sb.Append(c); - } - else if (c == ' ') - { - sb.Append("%20"); - } - else if (c <= 127) - { - AppendPercentEncoded(sb, (byte)c); - } - else - { - var bytes = Encoding.UTF8.GetBytes(new[] { c }); - foreach (var b in bytes) - { - AppendPercentEncoded(sb, b); - } - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AppendPercentEncoded(StringBuilder sb, byte value) - { - sb.Append('%'); - sb.Append((char)UpperHexChars[value >> 4]); - sb.Append((char)UpperHexChars[value & 0xF]); - } -#endif - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeChar(char c, EncodingContext context) - { - return context switch - { - EncodingContext.QueryKey => IsSafeQueryKeyChar(c), - EncodingContext.QueryValue => IsSafeQueryValueChar(c), - EncodingContext.Path => IsSafePathChar(c), - _ => false, - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeQueryKeyChar(char c) - { -#if NET8_0_OR_GREATER - return SafeQueryKeyChars.Contains(c); -#else - // query = *( pchar / "/" / "?" ) minus "&", "=", "+", "#" - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') - || c == '-' - || c == '_' - || c == '.' - || c == '~' - || c == '!' - || c == '$' - || c == (char)39 // single quote - || c == '(' - || c == ')' - || c == '*' - || c == ',' - || c == ';' - || c == ':' - || c == '@' - || c == '/' - || c == '?'; -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeQueryValueChar(char c) - { -#if NET8_0_OR_GREATER - return SafeQueryValueChars.Contains(c); -#else - // query = *( pchar / "/" / "?" ) minus "&", "+", "#" - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') - || c == '-' - || c == '_' - || c == '.' - || c == '~' - || c == '!' - || c == '$' - || c == (char)39 // single quote - || c == '(' - || c == ')' - || c == '*' - || c == ',' - || c == ';' - || c == '=' - || c == ':' - || c == '@' - || c == '/' - || c == '?'; -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafePathChar(char c) - { -#if NET8_0_OR_GREATER - return SafePathChars.Contains(c); -#else - // pchar = unreserved / sub-delims / ":" / "@" - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') - || c == '-' - || c == '_' - || c == '.' - || c == '~' - || c == '!' - || c == '$' - || c == '&' - || c == (char)39 // single quote - || c == '(' - || c == ')' - || c == '*' - || c == '+' - || c == ',' - || c == ';' - || c == '=' - || c == ':' - || c == '@'; -#endif - } - - /// - /// Fluent builder for constructing query strings with support for simple parameters and deep object notation. - /// - public sealed class Builder - { - private readonly List> _params; - - /// - /// Initializes a new instance with default capacity. - /// - public Builder() - { - _params = new List>(); - } - - /// - /// Initializes a new instance with the specified initial capacity. - /// - public Builder(int capacity) - { - _params = new List>(capacity); - } - - /// - /// Adds a simple parameter. For collections, adds multiple key-value pairs (one per element). - /// - public Builder Add(string key, object? value) - { - if (value is null) - { - return this; - } - - // Handle string separately since it implements IEnumerable - if (value is string stringValue) - { - _params.Add(new KeyValuePair(key, stringValue)); - return this; - } - - // Handle collections (arrays, lists, etc.) - add each element as a separate key-value pair - if ( - value - is global::System.Collections.IEnumerable enumerable - and not global::System.Collections.IDictionary - ) - { - foreach (var item in enumerable) - { - if (item is not null) - { - _params.Add( - new KeyValuePair( - key, - ValueConvert.ToQueryStringValue(item) - ) - ); - } - } - return this; - } - - // Handle scalar values - _params.Add( - new KeyValuePair(key, ValueConvert.ToQueryStringValue(value)) - ); - return this; - } - - /// - /// Sets a parameter, removing any existing parameters with the same key before adding the new value. - /// For collections, removes all existing parameters with the key, then adds multiple key-value pairs (one per element). - /// This allows overriding parameters set earlier in the builder. - /// - public Builder Set(string key, object? value) - { - // Remove all existing parameters with this key - _params.RemoveAll(kv => kv.Key == key); - - // Add the new value(s) - return Add(key, value); - } - - /// - /// Merges additional query parameters with override semantics. - /// Groups parameters by key and calls Set() once per unique key. - /// This ensures that parameters with the same key are properly merged: - /// - If a key appears once, it's added as a single value - /// - If a key appears multiple times, all values are added as an array - /// - All parameters override any existing parameters with the same key - /// - public Builder MergeAdditional( - global::System.Collections.Generic.IEnumerable>? additionalParameters - ) - { - if (additionalParameters is null) - { - return this; - } - - // Group by key to handle multiple values for the same key correctly - var grouped = additionalParameters - .GroupBy(kv => kv.Key) - .Select(g => new global::System.Collections.Generic.KeyValuePair( - g.Key, - g.Count() == 1 ? (object)g.First().Value : g.Select(kv => kv.Value).ToArray() - )); - - foreach (var param in grouped) - { - Set(param.Key, param.Value); - } - - return this; - } - - /// - /// Adds a complex object using deep object notation with a prefix. - /// Deep object notation nests properties with brackets: prefix[key][nested]=value - /// - public Builder AddDeepObject(string prefix, object? value) - { - if (value is not null) - { - _params.AddRange(QueryStringConverter.ToDeepObject(prefix, value)); - } - return this; - } - - /// - /// Adds a complex object using exploded form notation with an optional prefix. - /// Exploded form flattens properties: prefix[key]=value (no deep nesting). - /// - public Builder AddExploded(string prefix, object? value) - { - if (value is not null) - { - _params.AddRange(QueryStringConverter.ToExplodedForm(prefix, value)); - } - return this; - } - - /// - /// Builds the final query string. - /// - public string Build() - { - return QueryStringBuilder.Build(_params); - } - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs deleted file mode 100644 index be39e2f51aa4..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/QueryStringConverter.cs +++ /dev/null @@ -1,259 +0,0 @@ -using global::System.Text.Json; - -namespace SeedApi.Core; - -/// -/// Converts an object into a query string collection. -/// -internal static class QueryStringConverter -{ - /// - /// Converts an object into a query string collection using Deep Object notation with a prefix. - /// - /// The prefix to prepend to all keys (e.g., "session_settings"). Pass empty string for no prefix. - /// Object to form URL-encode. Can be an object, array of objects, or dictionary. - /// Throws when passing in a string or primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToDeepObject( - string prefix, - object value - ) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - JsonToDeepObject(json, prefix, queryCollection); - return queryCollection; - } - - /// - /// Converts an object into a query string collection using Deep Object notation. - /// - /// Object to form URL-encode. Can be an object, array of objects, or dictionary. - /// Throws when passing in a string or primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToDeepObject(object value) - { - return ToDeepObject("", value); - } - - /// - /// Converts an object into a query string collection using Exploded Form notation with a prefix. - /// - /// The prefix to prepend to all keys. Pass empty string for no prefix. - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToExplodedForm( - string prefix, - object value - ) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - AssertRootJson(json); - JsonToFormExploded(json, prefix, queryCollection); - return queryCollection; - } - - /// - /// Converts an object into a query string collection using Exploded Form notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToExplodedForm(object value) - { - return ToExplodedForm("", value); - } - - /// - /// Converts an object into a query string collection using Form notation without exploding parameters. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToForm(object value) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - AssertRootJson(json); - JsonToForm(json, "", queryCollection); - return queryCollection; - } - - private static void AssertRootJson(JsonElement json) - { - switch (json.ValueKind) - { - case JsonValueKind.Object: - break; - case JsonValueKind.Array: - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - case JsonValueKind.Null: - default: - throw new global::System.Exception( - $"Only objects can be converted to query string collections. Given type is {json.ValueKind}." - ); - } - } - - private static void JsonToForm( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToForm(property.Value, newPrefix, parameters); - } - break; - case JsonValueKind.Array: - var arrayValues = element.EnumerateArray().Select(ValueToString).ToArray(); - parameters.Add( - new KeyValuePair(prefix, string.Join(",", arrayValues)) - ); - break; - case JsonValueKind.Null: - break; - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static void JsonToFormExploded( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToFormExploded(property.Value, newPrefix, parameters); - } - - break; - case JsonValueKind.Array: - foreach (var item in element.EnumerateArray()) - { - if ( - item.ValueKind != JsonValueKind.Object - && item.ValueKind != JsonValueKind.Array - ) - { - parameters.Add( - new KeyValuePair(prefix, ValueToString(item)) - ); - } - else - { - JsonToFormExploded(item, prefix, parameters); - } - } - - break; - case JsonValueKind.Null: - break; - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static void JsonToDeepObject( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToDeepObject(property.Value, newPrefix, parameters); - } - - break; - case JsonValueKind.Array: - var index = 0; - foreach (var item in element.EnumerateArray()) - { - var newPrefix = $"{prefix}[{index++}]"; - - if ( - item.ValueKind != JsonValueKind.Object - && item.ValueKind != JsonValueKind.Array - ) - { - parameters.Add( - new KeyValuePair(newPrefix, ValueToString(item)) - ); - } - else - { - JsonToDeepObject(item, newPrefix, parameters); - } - } - - break; - case JsonValueKind.Null: - case JsonValueKind.Undefined: - // Skip null and undefined values - don't add parameters for them - break; - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static string ValueToString(JsonElement element) - { - return element.ValueKind switch - { - JsonValueKind.String => element.GetString() ?? "", - JsonValueKind.Number => element.GetRawText(), - JsonValueKind.True => "true", - JsonValueKind.False => "false", - JsonValueKind.Null => "", - _ => element.GetRawText(), - }; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs deleted file mode 100644 index d42791fcc0e0..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawClient.cs +++ /dev/null @@ -1,344 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; -using global::System.Text; -using SystemTask = global::System.Threading.Tasks.Task; - -namespace SeedApi.Core; - -/// -/// Utility class for making raw HTTP requests to the API. -/// -internal partial class RawClient(ClientOptions clientOptions) -{ - private const int MaxRetryDelayMs = 60000; - private const double JitterFactor = 0.2; -#if NET6_0_OR_GREATER - // Use Random.Shared for thread-safe random number generation on .NET 6+ -#else - private static readonly object JitterLock = new(); - private static readonly Random JitterRandom = new(); -#endif - internal int BaseRetryDelay { get; set; } = 1000; - - /// - /// The client options applied on every request. - /// - internal readonly ClientOptions Options = clientOptions; - - internal async global::System.Threading.Tasks.Task SendRequestAsync( - global::SeedApi.Core.BaseRequest request, - CancellationToken cancellationToken = default - ) - { - // Apply the request timeout. - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var timeout = request.Options?.Timeout ?? Options.Timeout; - cts.CancelAfter(timeout); - - var httpRequest = await CreateHttpRequestAsync(request).ConfigureAwait(false); - // Send the request. - return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token) - .ConfigureAwait(false); - } - - internal async global::System.Threading.Tasks.Task SendRequestAsync( - HttpRequestMessage request, - IRequestOptions? options, - CancellationToken cancellationToken = default - ) - { - // Apply the request timeout. - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var timeout = options?.Timeout ?? Options.Timeout; - cts.CancelAfter(timeout); - - // Send the request. - return await SendWithRetriesAsync(request, options, cts.Token).ConfigureAwait(false); - } - - private static async global::System.Threading.Tasks.Task CloneRequestAsync( - HttpRequestMessage request - ) - { - var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri); - clonedRequest.Version = request.Version; - - if (request.Content != null) - { - switch (request.Content) - { - case MultipartContent oldMultipartFormContent: - var originalBoundary = - oldMultipartFormContent - .Headers.ContentType?.Parameters.First(p => - p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) - ) - .Value?.Trim('"') - ?? Guid.NewGuid().ToString(); - var newMultipartContent = oldMultipartFormContent switch - { - MultipartFormDataContent => new MultipartFormDataContent(originalBoundary), - _ => new MultipartContent(), - }; - foreach (var content in oldMultipartFormContent) - { - var ms = new MemoryStream(); - await content.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - var newPart = new StreamContent(ms); - foreach (var header in content.Headers) - { - newPart.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - newMultipartContent.Add(newPart); - } - - clonedRequest.Content = newMultipartContent; - break; - default: - var bodyStream = new MemoryStream(); - await request.Content.CopyToAsync(bodyStream).ConfigureAwait(false); - bodyStream.Position = 0; - var clonedContent = new StreamContent(bodyStream); - foreach (var header in request.Content.Headers) - { - clonedContent.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - clonedRequest.Content = clonedContent; - break; - } - } - - foreach (var header in request.Headers) - { - clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - return clonedRequest; - } - - /// - /// Sends the request with retries, unless the request content is not retryable, - /// such as stream requests and multipart form data with stream content. - /// - private async global::System.Threading.Tasks.Task SendWithRetriesAsync( - HttpRequestMessage request, - IRequestOptions? options, - CancellationToken cancellationToken - ) - { - var httpClient = options?.HttpClient ?? Options.HttpClient; - var maxRetries = options?.MaxRetries ?? Options.MaxRetries; - var response = await httpClient - .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) - .ConfigureAwait(false); - var isRetryableContent = IsRetryableContent(request); - - if (!isRetryableContent) - { - return new global::SeedApi.Core.ApiResponse - { - StatusCode = (int)response.StatusCode, - Raw = response, - }; - } - - for (var i = 0; i < maxRetries; i++) - { - if (!ShouldRetry(response)) - { - break; - } - - var delayMs = GetRetryDelayFromHeaders(response, i); - await SystemTask.Delay(delayMs, cancellationToken).ConfigureAwait(false); - using var retryRequest = await CloneRequestAsync(request).ConfigureAwait(false); - response = await httpClient - .SendAsync( - retryRequest, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken - ) - .ConfigureAwait(false); - } - - return new global::SeedApi.Core.ApiResponse - { - StatusCode = (int)response.StatusCode, - Raw = response, - }; - } - - private static bool ShouldRetry(HttpResponseMessage response) - { - var statusCode = (int)response.StatusCode; - return statusCode is 408 or 429 or >= 500; - } - - private static int AddPositiveJitter(int delayMs) - { -#if NET6_0_OR_GREATER - var random = Random.Shared.NextDouble(); -#else - double random; - lock (JitterLock) - { - random = JitterRandom.NextDouble(); - } -#endif - var jitterMultiplier = 1 + random * JitterFactor; - return (int)(delayMs * jitterMultiplier); - } - - private static int AddSymmetricJitter(int delayMs) - { -#if NET6_0_OR_GREATER - var random = Random.Shared.NextDouble(); -#else - double random; - lock (JitterLock) - { - random = JitterRandom.NextDouble(); - } -#endif - var jitterMultiplier = 1 + (random - 0.5) * JitterFactor; - return (int)(delayMs * jitterMultiplier); - } - - private int GetRetryDelayFromHeaders(HttpResponseMessage response, int retryAttempt) - { - if (response.Headers.TryGetValues("Retry-After", out var retryAfterValues)) - { - var retryAfter = retryAfterValues.FirstOrDefault(); - if (!string.IsNullOrEmpty(retryAfter)) - { - if (int.TryParse(retryAfter, out var retryAfterSeconds) && retryAfterSeconds > 0) - { - return Math.Min(retryAfterSeconds * 1000, MaxRetryDelayMs); - } - - if (DateTimeOffset.TryParse(retryAfter, out var retryAfterDate)) - { - var delay = (int)(retryAfterDate - DateTimeOffset.UtcNow).TotalMilliseconds; - if (delay > 0) - { - return Math.Min(delay, MaxRetryDelayMs); - } - } - } - } - - if (response.Headers.TryGetValues("X-RateLimit-Reset", out var rateLimitResetValues)) - { - var rateLimitReset = rateLimitResetValues.FirstOrDefault(); - if ( - !string.IsNullOrEmpty(rateLimitReset) - && long.TryParse(rateLimitReset, out var resetTime) - ) - { - var resetDateTime = DateTimeOffset.FromUnixTimeSeconds(resetTime); - var delay = (int)(resetDateTime - DateTimeOffset.UtcNow).TotalMilliseconds; - if (delay > 0) - { - return AddPositiveJitter(Math.Min(delay, MaxRetryDelayMs)); - } - } - } - - var exponentialDelay = Math.Min(BaseRetryDelay * (1 << retryAttempt), MaxRetryDelayMs); - return AddSymmetricJitter(exponentialDelay); - } - - private static bool IsRetryableContent(HttpRequestMessage request) - { - return request.Content switch - { - IIsRetryableContent c => c.IsRetryable, - StreamContent => false, - MultipartContent content => !content.Any(c => c is StreamContent), - _ => true, - }; - } - - internal async global::System.Threading.Tasks.Task CreateHttpRequestAsync( - global::SeedApi.Core.BaseRequest request - ) - { - var url = BuildUrl(request); - var httpRequest = new HttpRequestMessage(request.Method, url); - httpRequest.Content = request.CreateContent(); - SetHeaders(httpRequest, request.Headers); - - return httpRequest; - } - - private string BuildUrl(global::SeedApi.Core.BaseRequest request) - { - var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl ?? Options.BaseUrl; - - var trimmedBaseUrl = baseUrl.TrimEnd('/'); - var trimmedBasePath = request.Path.TrimStart('/'); - var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; - - // Append query string if present - if (!string.IsNullOrEmpty(request.QueryString)) - { - return url + request.QueryString; - } - - return url; - } - - private void SetHeaders(HttpRequestMessage httpRequest, Dictionary? headers) - { - if (headers is null) - { - return; - } - - foreach (var kv in headers) - { - if (kv.Value is null) - { - continue; - } - - httpRequest.Headers.TryAddWithoutValidation(kv.Key, kv.Value); - } - } - - private static (Encoding encoding, string? charset, string mediaType) ParseContentTypeOrDefault( - string? contentType, - Encoding encodingFallback, - string mediaTypeFallback - ) - { - var encoding = encodingFallback; - var mediaType = mediaTypeFallback; - string? charset = null; - if (string.IsNullOrEmpty(contentType)) - { - return (encoding, charset, mediaType); - } - - if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) - { - return (encoding, charset, mediaType); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) - { - charset = mediaTypeHeaderValue.CharSet; - encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) - { - mediaType = mediaTypeHeaderValue.MediaType; - } - - return (encoding, charset, mediaType); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs deleted file mode 100644 index 5fc790dcc6f1..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/RawResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using global::System.Net; - -namespace SeedApi.Core; - -/// -/// Contains HTTP response metadata including status code, URL, and headers. -/// -public record RawResponse -{ - /// - /// The HTTP status code of the response. - /// - public required HttpStatusCode StatusCode { get; init; } - - /// - /// The request URL that generated this response. - /// - public required Uri Url { get; init; } - - /// - /// The HTTP response headers. - /// - public required Core.ResponseHeaders Headers { get; init; } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs deleted file mode 100644 index 2c394dd9a7a9..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ResponseHeaders.cs +++ /dev/null @@ -1,108 +0,0 @@ -using global::System.Collections; -using global::System.Net.Http.Headers; - -namespace SeedApi.Core; - -/// -/// Represents HTTP response headers with case-insensitive lookup. -/// -public readonly struct ResponseHeaders : IEnumerable -{ - private readonly HttpResponseHeaders? _headers; - private readonly HttpContentHeaders? _contentHeaders; - - private ResponseHeaders(HttpResponseHeaders headers, HttpContentHeaders? contentHeaders) - { - _headers = headers; - _contentHeaders = contentHeaders; - } - - /// - /// Gets the Content-Type header value, if present. - /// - public string? ContentType => _contentHeaders?.ContentType?.ToString(); - - /// - /// Gets the Content-Length header value, if present. - /// - public long? ContentLength => _contentHeaders?.ContentLength; - - /// - /// Creates a ResponseHeaders instance from an HttpResponseMessage. - /// - public static ResponseHeaders FromHttpResponseMessage(HttpResponseMessage response) - { - return new ResponseHeaders(response.Headers, response.Content?.Headers); - } - - /// - /// Tries to get a single header value. Returns the first value if multiple values exist. - /// - public bool TryGetValue(string name, out string? value) - { - if (TryGetValues(name, out var values) && values is not null) - { - value = values.FirstOrDefault(); - return true; - } - - value = null; - return false; - } - - /// - /// Tries to get all values for a header. - /// - public bool TryGetValues(string name, out IEnumerable? values) - { - if (_headers?.TryGetValues(name, out values) == true) - { - return true; - } - - if (_contentHeaders?.TryGetValues(name, out values) == true) - { - return true; - } - - values = null; - return false; - } - - /// - /// Checks if the headers contain a specific header name. - /// - public bool Contains(string name) - { - return _headers?.Contains(name) == true || _contentHeaders?.Contains(name) == true; - } - - /// - /// Gets an enumerator for all headers. - /// - public IEnumerator GetEnumerator() - { - if (_headers is not null) - { - foreach (var header in _headers) - { - yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); - } - } - - if (_contentHeaders is not null) - { - foreach (var header in _contentHeaders) - { - yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} - -/// -/// Represents a single HTTP header. -/// -public readonly record struct HttpHeader(string Name, string Value); diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs deleted file mode 100644 index 54076b686602..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StreamRequest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; - -namespace SeedApi.Core; - -/// -/// The request object to be sent for streaming uploads. -/// -internal record StreamRequest : BaseRequest -{ - internal Stream? Body { get; init; } - - internal override HttpContent? CreateContent() - { - if (Body is null) - { - return null; - } - - var content = new StreamContent(Body) - { - Headers = - { - ContentType = MediaTypeHeaderValue.Parse(ContentType ?? "application/octet-stream"), - }, - }; - return content; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs deleted file mode 100644 index 9f1f4a1c1181..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnum.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -public interface IStringEnum : IEquatable -{ - public string Value { get; } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs deleted file mode 100644 index 704cb6836ab8..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/StringEnumExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -internal static class StringEnumExtensions -{ - public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs deleted file mode 100644 index ba1f818399e2..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Core/ValueConvert.cs +++ /dev/null @@ -1,115 +0,0 @@ -using global::System.Globalization; - -namespace SeedApi.Core; - -/// -/// Convert values to string for path and query parameters. -/// -public static class ValueConvert -{ - internal static string ToPathParameterString(T value) => ToString(value); - - internal static string ToPathParameterString(bool v) => ToString(v); - - internal static string ToPathParameterString(int v) => ToString(v); - - internal static string ToPathParameterString(long v) => ToString(v); - - internal static string ToPathParameterString(float v) => ToString(v); - - internal static string ToPathParameterString(double v) => ToString(v); - - internal static string ToPathParameterString(decimal v) => ToString(v); - - internal static string ToPathParameterString(short v) => ToString(v); - - internal static string ToPathParameterString(ushort v) => ToString(v); - - internal static string ToPathParameterString(uint v) => ToString(v); - - internal static string ToPathParameterString(ulong v) => ToString(v); - - internal static string ToPathParameterString(string v) => - QueryStringBuilder.EncodePathSegment(v); - - internal static string ToPathParameterString(char v) => ToString(v); - - internal static string ToPathParameterString(Guid v) => ToString(v); - - internal static string ToQueryStringValue(T value) => value is null ? "" : ToString(value); - - internal static string ToQueryStringValue(bool v) => ToString(v); - - internal static string ToQueryStringValue(int v) => ToString(v); - - internal static string ToQueryStringValue(long v) => ToString(v); - - internal static string ToQueryStringValue(float v) => ToString(v); - - internal static string ToQueryStringValue(double v) => ToString(v); - - internal static string ToQueryStringValue(decimal v) => ToString(v); - - internal static string ToQueryStringValue(short v) => ToString(v); - - internal static string ToQueryStringValue(ushort v) => ToString(v); - - internal static string ToQueryStringValue(uint v) => ToString(v); - - internal static string ToQueryStringValue(ulong v) => ToString(v); - - internal static string ToQueryStringValue(string v) => v is null ? "" : v; - - internal static string ToQueryStringValue(char v) => ToString(v); - - internal static string ToQueryStringValue(Guid v) => ToString(v); - - internal static string ToString(T value) - { - return value switch - { - null => "null", - string str => str, - true => "true", - false => "false", - int i => ToString(i), - long l => ToString(l), - float f => ToString(f), - double d => ToString(d), - decimal dec => ToString(dec), - short s => ToString(s), - ushort u => ToString(u), - uint u => ToString(u), - ulong u => ToString(u), - char c => ToString(c), - Guid guid => ToString(guid), - _ => JsonUtils.SerializeRelaxedEscaping(value, value.GetType()).Trim('"'), - }; - } - - internal static string ToString(bool v) => v ? "true" : "false"; - - internal static string ToString(int v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(long v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(float v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(double v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(decimal v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(short v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(ushort v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(uint v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(ulong v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(char v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(string v) => v; - - internal static string ToString(Guid v) => v.ToString("D"); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs deleted file mode 100644 index 4b1eb3e650c8..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/ISeedApiClient.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace SeedApi; - -public partial interface ISeedApiClient -{ - WithRawResponseTask SearchRuleTypesAsync( - SearchRuleTypesRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask CreateRuleAsync( - RuleCreateRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask ListUsersAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask GetEntityAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask GetOrganizationAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs deleted file mode 100644 index 6437574c29a8..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/RuleCreateRequest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleCreateRequest -{ - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("executionContext")] - public required RuleExecutionContext ExecutionContext { get; set; } - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs deleted file mode 100644 index 3c1562150a13..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Requests/SearchRuleTypesRequest.cs +++ /dev/null @@ -1,17 +0,0 @@ -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record SearchRuleTypesRequest -{ - [JsonIgnore] - public string? Query { get; set; } - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props deleted file mode 100644 index 17a84cada530..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.Custom.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj deleted file mode 100644 index 6ba194fdf143..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApi.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - net462;net8.0;net9.0;netstandard2.0 - enable - 12 - enable - 0.0.1 - $(Version) - $(Version) - README.md - https://github.com/allof-inline/fern - true - - - - false - - - $(DefineConstants);USE_PORTABLE_DATE_ONLY - true - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - <_Parameter1>SeedApi.Test - - - - - diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs deleted file mode 100644 index 789e4c07f0e6..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/SeedApiClient.cs +++ /dev/null @@ -1,429 +0,0 @@ -using global::System.Text.Json; -using SeedApi.Core; - -namespace SeedApi; - -public partial class SeedApiClient : ISeedApiClient -{ - private readonly RawClient _client; - - public SeedApiClient(ClientOptions? clientOptions = null) - { - clientOptions ??= new ClientOptions(); - var platformHeaders = new Headers( - new Dictionary() - { - { "X-Fern-Language", "C#" }, - { "X-Fern-SDK-Name", "SeedApi" }, - { "X-Fern-SDK-Version", Version.Current }, - { "User-Agent", "Fernallof-inline/0.0.1" }, - } - ); - foreach (var header in platformHeaders) - { - if (!clientOptions.Headers.ContainsKey(header.Key)) - { - clientOptions.Headers[header.Key] = header.Value; - } - } - _client = new RawClient(clientOptions); - } - - private async Task> SearchRuleTypesAsyncCore( - SearchRuleTypesRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _queryString = new SeedApi.Core.QueryStringBuilder.Builder(capacity: 1) - .Add("query", request.Query) - .MergeAdditional(options?.AdditionalQueryParameters) - .Build(); - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "rule-types", - QueryString = _queryString, - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> CreateRuleAsyncCore( - RuleCreateRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Post, - Path = "rules", - Body = request, - Headers = _headers, - ContentType = "application/json", - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> ListUsersAsyncCore( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "users", - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> GetEntityAsyncCore( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "entities", - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> GetOrganizationAsyncCore( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "organizations", - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - /// - /// await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); - /// - public WithRawResponseTask SearchRuleTypesAsync( - SearchRuleTypesRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - SearchRuleTypesAsyncCore(request, options, cancellationToken) - ); - } - - /// - /// await client.CreateRuleAsync( - /// new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } - /// ); - /// - public WithRawResponseTask CreateRuleAsync( - RuleCreateRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - CreateRuleAsyncCore(request, options, cancellationToken) - ); - } - - /// - /// await client.ListUsersAsync(); - /// - public WithRawResponseTask ListUsersAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - ListUsersAsyncCore(options, cancellationToken) - ); - } - - /// - /// await client.GetEntityAsync(); - /// - public WithRawResponseTask GetEntityAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - GetEntityAsyncCore(options, cancellationToken) - ); - } - - /// - /// await client.GetOrganizationAsync(); - /// - public WithRawResponseTask GetOrganizationAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - GetOrganizationAsyncCore(options, cancellationToken) - ); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs deleted file mode 100644 index c0d843281678..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/AuditInfo.cs +++ /dev/null @@ -1,56 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -/// -/// Common audit metadata. -/// -[Serializable] -public record AuditInfo : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs deleted file mode 100644 index eb944d0030da..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrg.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public BaseOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs deleted file mode 100644 index fcb0efec5fea..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/BaseOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from BaseOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Subscription tier. - /// - [JsonPropertyName("tier")] - public string? Tier { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs deleted file mode 100644 index 633fb725fb81..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record CombinedEntity : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Describable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonPropertyName("status")] - public required CombinedEntityStatus Status { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs deleted file mode 100644 index 0ab2467f6bd7..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/CombinedEntityStatus.cs +++ /dev/null @@ -1,115 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] -[Serializable] -public readonly record struct CombinedEntityStatus : IStringEnum -{ - public static readonly CombinedEntityStatus Active = new(Values.Active); - - public static readonly CombinedEntityStatus Archived = new(Values.Archived); - - public CombinedEntityStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static CombinedEntityStatus FromCustom(string value) - { - return new CombinedEntityStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(CombinedEntityStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(CombinedEntityStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(CombinedEntityStatus value) => value.Value; - - public static explicit operator CombinedEntityStatus(string value) => new(value); - - internal class CombinedEntityStatusSerializer : JsonConverter - { - public override CombinedEntityStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override CombinedEntityStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Archived = "archived"; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs deleted file mode 100644 index f521e9082be7..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Describable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Describable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Display name from Describable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs deleted file mode 100644 index 7bc064682236..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrg.cs +++ /dev/null @@ -1,28 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("metadata")] - public DetailedOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs deleted file mode 100644 index 798903997238..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/DetailedOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from DetailedOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Custom domain name. - /// - [JsonPropertyName("domain")] - public string? Domain { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs deleted file mode 100644 index 05b3808b610b..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Identifiable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Identifiable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Identifiable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs deleted file mode 100644 index 602a7e16c442..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/Organization.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Organization : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public OrganizationMetadata? Metadata { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs deleted file mode 100644 index 084daaac298a..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/OrganizationMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record OrganizationMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from DetailedOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Custom domain name. - /// - [JsonPropertyName("domain")] - public string? Domain { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs deleted file mode 100644 index 9f4b1b5c0820..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PaginatedResult.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PaginatedResult : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable Results { get; set; } = new List(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs deleted file mode 100644 index b1f0001a4733..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/PagingCursors.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PagingCursors : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Cursor for the next page of results. - /// - [JsonPropertyName("next")] - public required string Next { get; set; } - - /// - /// Cursor for the previous page of results. - /// - [JsonPropertyName("previous")] - public string? Previous { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs deleted file mode 100644 index d22a26079f4e..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleExecutionContext.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] -[Serializable] -public readonly record struct RuleExecutionContext : IStringEnum -{ - public static readonly RuleExecutionContext Prod = new(Values.Prod); - - public static readonly RuleExecutionContext Staging = new(Values.Staging); - - public static readonly RuleExecutionContext Dev = new(Values.Dev); - - public RuleExecutionContext(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleExecutionContext FromCustom(string value) - { - return new RuleExecutionContext(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleExecutionContext value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleExecutionContext value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleExecutionContext value) => value.Value; - - public static explicit operator RuleExecutionContext(string value) => new(value); - - internal class RuleExecutionContextSerializer : JsonConverter - { - public override RuleExecutionContext Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleExecutionContext ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Prod = "prod"; - - public const string Staging = "staging"; - - public const string Dev = "dev"; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs deleted file mode 100644 index 78e1a0e6d50e..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponse.cs +++ /dev/null @@ -1,65 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("status")] - public required RuleResponseStatus Status { get; set; } - - [JsonPropertyName("executionContext")] - public RuleExecutionContext? ExecutionContext { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs deleted file mode 100644 index 9b587cdbcba0..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleResponseStatus.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] -[Serializable] -public readonly record struct RuleResponseStatus : IStringEnum -{ - public static readonly RuleResponseStatus Active = new(Values.Active); - - public static readonly RuleResponseStatus Inactive = new(Values.Inactive); - - public static readonly RuleResponseStatus Draft = new(Values.Draft); - - public RuleResponseStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleResponseStatus FromCustom(string value) - { - return new RuleResponseStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleResponseStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleResponseStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleResponseStatus value) => value.Value; - - public static explicit operator RuleResponseStatus(string value) => new(value); - - internal class RuleResponseStatusSerializer : JsonConverter - { - public override RuleResponseStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleResponseStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Inactive = "inactive"; - - public const string Draft = "draft"; - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs deleted file mode 100644 index 578b90315dde..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleType.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleType : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("description")] - public string? Description { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs deleted file mode 100644 index e05899151a38..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/RuleTypeSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleTypeSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs deleted file mode 100644 index abc389c0a6b6..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/User.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record User : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("email")] - public required string Email { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs b/seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs deleted file mode 100644 index 55c5368d774e..000000000000 --- a/seed/csharp-sdk/allof-inline/src/SeedApi/Types/UserSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record UserSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/.editorconfig b/seed/csharp-sdk/allof/.editorconfig deleted file mode 100644 index 1e7a0adbac80..000000000000 --- a/seed/csharp-sdk/allof/.editorconfig +++ /dev/null @@ -1,35 +0,0 @@ -root = true - -[*.cs] -resharper_arrange_object_creation_when_type_evident_highlighting = hint -resharper_auto_property_can_be_made_get_only_global_highlighting = hint -resharper_check_namespace_highlighting = hint -resharper_class_never_instantiated_global_highlighting = hint -resharper_class_never_instantiated_local_highlighting = hint -resharper_collection_never_updated_global_highlighting = hint -resharper_convert_type_check_pattern_to_null_check_highlighting = hint -resharper_inconsistent_naming_highlighting = hint -resharper_member_can_be_private_global_highlighting = hint -resharper_member_hides_static_from_outer_class_highlighting = hint -resharper_not_accessed_field_local_highlighting = hint -resharper_nullable_warning_suppression_is_used_highlighting = suggestion -resharper_partial_type_with_single_part_highlighting = hint -resharper_prefer_concrete_value_over_default_highlighting = none -resharper_private_field_can_be_converted_to_local_variable_highlighting = hint -resharper_property_can_be_made_init_only_global_highlighting = hint -resharper_property_can_be_made_init_only_local_highlighting = hint -resharper_redundant_name_qualifier_highlighting = none -resharper_redundant_using_directive_highlighting = hint -resharper_replace_slice_with_range_indexer_highlighting = none -resharper_unused_auto_property_accessor_global_highlighting = hint -resharper_unused_auto_property_accessor_local_highlighting = hint -resharper_unused_member_global_highlighting = hint -resharper_unused_type_global_highlighting = hint -resharper_use_string_interpolation_highlighting = hint -dotnet_diagnostic.CS1591.severity = suggestion - -[src/**/Types/*.cs] -resharper_check_namespace_highlighting = none - -[src/**/Core/Public/*.cs] -resharper_check_namespace_highlighting = none \ No newline at end of file diff --git a/seed/csharp-sdk/allof/.fern/metadata.json b/seed/csharp-sdk/allof/.fern/metadata.json deleted file mode 100644 index 91d0855bee07..000000000000 --- a/seed/csharp-sdk/allof/.fern/metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-csharp-sdk", - "generatorVersion": "latest", - "generatorConfig": {}, - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/csharp-sdk/allof/.github/workflows/ci.yml b/seed/csharp-sdk/allof/.github/workflows/ci.yml deleted file mode 100644 index 87068349b616..000000000000 --- a/seed/csharp-sdk/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -env: - DOTNET_NOLOGO: true - -jobs: - ci: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.x - - - name: Install tools - run: dotnet tool restore - - - name: Restore dependencies - run: dotnet restore src/SeedApi/SeedApi.csproj - - - name: Build - run: dotnet build src/SeedApi/SeedApi.csproj --no-restore -c Release - - - name: Restore test dependencies - run: dotnet restore src/SeedApi.Test/SeedApi.Test.csproj - - - name: Build tests - run: dotnet build src/SeedApi.Test/SeedApi.Test.csproj --no-restore -c Release - - - name: Test - run: dotnet test src/SeedApi.Test/SeedApi.Test.csproj --no-restore --no-build -c Release - - - name: Pack - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - run: dotnet pack src/SeedApi/SeedApi.csproj --no-build --no-restore -c Release - - - name: Publish to NuGet.org - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} - run: dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" - diff --git a/seed/csharp-sdk/allof/.gitignore b/seed/csharp-sdk/allof/.gitignore deleted file mode 100644 index 11014f2b33d7..000000000000 --- a/seed/csharp-sdk/allof/.gitignore +++ /dev/null @@ -1,484 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -## This is based on `dotnet new gitignore` and customized by Fern - -# dotenv files -.env - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -# [Rr]elease/ (Ignored by Fern) -# [Rr]eleases/ (Ignored by Fern) -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -# [Ll]og/ (Ignored by Fern) -# [Ll]ogs/ (Ignored by Fern) - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET -project.lock.json -project.fragment.lock.json -artifacts/ - -# Tye -.tye/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml -.idea - -## -## Visual studio for Mac -## - - -# globs -Makefile.in -*.userprefs -*.usertasks -config.make -config.status -aclocal.m4 -install-sh -autom4te.cache/ -*.tar.gz -tarballs/ -test-results/ - -# Mac bundle stuff -*.dmg -*.app - -# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Vim temporary swap files -*.swp diff --git a/seed/csharp-sdk/allof/README.md b/seed/csharp-sdk/allof/README.md deleted file mode 100644 index 8680768cea49..000000000000 --- a/seed/csharp-sdk/allof/README.md +++ /dev/null @@ -1,216 +0,0 @@ -# Seed C# Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FC%23) -[![nuget shield](https://img.shields.io/nuget/v/Fernallof)](https://nuget.org/packages/Fernallof) - -The Seed C# library provides convenient access to the Seed APIs from C#. - -## Table of Contents - -- [Requirements](#requirements) -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Raw Response](#raw-response) - - [Additional Headers](#additional-headers) - - [Additional Query Parameters](#additional-query-parameters) - - [Forward Compatible Enums](#forward-compatible-enums) -- [Contributing](#contributing) - -## Requirements - -This SDK requires: - -## Installation - -```sh -dotnet add package Fernallof -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```csharp -using SeedApi; - -var client = new SeedApiClient(); -await client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } -); -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```csharp -using SeedApi; - -var client = new SeedApiClient(new ClientOptions -{ - BaseUrl = SeedApiEnvironment.Default -}); -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```csharp -using SeedApi; - -try { - var response = await client.CreateRuleAsync(...); -} catch (SeedApiApiException e) { - System.Console.WriteLine(e.Body); - System.Console.WriteLine(e.StatusCode); -} -``` - -## Advanced - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `MaxRetries` request option to configure this behavior. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - MaxRetries: 0 // Override MaxRetries at the request level - } -); -``` - -### Timeouts - -The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s - } -); -``` - -### Raw Response - -Access raw HTTP response data (status code, headers, URL) alongside parsed response data using the `.WithRawResponse()` method. - -```csharp -using SeedApi; - -// Access raw response data (status code, headers, etc.) alongside the parsed response -var result = await client.CreateRuleAsync(...).WithRawResponse(); - -// Access the parsed data -var data = result.Data; - -// Access raw response metadata -var statusCode = result.RawResponse.StatusCode; -var headers = result.RawResponse.Headers; -var url = result.RawResponse.Url; - -// Access specific headers (case-insensitive) -if (headers.TryGetValue("X-Request-Id", out var requestId)) -{ - System.Console.WriteLine($"Request ID: {requestId}"); -} - -// For the default behavior, simply await without .WithRawResponse() -var data = await client.CreateRuleAsync(...); -``` - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `AdditionalHeaders` request option. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - AdditionalHeaders = new Dictionary - { - { "X-Custom-Header", "custom-value" } - } - } -); -``` - -### Additional Query Parameters - -If you would like to send additional query parameters as part of the request, use the `AdditionalQueryParameters` request option. - -```csharp -var response = await client.CreateRuleAsync( - ..., - new RequestOptions { - AdditionalQueryParameters = new Dictionary - { - { "custom_param", "custom-value" } - } - } -); -``` - -### Forward Compatible Enums - -This SDK uses forward-compatible enums that can handle unknown values gracefully. - -```csharp -using SeedApi; - -// Using a built-in value -var ruleExecutionContext = RuleExecutionContext.Prod; - -// Using a custom value -var customRuleExecutionContext = RuleExecutionContext.FromCustom("custom-value"); - -// Using in a switch statement -switch (ruleExecutionContext.Value) -{ - case RuleExecutionContext.Values.Prod: - Console.WriteLine("Prod"); - break; - default: - Console.WriteLine($"Unknown value: {ruleExecutionContext.Value}"); - break; -} - -// Explicit casting -string ruleExecutionContextString = (string)RuleExecutionContext.Prod; -RuleExecutionContext ruleExecutionContextFromString = (RuleExecutionContext)"prod"; -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/csharp-sdk/allof/SeedApi.slnx b/seed/csharp-sdk/allof/SeedApi.slnx deleted file mode 100644 index d4c63c241aad..000000000000 --- a/seed/csharp-sdk/allof/SeedApi.slnx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/seed/csharp-sdk/allof/reference.md b/seed/csharp-sdk/allof/reference.md deleted file mode 100644 index 09ba75269a0f..000000000000 --- a/seed/csharp-sdk/allof/reference.md +++ /dev/null @@ -1,158 +0,0 @@ -# Reference -
client.SearchRuleTypesAsync(SearchRuleTypesRequest { ... }) -> WithRawResponseTask<RuleTypeSearchResponse> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `SearchRuleTypesRequest` - -
-
-
-
- - -
-
-
- -
client.CreateRuleAsync(RuleCreateRequest { ... }) -> WithRawResponseTask<RuleResponse> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `RuleCreateRequest` - -
-
-
-
- - -
-
-
- -
client.ListUsersAsync() -> WithRawResponseTask<UserSearchResponse> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.ListUsersAsync(); -``` -
-
-
-
- - -
-
-
- -
client.GetEntityAsync() -> WithRawResponseTask<CombinedEntity> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.GetEntityAsync(); -``` -
-
-
-
- - -
-
-
- -
client.GetOrganizationAsync() -> WithRawResponseTask<Organization> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```csharp -await client.GetOrganizationAsync(); -``` -
-
-
-
- - -
-
-
- diff --git a/seed/csharp-sdk/allof/snippet.json b/seed/csharp-sdk/allof/snippet.json deleted file mode 100644 index df8ab96f5bc6..000000000000 --- a/seed/csharp-sdk/allof/snippet.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "types": {}, - "endpoints": [ - { - "example_identifier": null, - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.SearchRuleTypesAsync(new SearchRuleTypesRequest());\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.CreateRuleAsync(\n new RuleCreateRequest { Name = \"name\", ExecutionContext = RuleExecutionContext.Prod }\n);\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.ListUsersAsync();\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetEntityAsync();\n" - } - }, - { - "example_identifier": null, - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "csharp", - "client": "using SeedApi;\n\nvar client = new SeedApiClient();\nawait client.GetOrganizationAsync();\n" - } - } - ] -} \ No newline at end of file diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs deleted file mode 100644 index 0c6994903107..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example0.cs +++ /dev/null @@ -1,19 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example0 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.SearchRuleTypesAsync( - new SearchRuleTypesRequest() - ); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs deleted file mode 100644 index 44ebc3965c0d..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example1.cs +++ /dev/null @@ -1,21 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example1 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.SearchRuleTypesAsync( - new SearchRuleTypesRequest { - Query = "query" - } - ); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs deleted file mode 100644 index b63878a26a91..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example2.cs +++ /dev/null @@ -1,22 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example2 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.CreateRuleAsync( - new RuleCreateRequest { - Name = "name", - ExecutionContext = RuleExecutionContext.Prod - } - ); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs deleted file mode 100644 index eb0371508e02..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example3.cs +++ /dev/null @@ -1,22 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example3 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.CreateRuleAsync( - new RuleCreateRequest { - Name = "name", - ExecutionContext = RuleExecutionContext.Prod - } - ); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs deleted file mode 100644 index 440f17e55522..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example4.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example4 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.ListUsersAsync(); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs deleted file mode 100644 index c1d081509932..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example5.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example5 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.ListUsersAsync(); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs deleted file mode 100644 index 8694c56b98a0..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example6.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example6 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetEntityAsync(); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs deleted file mode 100644 index bc47bce361fb..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example7.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example7 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetEntityAsync(); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs deleted file mode 100644 index 897c0c509b2e..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example8.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example8 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetOrganizationAsync(); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs deleted file mode 100644 index 2f7b06bd2b7b..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/Example9.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SeedApi; - -namespace Usage; - -public class Example9 -{ - public async Task Do() { - var client = new SeedApiClient( - clientOptions: new ClientOptions { - BaseUrl = "https://api.fern.com" - } - ); - - await client.GetOrganizationAsync(); - } - -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj b/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj deleted file mode 100644 index 3417db2e58e2..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.DynamicSnippets/SeedApi.DynamicSnippets.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net8.0 - 12 - enable - enable - - - - - - \ No newline at end of file diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs deleted file mode 100644 index 7ad11f298637..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/HeadersBuilderTests.cs +++ /dev/null @@ -1,326 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class HeadersBuilderTests -{ - [Test] - public async global::System.Threading.Tasks.Task Add_SimpleHeaders() - { - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") - .Add("Authorization", "Bearer token123") - .Add("X-API-Key", "key456") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); - Assert.That(headers["Authorization"], Is.EqualTo("Bearer token123")); - Assert.That(headers["X-API-Key"], Is.EqualTo("key456")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_NullValuesIgnored() - { - var headers = await new HeadersBuilder.Builder() - .Add("Header1", "value1") - .Add("Header2", null) - .Add("Header3", "value3") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(2)); - Assert.That(headers.ContainsKey("Header1"), Is.True); - Assert.That(headers.ContainsKey("Header2"), Is.False); - Assert.That(headers.ContainsKey("Header3"), Is.True); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_OverwritesExistingHeader() - { - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") - .Add("Content-Type", "application/xml") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_MergesExistingHeaders() - { - var existingHeaders = new Headers( - new Dictionary { { "Header1", "value1" }, { "Header2", "value2" } } - ); - - var result = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(3)); - Assert.That(result["Header1"], Is.EqualTo("value1")); - Assert.That(result["Header2"], Is.EqualTo("value2")); - Assert.That(result["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_OverwritesExistingHeaders() - { - var existingHeaders = new Headers( - new Dictionary { { "Header1", "override" } } - ); - - var result = await new HeadersBuilder.Builder() - .Add("Header1", "original") - .Add("Header2", "keep") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(2)); - Assert.That(result["Header1"], Is.EqualTo("override")); - Assert.That(result["Header2"], Is.EqualTo("keep")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_NullHeadersIgnored() - { - var result = await new HeadersBuilder.Builder() - .Add("Header1", "value1") - .Add((Headers?)null) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(1)); - Assert.That(result["Header1"], Is.EqualTo("value1")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_AddsHeaders() - { - var additionalHeaders = new List> - { - new("Header1", "value1"), - new("Header2", "value2"), - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(additionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - Assert.That(headers["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_KeyValuePairOverload_IgnoresNullValues() - { - var additionalHeaders = new List> - { - new("Header1", "value1"), - new("Header2", null), // Should be ignored - }; - - var headers = await new HeadersBuilder.Builder() - .Add(additionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers.ContainsKey("Header2"), Is.False); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_DictionaryOverload_AddsHeaders() - { - var dict = new Dictionary - { - { "Header1", "value1" }, - { "Header2", "value2" }, - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Header3", "value3") - .Add(dict) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(3)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - Assert.That(headers["Header3"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task EmptyBuilder_ReturnsEmptyHeaders() - { - var headers = await new HeadersBuilder.Builder().BuildAsync().ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task OnlyNullValues_ReturnsEmptyHeaders() - { - var headers = await new HeadersBuilder.Builder() - .Add("Header1", null) - .Add("Header2", null) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task ComplexMergingScenario() - { - // Simulates real SDK usage: endpoint headers + client headers + request options - var clientHeaders = new Headers( - new Dictionary - { - { "X-Client-Version", "1.0.0" }, - { "User-Agent", "MyClient/1.0" }, - } - ); - - var clientAdditionalHeaders = new List> - { - new("X-Custom-Header", "custom-value"), - }; - - var requestOptionsHeaders = new Headers( - new Dictionary - { - { "Authorization", "Bearer user-token" }, - { "User-Agent", "MyClient/2.0" }, // Override - } - ); - - var requestAdditionalHeaders = new List> - { - new("X-Request-ID", "req-123"), - new("X-Custom-Header", "overridden-value"), // Override - }; - - var headers = await new HeadersBuilder.Builder() - .Add("Content-Type", "application/json") // Endpoint header - .Add("X-Endpoint-ID", "endpoint-1") - .Add(clientHeaders) - .Add(clientAdditionalHeaders) - .Add(requestOptionsHeaders) - .Add(requestAdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - - // Verify precedence - Assert.That(headers["Content-Type"], Is.EqualTo("application/json")); - Assert.That(headers["X-Endpoint-ID"], Is.EqualTo("endpoint-1")); - Assert.That(headers["X-Client-Version"], Is.EqualTo("1.0.0")); - Assert.That(headers["User-Agent"], Is.EqualTo("MyClient/2.0")); // Overridden - Assert.That(headers["Authorization"], Is.EqualTo("Bearer user-token")); - Assert.That(headers["X-Request-ID"], Is.EqualTo("req-123")); - Assert.That(headers["X-Custom-Header"], Is.EqualTo("overridden-value")); // Overridden - } - - [Test] - public async global::System.Threading.Tasks.Task Builder_WithCapacity() - { - // Test that capacity constructor works without errors - var headers = await new HeadersBuilder.Builder(capacity: 10) - .Add("Header1", "value1") - .Add("Header2", "value2") - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(2)); - Assert.That(headers["Header1"], Is.EqualTo("value1")); - Assert.That(headers["Header2"], Is.EqualTo("value2")); - } - - [Test] - public async global::System.Threading.Tasks.Task Add_HeadersOverload_ResolvesDynamicHeaderValues() - { - // Test that BuildAsync properly resolves HeaderValue instances - var existingHeaders = new Headers(); - existingHeaders["DynamicHeader"] = - (Func>)( - () => global::System.Threading.Tasks.Task.FromResult("dynamic-value") - ); - - var result = await new HeadersBuilder.Builder() - .Add("StaticHeader", "static-value") - .Add(existingHeaders) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(2)); - Assert.That(result["StaticHeader"], Is.EqualTo("static-value")); - Assert.That(result["DynamicHeader"], Is.EqualTo("dynamic-value")); - } - - [Test] - public async global::System.Threading.Tasks.Task MultipleSyncAdds() - { - var headers1 = new Headers(new Dictionary { { "H1", "v1" } }); - var headers2 = new Headers(new Dictionary { { "H2", "v2" } }); - var headers3 = new Headers(new Dictionary { { "H3", "v3" } }); - - var result = await new HeadersBuilder.Builder() - .Add(headers1) - .Add(headers2) - .Add(headers3) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result.Count, Is.EqualTo(3)); - Assert.That(result["H1"], Is.EqualTo("v1")); - Assert.That(result["H2"], Is.EqualTo("v2")); - Assert.That(result["H3"], Is.EqualTo("v3")); - } - - [Test] - public async global::System.Threading.Tasks.Task PrecedenceOrder_LatestWins() - { - // Test that later operations override earlier ones - var headers1 = new Headers(new Dictionary { { "Key", "value1" } }); - var headers2 = new Headers(new Dictionary { { "Key", "value2" } }); - var additional = new List> { new("Key", "value3") }; - - var result = await new HeadersBuilder.Builder() - .Add("Key", "value0") - .Add(headers1) - .Add(headers2) - .Add(additional) - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(result["Key"], Is.EqualTo("value3")); - } - - [Test] - public async global::System.Threading.Tasks.Task CaseInsensitiveKeys() - { - // Test that header keys are case-insensitive - var headers = await new HeadersBuilder.Builder() - .Add("content-type", "application/json") - .Add("Content-Type", "application/xml") // Should overwrite - .BuildAsync() - .ConfigureAwait(false); - - Assert.That(headers.Count, Is.EqualTo(1)); - Assert.That(headers["content-type"], Is.EqualTo("application/xml")); - Assert.That(headers["Content-Type"], Is.EqualTo("application/xml")); - Assert.That(headers["CONTENT-TYPE"], Is.EqualTo("application/xml")); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs deleted file mode 100644 index a12183113312..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/AdditionalPropertiesTests.cs +++ /dev/null @@ -1,365 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class AdditionalPropertiesTests -{ - [Test] - public void Record_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"].GetString(), Is.EqualTo("fiction")); - Assert.That(record.AdditionalProperties["title"].GetString(), Is.EqualTo("The Hobbit")); - }); - } - - [Test] - public void RecordWithWriteableAdditionalProperties_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecord - { - Id = "1", - AdditionalProperties = { ["category"] = "fiction", ["title"] = "The Hobbit" }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.Id, Is.EqualTo("1")); - Assert.That( - deserializedRecord.AdditionalProperties["category"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["category"]!).GetString(), - Is.EqualTo("fiction") - ); - Assert.That( - deserializedRecord.AdditionalProperties["title"], - Is.InstanceOf() - ); - Assert.That( - ((JsonElement)deserializedRecord.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void ReadOnlyAdditionalProperties_ShouldRetrieveValuesCorrectly() - { - // Arrange - var extensionData = new Dictionary - { - ["key1"] = JsonUtils.SerializeToElement("value1"), - ["key2"] = JsonUtils.SerializeToElement(123), - }; - var readOnlyProps = new ReadOnlyAdditionalProperties(); - readOnlyProps.CopyFromExtensionData(extensionData); - - // Act & Assert - Assert.That(readOnlyProps["key1"].GetString(), Is.EqualTo("value1")); - Assert.That(readOnlyProps["key2"].GetInt32(), Is.EqualTo(123)); - } - - [Test] - public void AdditionalProperties_ShouldBehaveAsDictionary() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - additionalProps["key3"] = true; - - // Assert - Assert.Multiple(() => - { - Assert.That(additionalProps["key1"], Is.EqualTo("value1")); - Assert.That(additionalProps["key2"], Is.EqualTo(123)); - Assert.That((bool)additionalProps["key3"]!, Is.True); - Assert.That(additionalProps.Count, Is.EqualTo(3)); - }); - } - - [Test] - public void AdditionalProperties_ToJsonObject_ShouldSerializeCorrectly() - { - // Arrange - var additionalProps = new AdditionalProperties { ["key1"] = "value1", ["key2"] = 123 }; - - // Act - var jsonObject = additionalProps.ToJsonObject(); - - Assert.Multiple(() => - { - // Assert - Assert.That(jsonObject["key1"]!.GetValue(), Is.EqualTo("value1")); - Assert.That(jsonObject["key2"]!.GetValue(), Is.EqualTo(123)); - }); - } - - [Test] - public void AdditionalProperties_MixReadAndWrite_ShouldOverwriteDeserializedProperty() - { - // Arrange - const string json = """ - { - "id": "1", - "category": "fiction", - "title": "The Hobbit" - } - """; - var record = JsonUtils.Deserialize(json); - - // Act - record.AdditionalProperties["category"] = "non-fiction"; - - // Assert - Assert.Multiple(() => - { - Assert.That(record, Is.Not.Null); - Assert.That(record.Id, Is.EqualTo("1")); - Assert.That(record.AdditionalProperties["category"], Is.EqualTo("non-fiction")); - Assert.That(record.AdditionalProperties["title"], Is.InstanceOf()); - Assert.That( - ((JsonElement)record.AdditionalProperties["title"]!).GetString(), - Is.EqualTo("The Hobbit") - ); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesInts_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": 42, - "extra2": 99 - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(record.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesInts_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithInts - { - AdditionalProperties = { ["extra1"] = 42, ["extra2"] = 99 }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"], Is.EqualTo(42)); - Assert.That(deserializedRecord.AdditionalProperties["extra2"], Is.EqualTo(99)); - }); - } - - [Test] - public void RecordWithReadonlyAdditionalPropertiesDictionaries_OnDeserialized_ShouldPopulateAdditionalProperties() - { - // Arrange - const string json = """ - { - "extra1": { "key1": true, "key2": false }, - "extra2": { "key3": true } - } - """; - - // Act - var record = JsonUtils.Deserialize(json); - - // Assert - Assert.That(record, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(record.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(record.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(record.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - [Test] - public void RecordWithAdditionalPropertiesDictionaries_OnSerialization_ShouldIncludeAdditionalProperties() - { - // Arrange - var record = new WriteableRecordWithDictionaries - { - AdditionalProperties = - { - ["extra1"] = new Dictionary { { "key1", true }, { "key2", false } }, - ["extra2"] = new Dictionary { { "key3", true } }, - }, - }; - - // Act - var json = JsonUtils.Serialize(record); - var deserializedRecord = JsonUtils.Deserialize(json); - - // Assert - Assert.That(deserializedRecord, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key1"], Is.True); - Assert.That(deserializedRecord.AdditionalProperties["extra1"]["key2"], Is.False); - Assert.That(deserializedRecord.AdditionalProperties["extra2"]["key3"], Is.True); - }); - } - - private record Record : IJsonOnDeserialized - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecord : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithInts : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithInts : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } - - private record RecordWithDictionaries : IJsonOnDeserialized - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties< - Dictionary - > AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - } - - private record WriteableRecordWithDictionaries : IJsonOnDeserialized, IJsonOnSerializing - { - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonIgnore] - public AdditionalProperties> AdditionalProperties { get; } = new(); - - void IJsonOnDeserialized.OnDeserialized() - { - AdditionalProperties.CopyFromExtensionData(_extensionData); - } - - void IJsonOnSerializing.OnSerializing() - { - AdditionalProperties.CopyToExtensionData(_extensionData); - } - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs deleted file mode 100644 index c0f258680b78..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateOnlyJsonTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateOnlyJsonTests -{ - [Test] - public void SerializeDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (new DateOnly(2023, 1, 1), "\"2023-01-01\""), - (new DateOnly(2023, 12, 31), "\"2023-12-31\""), - (new DateOnly(2023, 6, 15), "\"2023-06-15\""), - (new DateOnly(2023, 3, 10), "\"2023-03-10\""), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateOnly_ShouldMatchExpectedFormat() - { - (DateOnly? dateOnly, string expected)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - foreach (var (dateOnly, expected) in testCases) - { - var json = JsonUtils.Serialize(dateOnly); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateOnly_ShouldMatchExpectedDateOnly() - { - (DateOnly? expected, string json)[] testCases = - [ - (new DateOnly(2023, 10, 5), "\"2023-10-05\""), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateOnly = JsonUtils.Deserialize(json); - Assert.That(dateOnly, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateOnlyKey() - { - var key = new DateOnly(2023, 10, 5); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateOnlyKey() - { - var json = """ - { - "2023-10-05": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateOnly(2023, 10, 5); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs deleted file mode 100644 index 1dde45a8e939..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/DateTimeJsonTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class DateTimeJsonTests -{ - [Test] - public void SerializeDateTime_ShouldMatchExpectedFormat() - { - (DateTime dateTime, string expected)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - foreach (var (dateTime, expected) in testCases) - { - var json = JsonUtils.Serialize(dateTime); - Assert.That(json, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeDateTime_ShouldMatchExpectedDateTime() - { - (DateTime expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), "\"2023-01-01T00:00:00.000Z\""), - ( - new DateTime(2023, 12, 31, 23, 59, 59, DateTimeKind.Utc), - "\"2023-12-31T23:59:59.000Z\"" - ), - (new DateTime(2023, 6, 15, 12, 0, 0, DateTimeKind.Utc), "\"2023-06-15T12:00:00.000Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.000Z\"" - ), - (new DateTime(2023, 3, 10, 8, 45, 30, DateTimeKind.Utc), "\"2023-03-10T08:45:30Z\""), - ( - new DateTime(2023, 3, 10, 8, 45, 30, 123, DateTimeKind.Utc), - "\"2023-03-10T08:45:30.123Z\"" - ), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void SerializeNullableDateTime_ShouldMatchExpectedFormat() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void DeserializeNullableDateTime_ShouldMatchExpectedDateTime() - { - (DateTime? expected, string json)[] testCases = - [ - ( - new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc), - "\"2023-10-05T14:30:00.000Z\"" - ), - (null, "null"), - ]; - - foreach (var (expected, json) in testCases) - { - var dateTime = JsonUtils.Deserialize(json); - Assert.That(dateTime, Is.EqualTo(expected)); - } - } - - [Test] - public void ShouldSerializeDictionaryWithDateTimeKey() - { - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - var dict = new Dictionary { { key, "value_a" } }; - var json = JsonUtils.Serialize(dict); - Assert.That(json, Does.Contain("2023-10-05T14:30:00.000Z")); - Assert.That(json, Does.Contain("value_a")); - } - - [Test] - public void ShouldDeserializeDictionaryWithDateTimeKey() - { - var json = """ - { - "2023-10-05T14:30:00.000Z": "value_a" - } - """; - var dict = JsonUtils.Deserialize>(json); - Assert.That(dict, Is.Not.Null); - var key = new DateTime(2023, 10, 5, 14, 30, 0, DateTimeKind.Utc); - Assert.That(dict![key], Is.EqualTo("value_a")); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs deleted file mode 100644 index 969acd620998..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/Json/JsonAccessAttributeTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.Json; - -[TestFixture] -public class JsonAccessAttributeTests -{ - private class MyClass - { - [JsonPropertyName("read_only_prop")] - [JsonAccess(JsonAccessType.ReadOnly)] - public string? ReadOnlyProp { get; set; } - - [JsonPropertyName("write_only_prop")] - [JsonAccess(JsonAccessType.WriteOnly)] - public string? WriteOnlyProp { get; set; } - - [JsonPropertyName("normal_prop")] - public string? NormalProp { get; set; } - - [JsonPropertyName("read_only_nullable_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable? ReadOnlyNullableList { get; set; } - - [JsonPropertyName("read_only_list")] - [JsonAccess(JsonAccessType.ReadOnly)] - public IEnumerable ReadOnlyList { get; set; } = []; - - [JsonPropertyName("write_only_nullable_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable? WriteOnlyNullableList { get; set; } - - [JsonPropertyName("write_only_list")] - [JsonAccess(JsonAccessType.WriteOnly)] - public IEnumerable WriteOnlyList { get; set; } = []; - - [JsonPropertyName("normal_list")] - public IEnumerable NormalList { get; set; } = []; - - [JsonPropertyName("normal_nullable_list")] - public IEnumerable? NullableNormalList { get; set; } - } - - [Test] - public void JsonAccessAttribute_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "write_only_prop": "write", - "normal_prop": "normal_prop", - "read_only_nullable_list": ["item1", "item2"], - "read_only_list": ["item3", "item4"], - "write_only_nullable_list": ["item5", "item6"], - "write_only_list": ["item7", "item8"], - "normal_list": ["normal1", "normal2"], - "normal_nullable_list": ["normal1", "normal2"] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // String properties - Assert.That(obj.ReadOnlyProp, Is.EqualTo("read")); - Assert.That(obj.WriteOnlyProp, Is.Null); - Assert.That(obj.NormalProp, Is.EqualTo("normal_prop")); - - // List properties - read only - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Not.Null); - Assert.That(nullableReadOnlyList, Has.Length.EqualTo(2)); - Assert.That(nullableReadOnlyList![0], Is.EqualTo("item1")); - Assert.That(nullableReadOnlyList![1], Is.EqualTo("item2")); - - var readOnlyList = obj.ReadOnlyList.ToArray(); - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Has.Length.EqualTo(2)); - Assert.That(readOnlyList[0], Is.EqualTo("item3")); - Assert.That(readOnlyList[1], Is.EqualTo("item4")); - - // List properties - write only - Assert.That(obj.WriteOnlyNullableList, Is.Null); - Assert.That(obj.WriteOnlyList, Is.Not.Null); - Assert.That(obj.WriteOnlyList, Is.Empty); - - // Normal list property - var normalList = obj.NormalList.ToArray(); - Assert.That(normalList, Is.Not.Null); - Assert.That(normalList, Has.Length.EqualTo(2)); - Assert.That(normalList[0], Is.EqualTo("normal1")); - Assert.That(normalList[1], Is.EqualTo("normal2")); - }); - - // Set up values for serialization - obj.WriteOnlyProp = "write"; - obj.NormalProp = "new_value"; - obj.WriteOnlyNullableList = new List { "write1", "write2" }; - obj.WriteOnlyList = new List { "write3", "write4" }; - obj.NormalList = new List { "new_normal" }; - obj.NullableNormalList = new List { "new_normal" }; - - var serializedJson = JsonUtils.Serialize(obj); - const string expectedJson = """ - { - "write_only_prop": "write", - "normal_prop": "new_value", - "write_only_nullable_list": [ - "write1", - "write2" - ], - "write_only_list": [ - "write3", - "write4" - ], - "normal_list": [ - "new_normal" - ], - "normal_nullable_list": [ - "new_normal" - ] - } - """; - Assert.That(serializedJson, Is.EqualTo(expectedJson).IgnoreWhiteSpace); - } - - [Test] - public void JsonAccessAttribute_WithNullListsInJson_ShouldWorkAsExpected() - { - const string json = """ - { - "read_only_prop": "read", - "normal_prop": "normal_prop", - "read_only_nullable_list": null, - "read_only_list": [] - } - """; - var obj = JsonUtils.Deserialize(json); - - Assert.Multiple(() => - { - // Read-only nullable list should be null when JSON contains null - var nullableReadOnlyList = obj.ReadOnlyNullableList?.ToArray(); - Assert.That(nullableReadOnlyList, Is.Null); - - // Read-only non-nullable list should never be null, but empty when JSON contains null - var readOnlyList = obj.ReadOnlyList.ToArray(); // This should be initialized to an empty list by default - Assert.That(readOnlyList, Is.Not.Null); - Assert.That(readOnlyList, Is.Empty); - }); - - // Serialize and verify read-only lists are not included - var serializedJson = JsonUtils.Serialize(obj); - Assert.That(serializedJson, Does.Not.Contain("read_only_prop")); - Assert.That(serializedJson, Does.Not.Contain("read_only_nullable_list")); - Assert.That(serializedJson, Does.Not.Contain("read_only_list")); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs deleted file mode 100644 index 493d8e99c329..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringBuilderTests.cs +++ /dev/null @@ -1,658 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class QueryStringBuilderTests -{ - [Test] - public void Build_SimpleParameters() - { - var parameters = new List> - { - new("name", "John Doe"), - new("age", "30"), - new("city", "New York"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo("?name=John%20Doe&age=30&city=New%20York")); - } - - [Test] - public void Build_EmptyList_ReturnsEmptyString() - { - var parameters = new List>(); - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Build_SpecialCharacters() - { - var parameters = new List> - { - new("email", "test@example.com"), - new("url", "https://example.com/path?query=value"), - new("special", "a+b=c&d"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That( - result, - Is.EqualTo( - "?email=test@example.com&url=https://example.com/path?query=value&special=a%2Bb=c%26d" - ) - ); - } - - [Test] - public void Build_UnicodeCharacters() - { - var parameters = new List> { new("greeting", "Hello 世界") }; - - var result = QueryStringBuilder.Build(parameters); - - // Verify the Chinese characters are properly UTF-8 encoded - Assert.That(result, Does.StartWith("?greeting=Hello%20")); - Assert.That(result, Does.Contain("%E4%B8%96%E7%95%8C")); // 世界 - } - - [Test] - public void Build_SessionSettings_DeepObject() - { - // Simulate session settings with nested properties - var sessionSettings = new - { - custom_session_id = "my-custom-session-id", - system_prompt = "You are a helpful assistant", - variables = new Dictionary - { - { "userName", "John" }, - { "userAge", 30 }, - { "isPremium", true }, - }, - }; - - // Build query parameters list - var queryParams = new List> { new("api_key", "test_key_123") }; - - // Add session_settings with prefix using the new overload - queryParams.AddRange( - QueryStringConverter.ToDeepObject("session_settings", sessionSettings) - ); - - var result = QueryStringBuilder.Build(queryParams); - - // Verify the result contains properly formatted deep object notation - // Note: Square brackets are URL-encoded as %5B and %5D - Assert.That(result, Does.StartWith("?api_key=test_key_123")); - Assert.That( - result, - Does.Contain("session_settings%5Bcustom_session_id%5D=my-custom-session-id") - ); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20a%20helpful%20assistant") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BisPremium%5D=true")); - - // Verify it's NOT JSON encoded (no braces or quotes in the original format) - Assert.That(result, Does.Not.Contain("%7B%22")); // Not {" sequence - } - - [Test] - public void Build_ChatApiLikeParameters() - { - // Simulate what ChatApi constructor does - var sessionSettings = new - { - system_prompt = "You are helpful", - variables = new Dictionary { { "name", "Alice" } }, - }; - - var queryParams = new List>(); - - // Simple parameters - var simpleParams = new Dictionary - { - { "access_token", "token123" }, - { "config_id", "config456" }, - { "api_key", "key789" }, - }; - queryParams.AddRange(QueryStringConverter.ToExplodedForm(simpleParams)); - - // Session settings as deep object with prefix - queryParams.AddRange( - QueryStringConverter.ToDeepObject("session_settings", sessionSettings) - ); - - var result = QueryStringBuilder.Build(queryParams); - - // Verify structure (square brackets are URL-encoded) - Assert.That(result, Does.StartWith("?")); - Assert.That(result, Does.Contain("access_token=token123")); - Assert.That(result, Does.Contain("config_id=config456")); - Assert.That(result, Does.Contain("api_key=key789")); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); - } - - [Test] - public void Build_ReservedCharacters_NotEncoded() - { - var parameters = new List> - { - new("path", "some-path"), - new("id", "123-456_789.test~value"), - }; - - var result = QueryStringBuilder.Build(parameters); - - // Safe query characters include RFC 3986 unreserved + sub-delimiters (except & = +) + : @ / - Assert.That(result, Is.EqualTo("?path=some-path&id=123-456_789.test~value")); - } - - [Test] - public void Builder_Add_SimpleParameters() - { - var result = new QueryStringBuilder.Builder() - .Add("name", "John Doe") - .Add("age", 30) - .Add("active", true) - .Build(); - - Assert.That(result, Does.Contain("name=John%20Doe")); - Assert.That(result, Does.Contain("age=30")); - Assert.That(result, Does.Contain("active=true")); - } - - [Test] - public void Builder_Add_NullValuesIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("name", "John") - .Add("middle", null) - .Add("age", 30) - .Build(); - - Assert.That(result, Does.Contain("name=John")); - Assert.That(result, Does.Contain("age=30")); - Assert.That(result, Does.Not.Contain("middle")); - } - - [Test] - public void Builder_AddDeepObject_WithPrefix() - { - var settings = new - { - custom_session_id = "id-123", - system_prompt = "You are helpful", - variables = new { name = "Alice", age = 25 }, - }; - - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddDeepObject("session_settings", settings) - .Build(); - - Assert.That(result, Does.Contain("api_key=key123")); - Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=id-123")); - Assert.That( - result, - Does.Contain("session_settings%5Bsystem_prompt%5D=You%20are%20helpful") - ); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bname%5D=Alice")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5Bage%5D=25")); - } - - [Test] - public void Builder_AddDeepObject_NullIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddDeepObject("settings", null) - .Build(); - - Assert.That(result, Is.EqualTo("?api_key=key123")); - Assert.That(result, Does.Not.Contain("settings")); - } - - [Test] - public void Builder_AddExploded_WithPrefix() - { - var filter = new { status = "active", type = "user" }; - - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddExploded("filter", filter) - .Build(); - - Assert.That(result, Does.Contain("api_key=key123")); - Assert.That(result, Does.Contain("filter%5Bstatus%5D=active")); - Assert.That(result, Does.Contain("filter%5Btype%5D=user")); - } - - [Test] - public void Builder_AddExploded_NullIgnored() - { - var result = new QueryStringBuilder.Builder() - .Add("api_key", "key123") - .AddExploded("filter", null) - .Build(); - - Assert.That(result, Is.EqualTo("?api_key=key123")); - Assert.That(result, Does.Not.Contain("filter")); - } - - [Test] - public void Builder_WithCapacity() - { - // Test that capacity constructor works without errors - var result = new QueryStringBuilder.Builder(capacity: 10) - .Add("param1", "value1") - .Add("param2", "value2") - .Build(); - - Assert.That(result, Does.Contain("param1=value1")); - Assert.That(result, Does.Contain("param2=value2")); - } - - [Test] - public void Builder_ChatApiLikeUsage() - { - // Simulate real usage from ChatApi - var sessionSettings = new - { - custom_session_id = "session-123", - variables = new Dictionary - { - { "userName", "John" }, - { "userAge", 30 }, - }, - }; - - var result = new QueryStringBuilder.Builder(capacity: 16) - .Add("access_token", "token123") - .Add("allow_connection", true) - .Add("config_id", "config456") - .Add("api_key", "key789") - .AddDeepObject("session_settings", sessionSettings) - .Build(); - - Assert.That(result, Does.StartWith("?")); - Assert.That(result, Does.Contain("access_token=token123")); - Assert.That(result, Does.Contain("allow_connection=true")); - Assert.That(result, Does.Contain("config_id=config456")); - Assert.That(result, Does.Contain("api_key=key789")); - Assert.That(result, Does.Contain("session_settings%5Bcustom_session_id%5D=session-123")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserName%5D=John")); - Assert.That(result, Does.Contain("session_settings%5Bvariables%5D%5BuserAge%5D=30")); - } - - [Test] - public void Builder_EmptyBuilder_ReturnsEmptyString() - { - var result = new QueryStringBuilder.Builder().Build(); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Builder_OnlyNullValues_ReturnsEmptyString() - { - var result = new QueryStringBuilder.Builder() - .Add("param1", null) - .Add("param2", null) - .AddDeepObject("settings", null) - .Build(); - - Assert.That(result, Is.EqualTo(string.Empty)); - } - - [Test] - public void Builder_Set_OverridesSingleValue() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Set("foo", "override") - .Build(); - - Assert.That(result, Is.EqualTo("?foo=override")); - } - - [Test] - public void Builder_Set_OverridesMultipleValues() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "value1") - .Add("foo", "value2") - .Set("foo", "override") - .Build(); - - Assert.That(result, Is.EqualTo("?foo=override")); - } - - [Test] - public void Builder_Set_WithArray_CreatesMultipleParameters() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Set("foo", new[] { "value1", "value2" }) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value1&foo=value2")); - } - - [Test] - public void Builder_Set_WithNull_RemovesParameter() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "original") - .Add("bar", "keep") - .Set("foo", null) - .Build(); - - Assert.That(result, Is.EqualTo("?bar=keep")); - } - - [Test] - public void Builder_MergeAdditional_WithSingleValues() - { - var additional = new List> - { - new("foo", "bar"), - new("baz", "qux"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("existing", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("existing=value")); - Assert.That(result, Does.Contain("foo=bar")); - Assert.That(result, Does.Contain("baz=qux")); - } - - [Test] - public void Builder_MergeAdditional_WithDuplicateKeys_CreatesList() - { - var additional = new List> - { - new("foo", "bar1"), - new("foo", "bar2"), - new("baz", "qux"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("existing", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("existing=value")); - Assert.That(result, Does.Contain("foo=bar1")); - Assert.That(result, Does.Contain("foo=bar2")); - Assert.That(result, Does.Contain("baz=qux")); - } - - [Test] - public void Builder_MergeAdditional_OverridesExistingParameters() - { - var additional = new List> { new("foo", "override") }; - - var result = new QueryStringBuilder.Builder() - .Add("foo", "original1") - .Add("foo", "original2") - .Add("bar", "keep") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("bar=keep")); - Assert.That(result, Does.Contain("foo=override")); - Assert.That(result, Does.Not.Contain("original1")); - Assert.That(result, Does.Not.Contain("original2")); - } - - [Test] - public void Builder_MergeAdditional_WithDuplicates_OverridesExisting() - { - var additional = new List> - { - new("foo", "new1"), - new("foo", "new2"), - new("foo", "new3"), - }; - - var result = new QueryStringBuilder.Builder() - .Add("foo", "original1") - .Add("foo", "original2") - .Add("bar", "keep") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Does.Contain("bar=keep")); - Assert.That(result, Does.Contain("foo=new1")); - Assert.That(result, Does.Contain("foo=new2")); - Assert.That(result, Does.Contain("foo=new3")); - Assert.That(result, Does.Not.Contain("original1")); - Assert.That(result, Does.Not.Contain("original2")); - } - - [Test] - public void Builder_MergeAdditional_WithNull_NoOp() - { - var result = new QueryStringBuilder.Builder() - .Add("foo", "value") - .MergeAdditional(null) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value")); - } - - [Test] - public void Builder_MergeAdditional_WithEmptyList_NoOp() - { - var additional = new List>(); - - var result = new QueryStringBuilder.Builder() - .Add("foo", "value") - .MergeAdditional(additional) - .Build(); - - Assert.That(result, Is.EqualTo("?foo=value")); - } - - [Test] - public void Builder_MergeAdditional_RealWorldScenario() - { - // SDK generates foo=foo1&foo=foo2 - var builder = new QueryStringBuilder.Builder() - .Add("foo", "foo1") - .Add("foo", "foo2") - .Add("bar", "baz"); - - // User provides foo=override in AdditionalQueryParameters - var additional = new List> { new("foo", "override") }; - - var result = builder.MergeAdditional(additional).Build(); - - // Result should be foo=override&bar=baz (user overrides SDK) - Assert.That(result, Does.Contain("bar=baz")); - Assert.That(result, Does.Contain("foo=override")); - Assert.That(result, Does.Not.Contain("foo1")); - Assert.That(result, Does.Not.Contain("foo2")); - } - - [Test] - public void Builder_MergeAdditional_UserProvidesMultipleValues() - { - // SDK generates no foo parameter - var builder = new QueryStringBuilder.Builder().Add("bar", "baz"); - - // User provides foo=bar1&foo=bar2 in AdditionalQueryParameters - var additional = new List> - { - new("foo", "bar1"), - new("foo", "bar2"), - }; - - var result = builder.MergeAdditional(additional).Build(); - - // Result should be bar=baz&foo=bar1&foo=bar2 - Assert.That(result, Does.Contain("bar=baz")); - Assert.That(result, Does.Contain("foo=bar1")); - Assert.That(result, Does.Contain("foo=bar2")); - } - - [Test] - public void Builder_Add_WithCollection_CreatesMultipleParameters() - { - var tags = new[] { "tag1", "tag2", "tag3" }; - var result = new QueryStringBuilder.Builder().Add("tag", tags).Build(); - - Assert.That(result, Does.Contain("tag=tag1")); - Assert.That(result, Does.Contain("tag=tag2")); - Assert.That(result, Does.Contain("tag=tag3")); - } - - [Test] - public void Builder_Add_WithList_CreatesMultipleParameters() - { - var ids = new List { 1, 2, 3 }; - var result = new QueryStringBuilder.Builder().Add("id", ids).Build(); - - Assert.That(result, Does.Contain("id=1")); - Assert.That(result, Does.Contain("id=2")); - Assert.That(result, Does.Contain("id=3")); - } - - [Test] - public void Builder_Set_WithCollection_ReplacesAllPreviousValues() - { - var result = new QueryStringBuilder.Builder() - .Add("id", 1) - .Add("id", 2) - .Set("id", new[] { 10, 20, 30 }) - .Build(); - - Assert.That(result, Does.Contain("id=10")); - Assert.That(result, Does.Contain("id=20")); - Assert.That(result, Does.Contain("id=30")); - // Check that old values are not present (use word boundaries to avoid false positives with id=10) - Assert.That(result, Does.Not.Contain("id=1&")); - Assert.That(result, Does.Not.Contain("id=2&")); - Assert.That(result, Does.Not.Contain("id=1?")); - Assert.That(result, Does.Not.Contain("id=2?")); - Assert.That(result, Does.Not.EndWith("id=1")); - Assert.That(result, Does.Not.EndWith("id=2")); - } - - [Test] - public void EncodePathSegment_UnreservedChars_NotEncoded() - { - var result = QueryStringBuilder.EncodePathSegment("hello-world_test.value~123"); - Assert.That(result, Is.EqualTo("hello-world_test.value~123")); - } - - [Test] - public void EncodePathSegment_SubDelimiters_NotEncoded() - { - // All sub-delimiters are safe in path segments per RFC 3986 - var result = QueryStringBuilder.EncodePathSegment("a!b$c&d'e(f)g*h+i,j;k=l"); - Assert.That(result, Is.EqualTo("a!b$c&d'e(f)g*h+i,j;k=l")); - } - - [Test] - public void EncodePathSegment_ColonAndAt_NotEncoded() - { - var result = QueryStringBuilder.EncodePathSegment("user@host:8080"); - Assert.That(result, Is.EqualTo("user@host:8080")); - } - - [Test] - public void EncodePathSegment_SlashAndQuestion_Encoded() - { - // "/" and "?" are NOT part of pchar, so they must be encoded in path segments - var result = QueryStringBuilder.EncodePathSegment("path/with?query"); - Assert.That(result, Is.EqualTo("path%2Fwith%3Fquery")); - } - - [Test] - public void EncodePathSegment_Space_Encoded() - { - var result = QueryStringBuilder.EncodePathSegment("hello world"); - Assert.That(result, Is.EqualTo("hello%20world")); - } - - [Test] - public void EncodePathSegment_EmptyAndNull() - { - Assert.That(QueryStringBuilder.EncodePathSegment(""), Is.EqualTo("")); - Assert.That(QueryStringBuilder.EncodePathSegment(null!), Is.Null); - } - - [Test] - public void Build_QueryKeyVsValue_DifferentEncoding() - { - // "=" is safe in query values but NOT in query keys - var parameters = new List> - { - new("key=with=equals", "value=with=equals"), - }; - - var result = QueryStringBuilder.Build(parameters); - - // Key: "=" must be encoded - // Value: "=" is safe (part of query value safe chars) - Assert.That(result, Is.EqualTo("?key%3Dwith%3Dequals=value=with=equals")); - } - - [Test] - public void Build_QueryValue_QuestionMarkNotEncoded() - { - // "?" is safe in both query keys and query values per RFC 3986 - var parameters = new List> { new("q?key", "is this?") }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo("?q?key=is%20this?")); - } - - [Test] - public void Build_QueryKey_PlusEncoded() - { - // "+" must be encoded in both query keys and query values - var parameters = new List> { new("a+b", "c+d") }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Is.EqualTo("?a%2Bb=c%2Bd")); - } - - [Test] - public void Build_ODataFilter_DollarPreserved() - { - // "$" is safe in query keys (sub-delimiter), verifies OData-style parameters work - var parameters = new List> - { - new("$filter", "status eq 'active'"), - new("$top", "10"), - }; - - var result = QueryStringBuilder.Build(parameters); - - Assert.That(result, Does.Contain("$filter=status%20eq%20'active'")); - Assert.That(result, Does.Contain("$top=10")); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs deleted file mode 100644 index 06293e022863..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/QueryStringConverterTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class QueryStringConverterTests -{ - [Test] - public void ToQueryStringCollection_Form() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToForm(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates]", "39.78172,-89.65015"), - new("Tags", "Developer,Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_ExplodedForm() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToExplodedForm(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates]", "39.78172"), - new("Address[Coordinates]", "-89.65015"), - new("Tags", "Developer"), - new("Tags", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_DeepObject() - { - var obj = new - { - Name = "John", - Age = 30, - Address = new - { - Street = "123 Main St", - City = "Anytown", - Coordinates = new[] { 39.781721f, -89.650148f }, - }, - Tags = new[] { "Developer", "Blogger" }, - }; - var result = QueryStringConverter.ToDeepObject(obj); - var expected = new List> - { - new("Name", "John"), - new("Age", "30"), - new("Address[Street]", "123 Main St"), - new("Address[City]", "Anytown"), - new("Address[Coordinates][0]", "39.78172"), - new("Address[Coordinates][1]", "-89.65015"), - new("Tags[0]", "Developer"), - new("Tags[1]", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_OnString_ThrowsException() - { - var exception = Assert.Throws(() => - QueryStringConverter.ToForm("invalid") - ); - Assert.That( - exception.Message, - Is.EqualTo( - "Only objects can be converted to query string collections. Given type is String." - ) - ); - } - - [Test] - public void ToQueryStringCollection_OnArray_ThrowsException() - { - var exception = Assert.Throws(() => - QueryStringConverter.ToForm(Array.Empty()) - ); - Assert.That( - exception.Message, - Is.EqualTo( - "Only objects can be converted to query string collections. Given type is Array." - ) - ); - } - - [Test] - public void ToQueryStringCollection_DeepObject_WithPrefix() - { - var obj = new - { - custom_session_id = "my-id", - system_prompt = "You are helpful", - variables = new { name = "Alice", age = 25 }, - }; - var result = QueryStringConverter.ToDeepObject("session_settings", obj); - var expected = new List> - { - new("session_settings[custom_session_id]", "my-id"), - new("session_settings[system_prompt]", "You are helpful"), - new("session_settings[variables][name]", "Alice"), - new("session_settings[variables][age]", "25"), - }; - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void ToQueryStringCollection_ExplodedForm_WithPrefix() - { - var obj = new { Name = "John", Tags = new[] { "Developer", "Blogger" } }; - var result = QueryStringConverter.ToExplodedForm("user", obj); - var expected = new List> - { - new("user[Name]", "John"), - new("user[Tags]", "Developer"), - new("user[Tags]", "Blogger"), - }; - Assert.That(result, Is.EqualTo(expected)); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs deleted file mode 100644 index 39ac12fc18b2..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/MultipartFormTests.cs +++ /dev/null @@ -1,1120 +0,0 @@ -using global::System.Net.Http; -using global::System.Text; -using global::System.Text.Json.Serialization; -using NUnit.Framework; -using SeedApi.Core; -using SystemTask = global::System.Threading.Tasks.Task; - -namespace SeedApi.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class MultipartFormTests -{ - private static SimpleObject _simpleObject = new(); - - private static string _simpleFormEncoded = - "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data,2023-10-01,12:00:00,01:00:00,1a1bb98f-47c6-407b-9481-78476affe52a,true,42,A"; - - private static string _simpleExplodedFormEncoded = - "meta=data&Date=2023-10-01&Time=12:00:00&Duration=01:00:00&Id=1a1bb98f-47c6-407b-9481-78476affe52a&IsActive=true&Count=42&Initial=A&Values=data&Values=2023-10-01&Values=12:00:00&Values=01:00:00&Values=1a1bb98f-47c6-407b-9481-78476affe52a&Values=true&Values=42&Values=A"; - - private static ComplexObject _complexObject = new(); - - private static string _complexJson = """ - { - "meta": "data", - "Nested": { - "foo": "value" - }, - "NestedDictionary": { - "key": { - "foo": "value" - } - }, - "ListOfObjects": [ - { - "foo": "value" - }, - { - "foo": "value2" - } - ], - "Date": "2023-10-01", - "Time": "12:00:00", - "Duration": "01:00:00", - "Id": "1a1bb98f-47c6-407b-9481-78476affe52a", - "IsActive": true, - "Count": 42, - "Initial": "A" - } - """; - - [Test] - public async SystemTask ShouldAddStringPart() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, partInput]); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddStringPart() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", null); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithNullsInList() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, null, partInput]); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringPart_WithContentType() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput, "text/xml"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringPart_WithContentTypeAndCharset() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringPart("string", partInput, "text/xml; charset=utf-8"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=string - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithContentType() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts("strings", [partInput, partInput], "text/xml"); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/xml - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddStringParts_WithContentTypeAndCharset() - { - const string partInput = "string content"; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddStringParts( - "strings", - [partInput, partInput], - "text/xml; charset=utf-8" - ); - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary} - Content-Type: text/xml; charset=utf-8 - Content-Disposition: form-data; name=strings - - {partInput} - --{boundary}-- - """; - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFileName() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithoutFileName() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", partInput); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithContentType() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter - { - Stream = partInput, - FileName = "test.txt", - ContentType = "text/plain", - }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithContentTypeAndCharset() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter - { - Stream = partInput, - FileName = "test.txt", - ContentType = "text/plain; charset=utf-8", - }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "ignored-fallback-content-type"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain; charset=utf-8 - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFallbackContentType() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "text/plain"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameter_WithFallbackContentTypeAndCharset() - { - var (partInput, partExpectedString) = GetFileParameterTestData(); - var file = new FileParameter { Stream = partInput, FileName = "test.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", file, "text/plain; charset=utf-8"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: text/plain; charset=utf-8 - Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt - - {partExpectedString} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameters() - { - var (partInput1, partExpectedString1) = GetFileParameterTestData(); - var (partInput2, partExpectedString2) = GetFileParameterTestData(); - var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; - var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterParts("file", [file1, file2]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt - - {partExpectedString1} - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt - - {partExpectedString2} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFileParameters_WithNullsInList() - { - var (partInput1, partExpectedString1) = GetFileParameterTestData(); - var (partInput2, partExpectedString2) = GetFileParameterTestData(); - var file1 = new FileParameter { Stream = partInput1, FileName = "test1.txt" }; - var file2 = new FileParameter { Stream = partInput2, FileName = "test2.txt" }; - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterParts("file", [file1, null, file2]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test1.txt; filename*=utf-8''test1.txt - - {partExpectedString1} - --{boundary} - Content-Type: application/octet-stream - Content-Disposition: form-data; name=file; filename=test2.txt; filename*=utf-8''test2.txt - - {partExpectedString2} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddFileParameter() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFileParameterPart("file", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonPart_WithComplexObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonPart("object", _complexObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=object - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonPart_WithComplexObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [_complexObject, _complexObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask GivenNull_ShouldNotAddJsonPart() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonPart("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [_complexObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/json - Content-Disposition: form-data; name=objects - - {_complexJson} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddJsonParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddJsonParts("objects", [new { }], "application/json-patch+json"); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $$""" - --{{boundary}} - Content-Type: application/json-patch+json - Content-Disposition: form-data; name=objects - - {} - --{{boundary}}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithSimpleObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart("object", _simpleObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=object - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithSimpleObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, _simpleObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddFormEncodedParts_WithNull() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddFormEncodedParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts("objects", [_simpleObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedPart_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedPart_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddFormEncodedParts_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObject() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart("object", _simpleObject); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=object - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithSimpleObjectList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, _simpleObject]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNull() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart("object", null); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldNotAddExplodedFormEncodedParts_WithNullsInList() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts("objects", [_simpleObject, null]); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - {EscapeFormEncodedString(_simpleExplodedFormEncoded)} - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedPart_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedPart( - "objects", - new { foo = "bar" }, - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentType() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - [Test] - public async SystemTask ShouldAddExplodedFormEncodedParts_WithContentTypeAndCharset() - { - var multipartFormRequest = CreateMultipartFormRequest(); - multipartFormRequest.AddExplodedFormEncodedParts( - "objects", - [new { foo = "bar" }], - "application/x-www-form-urlencoded; charset=utf-8" - ); - - var httpContent = multipartFormRequest.CreateContent(); - Assert.That(httpContent, Is.InstanceOf()); - var multipartContent = (MultipartFormDataContent)httpContent; - - var boundary = GetBoundary(multipartContent); - var expected = $""" - --{boundary} - Content-Type: application/x-www-form-urlencoded; charset=utf-8 - Content-Disposition: form-data; name=objects - - foo=bar - --{boundary}-- - """; - - var actual = await multipartContent.ReadAsStringAsync(); - Assert.That(actual, Is.EqualTo(expected).IgnoreWhiteSpace); - } - - private static string EscapeFormEncodedString(string input) - { - return string.Join( - "&", - input - .Split('&') - .Select(x => x.Split('=')) - .Select(x => $"{Uri.EscapeDataString(x[0])}={Uri.EscapeDataString(x[1])}") - ); - } - - private static string GetBoundary(MultipartFormDataContent content) - { - return content - .Headers.ContentType?.Parameters.Single(p => - p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) - ) - .Value?.Trim('"') ?? throw new global::System.Exception("Boundary not found"); - } - - private static SeedApi.Core.MultipartFormRequest CreateMultipartFormRequest() - { - return new SeedApi.Core.MultipartFormRequest - { - BaseUrl = "https://localhost", - Method = HttpMethod.Post, - Path = "", - }; - } - - private static (Stream partInput, string partExpectedString) GetFileParameterTestData() - { - const string partExpectedString = "file content"; - var partInput = new MemoryStream(Encoding.Default.GetBytes(partExpectedString)); - return (partInput, partExpectedString); - } - - private class SimpleObject - { - [JsonPropertyName("meta")] - public string Meta { get; set; } = "data"; - public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); - public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); - public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); - public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); - public bool IsActive { get; set; } = true; - public int Count { get; set; } = 42; - public char Initial { get; set; } = 'A'; - public IEnumerable Values { get; set; } = - [ - "data", - DateOnly.Parse("2023-10-01"), - TimeOnly.Parse("12:00:00"), - TimeSpan.FromHours(1), - Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"), - true, - 42, - 'A', - ]; - } - - private class ComplexObject - { - [JsonPropertyName("meta")] - public string Meta { get; set; } = "data"; - - public object Nested { get; set; } = new { foo = "value" }; - - public Dictionary NestedDictionary { get; set; } = - new() { { "key", new { foo = "value" } } }; - - public IEnumerable ListOfObjects { get; set; } = - new List { new { foo = "value" }, new { foo = "value2" } }; - - public DateOnly Date { get; set; } = DateOnly.Parse("2023-10-01"); - public TimeOnly Time { get; set; } = TimeOnly.Parse("12:00:00"); - public TimeSpan Duration { get; set; } = TimeSpan.FromHours(1); - public Guid Id { get; set; } = Guid.Parse("1a1bb98f-47c6-407b-9481-78476affe52a"); - public bool IsActive { get; set; } = true; - public int Count { get; set; } = 42; - public char Initial { get; set; } = 'A'; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs deleted file mode 100644 index f4edf9ef52a6..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/QueryParameterTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class QueryParameterTests -{ - [Test] - public void QueryParameters_BasicParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .Add("baz", "qux") - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar&baz=qux")); - } - - [Test] - public void QueryParameters_SpecialCharacterEscaping() - { - var queryString = new QueryStringBuilder.Builder() - .Add("email", "bob+test@example.com") - .Add("%Complete", "100") - .Add("space test", "hello world") - .Build(); - - Assert.That(queryString, Does.Contain("email=bob%2Btest@example.com")); - Assert.That(queryString, Does.Contain("%25Complete=100")); - Assert.That(queryString, Does.Contain("space%20test=hello%20world")); - } - - [Test] - public void QueryParameters_MergeAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("sdk", "param") - .MergeAdditional(new List> { new("user", "value") }) - .Build(); - - Assert.That(queryString, Does.Contain("sdk=param")); - Assert.That(queryString, Does.Contain("user=value")); - } - - [Test] - public void QueryParameters_AdditionalOverridesSdk() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "sdk_value") - .MergeAdditional(new List> { new("foo", "user_override") }) - .Build(); - - Assert.That(queryString, Does.Contain("foo=user_override")); - Assert.That(queryString, Does.Not.Contain("sdk_value")); - } - - [Test] - public void QueryParameters_AdditionalMultipleValues() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "sdk_value") - .MergeAdditional( - new List> { new("foo", "user1"), new("foo", "user2") } - ) - .Build(); - - Assert.That(queryString, Does.Contain("foo=user1")); - Assert.That(queryString, Does.Contain("foo=user2")); - Assert.That(queryString, Does.Not.Contain("sdk_value")); - } - - [Test] - public void QueryParameters_OnlyAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .MergeAdditional( - new List> { new("foo", "bar"), new("baz", "qux") } - ) - .Build(); - - Assert.That(queryString, Does.Contain("foo=bar")); - Assert.That(queryString, Does.Contain("baz=qux")); - } - - [Test] - public void QueryParameters_EmptyAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .MergeAdditional(new List>()) - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar")); - } - - [Test] - public void QueryParameters_NullAdditionalParameters() - { - var queryString = new QueryStringBuilder.Builder() - .Add("foo", "bar") - .MergeAdditional(null) - .Build(); - - Assert.That(queryString, Is.EqualTo("?foo=bar")); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs deleted file mode 100644 index 22e8103847cb..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/RawClientTests/RetriesTests.cs +++ /dev/null @@ -1,406 +0,0 @@ -using global::System.Net.Http; -using global::System.Text.Json; -using NUnit.Framework; -using SeedApi.Core; -using WireMock.Server; -using SystemTask = global::System.Threading.Tasks.Task; -using WireMockRequest = WireMock.RequestBuilders.Request; -using WireMockResponse = WireMock.ResponseBuilders.Response; - -namespace SeedApi.Test.Core.RawClientTests; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class RetriesTests -{ - private const int MaxRetries = 3; - private WireMockServer _server; - private HttpClient _httpClient; - private RawClient _rawClient; - private string _baseUrl; - - [SetUp] - public void SetUp() - { - _server = WireMockServer.Start(); - _baseUrl = _server.Url ?? ""; - _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) }; - _rawClient = new RawClient( - new ClientOptions { HttpClient = _httpClient, MaxRetries = MaxRetries } - ) - { - BaseRetryDelay = 0, - }; - } - - [Test] - [TestCase(408)] - [TestCase(429)] - [TestCase(500)] - [TestCase(504)] - public async SystemTask SendRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode) - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WhenStateIs("Server Error") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - - Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); - } - } - - [Test] - [TestCase(400)] - [TestCase(409)] - public async SystemTask SendRequestAsync_ShouldRetry_OnNonRetryableStatusCodes(int statusCode) - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure")); - - var request = new JsonRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - Body = new { }, - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(statusCode)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldNotRetry_WithStreamRequest() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - - var request = new StreamRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - Body = new MemoryStream(), - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(429)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldNotRetry_WithMultiPartFormRequest_WithStream() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429).WithBody("Failure")); - - var request = new SeedApi.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddFileParameterPart("file", new MemoryStream()); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(429)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Failure")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(1)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRetry_WithMultiPartFormRequest_WithoutStream() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WillSetStateTo("Server Error") - .RespondWith(WireMockResponse.Create().WithStatusCode(429)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WhenStateIs("Server Error") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(429)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("Retry") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new SeedApi.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddJsonPart("object", new { }); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(MaxRetries)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithSecondsValue() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfter") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse.Create().WithStatusCode(429).WithHeader("Retry-After", "1") - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfter") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectRetryAfterHeader_WithHttpDateValue() - { - var retryAfterDate = DateTimeOffset.UtcNow.AddSeconds(1).ToString("R"); - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfterDate") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse - .Create() - .WithStatusCode(429) - .WithHeader("Retry-After", retryAfterDate) - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RetryAfterDate") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldRespectXRateLimitResetHeader() - { - var resetTime = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeSeconds().ToString(); - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RateLimitReset") - .WillSetStateTo("Success") - .RespondWith( - WireMockResponse - .Create() - .WithStatusCode(429) - .WithHeader("X-RateLimit-Reset", resetTime) - ); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) - .InScenario("RateLimitReset") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new EmptyRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Get, - Path = "/test", - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - Assert.Multiple(() => - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - }); - } - - [Test] - public async SystemTask SendRequestAsync_ShouldPreserveJsonBody_OnRetry() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryWithBody") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(500)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryWithBody") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new JsonRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - Body = new { key = "value" }, - }; - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - - // Verify the retried request preserved the JSON body (compare parsed to ignore formatting differences) - var retriedEntry = _server.LogEntries.ElementAt(1); - using var actualJson = JsonDocument.Parse(retriedEntry.RequestMessage.Body!); - Assert.That(actualJson.RootElement.GetProperty("key").GetString(), Is.EqualTo("value")); - } - } - - [Test] - public async SystemTask SendRequestAsync_ShouldPreserveMultipartBody_OnRetry() - { - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryMultipart") - .WillSetStateTo("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(500)); - - _server - .Given(WireMockRequest.Create().WithPath("/test").UsingPost()) - .InScenario("RetryMultipart") - .WhenStateIs("Success") - .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); - - var request = new SeedApi.Core.MultipartFormRequest - { - BaseUrl = _baseUrl, - Method = HttpMethod.Post, - Path = "/test", - }; - request.AddJsonPart("object", new { key = "value" }); - - var response = await _rawClient.SendRequestAsync(request); - Assert.That(response.StatusCode, Is.EqualTo(200)); - - var content = await response.Raw.Content.ReadAsStringAsync(); - using (Assert.EnterMultipleScope()) - { - Assert.That(content, Is.EqualTo("Success")); - Assert.That(_server.LogEntries, Has.Count.EqualTo(2)); - - // Verify the retried request preserved the multipart body (check key/value presence to ignore formatting differences) - var retriedEntry = _server.LogEntries.ElementAt(1); - Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"key\"")); - Assert.That(retriedEntry.RequestMessage.Body, Does.Contain("\"value\"")); - } - } - - [TearDown] - public void TearDown() - { - _server.Dispose(); - _httpClient.Dispose(); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs deleted file mode 100644 index 27337ed343e8..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Core/WithRawResponseTests.cs +++ /dev/null @@ -1,269 +0,0 @@ -using global::System.Net; -using global::System.Net.Http.Headers; -using NUnit.Framework; -using SeedApi; -using SeedApi.Core; - -namespace SeedApi.Test.Core; - -[TestFixture] -public class WithRawResponseTests -{ - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_DirectAwait_ReturnsData() - { - // Arrange - var expectedData = "test-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - var result = await task; - - // Assert - Assert.That(result, Is.EqualTo(expectedData)); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_WithRawResponse_ReturnsDataAndMetadata() - { - // Arrange - var expectedData = "test-data"; - var expectedStatusCode = HttpStatusCode.Created; - var task = CreateWithRawResponseTask(expectedData, expectedStatusCode); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.Data, Is.EqualTo(expectedData)); - Assert.That(result.RawResponse.StatusCode, Is.EqualTo(expectedStatusCode)); - Assert.That(result.RawResponse.Url, Is.Not.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_CaseInsensitive() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Request-Id", "12345"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act & Assert - Assert.That(headers.TryGetValue("X-Request-Id", out var value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - - Assert.That(headers.TryGetValue("x-request-id", out value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - - Assert.That(headers.TryGetValue("X-REQUEST-ID", out value), Is.True); - Assert.That(value, Is.EqualTo("12345")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_ReturnsMultipleValues() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("Set-Cookie", new[] { "cookie1=value1", "cookie2=value2" }); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValues("Set-Cookie", out var values); - - // Assert - Assert.That(success, Is.True); - Assert.That(values, Is.Not.Null); - Assert.That(values!.Count(), Is.EqualTo(2)); - Assert.That(values, Does.Contain("cookie1=value1")); - Assert.That(values, Does.Contain("cookie2=value2")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_ContentType_ReturnsValue() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Content = new StringContent( - "{}", - global::System.Text.Encoding.UTF8, - "application/json" - ); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var contentType = headers.ContentType; - - // Assert - Assert.That(contentType, Is.Not.Null); - Assert.That(contentType, Does.Contain("application/json")); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_ContentLength_ReturnsValue() - { - // Arrange - var content = "test content"; - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Content = new StringContent(content); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var contentLength = headers.ContentLength; - - // Assert - Assert.That(contentLength, Is.Not.Null); - Assert.That(contentLength, Is.GreaterThan(0)); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_Contains_ReturnsTrueForExistingHeader() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Custom-Header", "value"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act & Assert - Assert.That(headers.Contains("X-Custom-Header"), Is.True); - Assert.That(headers.Contains("x-custom-header"), Is.True); - Assert.That(headers.Contains("NonExistent"), Is.False); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_Enumeration_IncludesAllHeaders() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - response.Headers.Add("X-Header-1", "value1"); - response.Headers.Add("X-Header-2", "value2"); - response.Content = new StringContent("test"); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var allHeaders = headers.ToList(); - - // Assert - Assert.That(allHeaders.Count, Is.GreaterThan(0)); - Assert.That(allHeaders.Any(h => h.Name == "X-Header-1"), Is.True); - Assert.That(allHeaders.Any(h => h.Name == "X-Header-2"), Is.True); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_ErrorStatusCode_StillReturnsMetadata() - { - // Arrange - var expectedData = "error-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.BadRequest); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.Data, Is.EqualTo(expectedData)); - Assert.That(result.RawResponse.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_Url_IsPreserved() - { - // Arrange - var expectedUrl = new Uri("https://api.example.com/users/123"); - var task = CreateWithRawResponseTask("data", HttpStatusCode.OK, expectedUrl); - - // Act - var result = await task.WithRawResponse(); - - // Assert - Assert.That(result.RawResponse.Url, Is.EqualTo(expectedUrl)); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValue_NonExistentHeader_ReturnsFalse() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValue("X-NonExistent", out var value); - - // Assert - Assert.That(success, Is.False); - Assert.That(value, Is.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task ResponseHeaders_TryGetValues_NonExistentHeader_ReturnsFalse() - { - // Arrange - using var response = CreateHttpResponse(HttpStatusCode.OK); - var headers = ResponseHeaders.FromHttpResponseMessage(response); - - // Act - var success = headers.TryGetValues("X-NonExistent", out var values); - - // Assert - Assert.That(success, Is.False); - Assert.That(values, Is.Null); - } - - [Test] - public async global::System.Threading.Tasks.Task WithRawResponseTask_ImplicitConversion_ToTask() - { - // Arrange - var expectedData = "test-data"; - var task = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - implicitly convert to Task - global::System.Threading.Tasks.Task regularTask = task; - var result = await regularTask; - - // Assert - Assert.That(result, Is.EqualTo(expectedData)); - } - - [Test] - public void WithRawResponseTask_ImplicitConversion_AssignToTaskVariable() - { - // Arrange - var expectedData = "test-data"; - var wrappedTask = CreateWithRawResponseTask(expectedData, HttpStatusCode.OK); - - // Act - assign to Task variable - global::System.Threading.Tasks.Task regularTask = wrappedTask; - - // Assert - Assert.That(regularTask, Is.Not.Null); - Assert.That(regularTask, Is.InstanceOf>()); - } - - // Helper methods - - private static WithRawResponseTask CreateWithRawResponseTask( - T data, - HttpStatusCode statusCode, - Uri? url = null - ) - { - url ??= new Uri("https://api.example.com/test"); - using var httpResponse = CreateHttpResponse(statusCode); - httpResponse.RequestMessage = new HttpRequestMessage(HttpMethod.Get, url); - - var rawResponse = new RawResponse - { - StatusCode = statusCode, - Url = url, - Headers = ResponseHeaders.FromHttpResponseMessage(httpResponse), - }; - - var withRawResponse = new WithRawResponse { Data = data, RawResponse = rawResponse }; - - var task = global::System.Threading.Tasks.Task.FromResult(withRawResponse); - return new WithRawResponseTask(task); - } - - private static HttpResponseMessage CreateHttpResponse(HttpStatusCode statusCode) - { - return new HttpResponseMessage(statusCode) { Content = new StringContent("") }; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props b/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props deleted file mode 100644 index aac9b5020d80..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.Custom.props +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj deleted file mode 100644 index a4dfe85227f6..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/SeedApi.Test.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - net9.0 - 12 - enable - enable - false - true - true - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs deleted file mode 100644 index 18aaa67904d8..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/TestClient.cs +++ /dev/null @@ -1,6 +0,0 @@ -using NUnit.Framework; - -namespace SeedApi.Test; - -[TestFixture] -public class TestClient; diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs deleted file mode 100644 index 3f4ed8503ec1..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/BaseMockServerTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using NUnit.Framework; -using SeedApi; -using WireMock.Logging; -using WireMock.Server; -using WireMock.Settings; - -namespace SeedApi.Test.Unit.MockServer; - -public class BaseMockServerTest -{ - protected WireMockServer Server { get; set; } = null!; - - protected SeedApiClient Client { get; set; } = null!; - - protected RequestOptions RequestOptions { get; set; } = new(); - - [OneTimeSetUp] - public void GlobalSetup() - { - // Start the WireMock server - Server = WireMockServer.Start( - new WireMockServerSettings { Logger = new WireMockConsoleLogger() } - ); - - // Initialize the Client - Client = new SeedApiClient( - clientOptions: new ClientOptions { BaseUrl = Server.Urls[0], MaxRetries = 0 } - ); - } - - [OneTimeTearDown] - public void GlobalTeardown() - { - Server.Stop(); - Server.Dispose(); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs deleted file mode 100644 index 053c0fd711a1..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/CreateRuleTest.cs +++ /dev/null @@ -1,92 +0,0 @@ -using NUnit.Framework; -using SeedApi; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class CreateRuleTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string requestJson = """ - { - "name": "name", - "executionContext": "prod" - } - """; - - const string mockResponse = """ - { - "id": "id", - "name": "name", - "status": "active", - "executionContext": "prod" - } - """; - - Server - .Given( - WireMock - .RequestBuilders.Request.Create() - .WithPath("/rules") - .WithHeader("Content-Type", "application/json") - .UsingPost() - .WithBodyAsJson(requestJson) - ) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } - ); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string requestJson = """ - { - "name": "name", - "executionContext": "prod" - } - """; - - const string mockResponse = """ - { - "id": "id", - "name": "name", - "status": "active", - "executionContext": "prod" - } - """; - - Server - .Given( - WireMock - .RequestBuilders.Request.Create() - .WithPath("/rules") - .WithHeader("Content-Type", "application/json") - .UsingPost() - .WithBodyAsJson(requestJson) - ) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.CreateRuleAsync( - new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } - ); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs deleted file mode 100644 index 6b0e6c1e74ac..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetEntityTest.cs +++ /dev/null @@ -1,59 +0,0 @@ -using NUnit.Framework; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class GetEntityTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "status": "active", - "id": "id", - "name": "name", - "summary": "summary" - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetEntityAsync(); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "name": "name", - "summary": "summary", - "id": "id", - "status": "active" - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/entities").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetEntityAsync(); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs deleted file mode 100644 index c43f55c6dfcd..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/GetOrganizationTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -using NUnit.Framework; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class GetOrganizationTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "name": "name", - "id": "id", - "metadata": { - "region": "region", - "tier": "tier" - } - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetOrganizationAsync(); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "metadata": { - "region": "region", - "tier": "tier" - }, - "id": "id", - "name": "name" - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/organizations").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.GetOrganizationAsync(); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs deleted file mode 100644 index 2fd3294e9d26..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/ListUsersTest.cs +++ /dev/null @@ -1,75 +0,0 @@ -using NUnit.Framework; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class ListUsersTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "results": [ - { - "id": "id", - "email": "email" - }, - { - "id": "id", - "email": "email" - } - ], - "paging": { - "next": "next", - "previous": "previous" - } - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.ListUsersAsync(); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "paging": { - "next": "next", - "previous": "previous" - }, - "results": [ - { - "id": "id", - "email": "email" - } - ] - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/users").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.ListUsersAsync(); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs deleted file mode 100644 index 54703790876e..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Unit/MockServer/SearchRuleTypesTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework; -using SeedApi; -using SeedApi.Test.Utils; - -namespace SeedApi.Test.Unit.MockServer; - -[TestFixture] -[Parallelizable(ParallelScope.Self)] -public class SearchRuleTypesTest : BaseMockServerTest -{ - [NUnit.Framework.Test] - public async Task MockServerTest_1() - { - const string mockResponse = """ - { - "results": [ - { - "id": "id", - "name": "name", - "description": "description" - }, - { - "id": "id", - "name": "name", - "description": "description" - } - ], - "paging": { - "next": "next", - "previous": "previous" - } - } - """; - - Server - .Given( - WireMock - .RequestBuilders.Request.Create() - .WithPath("/rule-types") - .WithParam("query", "query") - .UsingGet() - ) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.SearchRuleTypesAsync( - new SearchRuleTypesRequest { Query = "query" } - ); - JsonAssert.AreEqual(response, mockResponse); - } - - [NUnit.Framework.Test] - public async Task MockServerTest_2() - { - const string mockResponse = """ - { - "paging": { - "next": "next", - "previous": "previous" - }, - "results": [ - { - "id": "id", - "name": "name", - "description": "description" - } - ] - } - """; - - Server - .Given(WireMock.RequestBuilders.Request.Create().WithPath("/rule-types").UsingGet()) - .RespondWith( - WireMock - .ResponseBuilders.Response.Create() - .WithStatusCode(200) - .WithBody(mockResponse) - ); - - var response = await Client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); - JsonAssert.AreEqual(response, mockResponse); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs deleted file mode 100644 index 3ac7e5310f95..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/AdditionalPropertiesComparer.cs +++ /dev/null @@ -1,219 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; -using SeedApi; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle AdditionalProperties values. -/// -public static class AdditionalPropertiesComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle AdditionalProperties instances by comparing their - /// serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingAdditionalPropertiesComparer(this EqualConstraint constraint) - { - constraint.Using( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - /// - /// Modifies the EqualConstraint to handle Dictionary<string, object?> values by comparing - /// their serialized JSON representations. This handles the type mismatch between native C# types - /// and JsonElement values that occur when comparing manually constructed objects with - /// deserialized objects. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingObjectDictionaryComparer(this EqualConstraint constraint) - { - constraint.Using>( - (x, y) => - { - if (x.Count != y.Count) - { - return false; - } - - foreach (var key in x.Keys) - { - if (!y.ContainsKey(key)) - { - return false; - } - - var xElement = JsonUtils.SerializeToElement(x[key]); - var yElement = JsonUtils.SerializeToElement(y[key]); - - if (!JsonElementsAreEqual(xElement, yElement)) - { - return false; - } - } - - return true; - } - ); - - return constraint; - } - - internal static bool JsonElementsAreEqualPublic(JsonElement x, JsonElement y) => - JsonElementsAreEqual(x, y); - - private static bool JsonElementsAreEqual(JsonElement x, JsonElement y) - { - if (x.ValueKind != y.ValueKind) - { - return false; - } - - return x.ValueKind switch - { - JsonValueKind.Object => CompareJsonObjects(x, y), - JsonValueKind.Array => CompareJsonArrays(x, y), - JsonValueKind.String => x.GetString() == y.GetString(), - JsonValueKind.Number => x.GetDecimal() == y.GetDecimal(), - JsonValueKind.True => true, - JsonValueKind.False => true, - JsonValueKind.Null => true, - _ => false, - }; - } - - private static bool CompareJsonObjects(JsonElement x, JsonElement y) - { - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - if (xProps.Count != yProps.Count) - { - return false; - } - - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - return false; - } - - if (!JsonElementsAreEqual(xProps[key], yProps[key])) - { - return false; - } - } - - return true; - } - - private static bool CompareJsonArrays(JsonElement x, JsonElement y) - { - var xArray = x.EnumerateArray().ToList(); - var yArray = y.EnumerateArray().ToList(); - - if (xArray.Count != yArray.Count) - { - return false; - } - - for (var i = 0; i < xArray.Count; i++) - { - if (!JsonElementsAreEqual(xArray[i], yArray[i])) - { - return false; - } - } - - return true; - } - - /// - /// Modifies the EqualConstraint to handle cross-type comparisons involving JsonElement. - /// When UsingPropertiesComparer() walks object properties and encounters a property typed as - /// 'object', the expected side may be a Dictionary<object, object?> while the actual - /// (deserialized) side is a JsonElement. These typed predicates bridge that gap by serializing - /// the non-JsonElement side and comparing JSON representations. - /// - /// Uses typed Func<TExpected, TActual, bool> predicates instead of a non-generic - /// IComparer/IEqualityComparer so that NUnit's CanCompare type check ensures these only - /// fire when one side is a JsonElement, letting UsingPropertiesComparer() handle all - /// same-type comparisons normally. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingJsonSerializationComparer(this EqualConstraint constraint) - { - // Handle: expected is non-JsonElement, actual is JsonElement - constraint.Using( - (actualJsonElement, expectedObj) => - { - try - { - var expectedElement = JsonUtils.SerializeToElement(expectedObj); - return JsonElementsAreEqualPublic(expectedElement, actualJsonElement); - } - catch - { - return false; - } - } - ); - // Handle reverse: expected is JsonElement, actual is non-JsonElement - constraint.Using( - (actualObj, expectedJsonElement) => - { - try - { - var actualElement = JsonUtils.SerializeToElement(actualObj); - return JsonElementsAreEqualPublic(expectedJsonElement, actualElement); - } - catch - { - return false; - } - } - ); - return constraint; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs deleted file mode 100644 index 3f4b5eb602b2..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonAssert.cs +++ /dev/null @@ -1,29 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework; -using SeedApi.Core; - -namespace SeedApi.Test.Utils; - -internal static class JsonAssert -{ - /// - /// Asserts that the serialized JSON of an object equals the expected JSON string. - /// Uses JsonElement comparison for reliable deep equality of collections and union types. - /// - internal static void AreEqual(object actual, string expectedJson) - { - var actualElement = JsonUtils.SerializeToElement(actual); - var expectedElement = JsonUtils.Deserialize(expectedJson); - Assert.That(actualElement, Is.EqualTo(expectedElement).UsingJsonElementComparer()); - } - - /// - /// Asserts that the given JSON string survives a deserialization/serialization round-trip - /// intact: deserializes to T then re-serializes and compares to the original JSON. - /// - internal static void Roundtrips(string json) - { - var deserialized = JsonUtils.Deserialize(json); - AreEqual(deserialized!, json); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs deleted file mode 100644 index a37ef402c1ac..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/JsonElementComparer.cs +++ /dev/null @@ -1,236 +0,0 @@ -using global::System.Text.Json; -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle JsonElement objects. -/// -public static class JsonElementComparerExtensions -{ - /// - /// Extension method for comparing JsonElement objects in NUnit tests. - /// Property order doesn't matter, but array order does matter. - /// Includes special handling for DateTime string formats. - /// - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare JsonElements with detailed diffs. - public static EqualConstraint UsingJsonElementComparer(this EqualConstraint constraint) - { - return constraint.Using(new JsonElementComparer()); - } -} - -/// -/// Equality comparer for JsonElement with detailed reporting. -/// Property order doesn't matter, but array order does matter. -/// Now includes special handling for DateTime string formats with improved null handling. -/// -public class JsonElementComparer : IEqualityComparer -{ - private string _failurePath = string.Empty; - - /// - public bool Equals(JsonElement x, JsonElement y) - { - _failurePath = string.Empty; - return CompareJsonElements(x, y, string.Empty); - } - - /// - public int GetHashCode(JsonElement obj) - { - return JsonSerializer.Serialize(obj).GetHashCode(); - } - - private bool CompareJsonElements(JsonElement x, JsonElement y, string path) - { - // If value kinds don't match, they're not equivalent - if (x.ValueKind != y.ValueKind) - { - _failurePath = $"{path}: Expected {x.ValueKind} but got {y.ValueKind}"; - return false; - } - - switch (x.ValueKind) - { - case JsonValueKind.Object: - return CompareJsonObjects(x, y, path); - - case JsonValueKind.Array: - return CompareJsonArraysInOrder(x, y, path); - - case JsonValueKind.String: - string? xStr = x.GetString(); - string? yStr = y.GetString(); - - // Handle null strings - if (xStr is null && yStr is null) - return true; - - if (xStr is null || yStr is null) - { - _failurePath = - $"{path}: Expected {(xStr is null ? "null" : $"\"{xStr}\"")} but got {(yStr is null ? "null" : $"\"{yStr}\"")}"; - return false; - } - - // Check if they are identical strings - if (xStr == yStr) - return true; - - // Try to handle DateTime strings - if (IsLikelyDateTimeString(xStr) && IsLikelyDateTimeString(yStr)) - { - if (AreEquivalentDateTimeStrings(xStr, yStr)) - return true; - } - - _failurePath = $"{path}: Expected \"{xStr}\" but got \"{yStr}\""; - return false; - - case JsonValueKind.Number: - if (x.GetDecimal() != y.GetDecimal()) - { - _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; - return false; - } - - return true; - - case JsonValueKind.True: - case JsonValueKind.False: - if (x.GetBoolean() != y.GetBoolean()) - { - _failurePath = $"{path}: Expected {x.GetBoolean()} but got {y.GetBoolean()}"; - return false; - } - - return true; - - case JsonValueKind.Null: - return true; - - default: - _failurePath = $"{path}: Unsupported JsonValueKind {x.ValueKind}"; - return false; - } - } - - private bool IsLikelyDateTimeString(string? str) - { - // Simple heuristic to identify likely ISO date time strings - return str is not null - && (str.Contains("T") && (str.EndsWith("Z") || str.Contains("+") || str.Contains("-"))); - } - - private bool AreEquivalentDateTimeStrings(string str1, string str2) - { - // Try to parse both as DateTime - if (DateTime.TryParse(str1, out DateTime dt1) && DateTime.TryParse(str2, out DateTime dt2)) - { - return dt1 == dt2; - } - - return false; - } - - private bool CompareJsonObjects(JsonElement x, JsonElement y, string path) - { - // Create dictionaries for both JSON objects - var xProps = new Dictionary(); - var yProps = new Dictionary(); - - foreach (var prop in x.EnumerateObject()) - xProps[prop.Name] = prop.Value; - - foreach (var prop in y.EnumerateObject()) - yProps[prop.Name] = prop.Value; - - // Check if all properties in x exist in y - foreach (var key in xProps.Keys) - { - if (!yProps.ContainsKey(key)) - { - _failurePath = $"{path}: Missing property '{key}'"; - return false; - } - } - - // Check if y has extra properties - foreach (var key in yProps.Keys) - { - if (!xProps.ContainsKey(key)) - { - _failurePath = $"{path}: Unexpected property '{key}'"; - return false; - } - } - - // Compare each property value - foreach (var key in xProps.Keys) - { - var propPath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}"; - if (!CompareJsonElements(xProps[key], yProps[key], propPath)) - { - return false; - } - } - - return true; - } - - private bool CompareJsonArraysInOrder(JsonElement x, JsonElement y, string path) - { - var xArray = x.EnumerateArray(); - var yArray = y.EnumerateArray(); - - // Count x elements - var xCount = 0; - var xElements = new List(); - foreach (var item in xArray) - { - xElements.Add(item); - xCount++; - } - - // Count y elements - var yCount = 0; - var yElements = new List(); - foreach (var item in yArray) - { - yElements.Add(item); - yCount++; - } - - // Check if counts match - if (xCount != yCount) - { - _failurePath = $"{path}: Expected {xCount} items but found {yCount}"; - return false; - } - - // Compare elements in order - for (var i = 0; i < xCount; i++) - { - var itemPath = $"{path}[{i}]"; - if (!CompareJsonElements(xElements[i], yElements[i], itemPath)) - { - return false; - } - } - - return true; - } - - /// - public override string ToString() - { - if (!string.IsNullOrEmpty(_failurePath)) - { - return $"JSON comparison failed at {_failurePath}"; - } - - return "JsonElementEqualityComparer"; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs deleted file mode 100644 index 816f4c010e6e..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/NUnitExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class NUnitExtensions -{ - /// - /// Modifies the EqualConstraint to use our own set of default comparers. - /// - /// - /// - public static EqualConstraint UsingDefaults(this EqualConstraint constraint) => - constraint - .UsingPropertiesComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingReadOnlyMemoryComparer() - .UsingOneOfComparer() - .UsingJsonElementComparer() - .UsingOptionalComparer() - .UsingObjectDictionaryComparer() - .UsingAdditionalPropertiesComparer() - .UsingJsonSerializationComparer(); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs deleted file mode 100644 index 767439174363..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OneOfComparer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle OneOf values. -/// -public static class EqualConstraintExtensions -{ - /// - /// Modifies the EqualConstraint to handle OneOf instances by comparing their inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOneOfComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOneOf types - constraint.Using( - (x, y) => - { - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (x.Value is null && y.Value is null) - { - return true; - } - - if (x.Value is null) - { - return false; - } - - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(x.Value, y.Value, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs deleted file mode 100644 index 98bfcac477b8..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/OptionalComparer.cs +++ /dev/null @@ -1,104 +0,0 @@ -using NUnit.Framework.Constraints; -using OneOf; -using SeedApi.Core; - -namespace NUnit.Framework; - -/// -/// Extensions for EqualConstraint to handle Optional values. -/// -public static class OptionalComparerExtensions -{ - /// - /// Modifies the EqualConstraint to handle Optional instances by comparing their IsDefined state and inner values. - /// This works alongside other comparison modifiers like UsingPropertiesComparer. - /// - /// The EqualConstraint to modify. - /// The same constraint instance for method chaining. - public static EqualConstraint UsingOptionalComparer(this EqualConstraint constraint) - { - // Register a comparer factory for IOptional types - constraint.Using( - (x, y) => - { - // Both must have the same IsDefined state - if (x.IsDefined != y.IsDefined) - { - return false; - } - - // If both are undefined, they're equal - if (!x.IsDefined) - { - return true; - } - - // Both are defined, compare their boxed values - var xValue = x.GetBoxedValue(); - var yValue = y.GetBoxedValue(); - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (xValue is null && yValue is null) - { - return true; - } - - if (xValue is null || yValue is null) - { - return false; - } - - // Use NUnit's property comparer for the inner values - var propertiesComparer = new NUnitEqualityComparer(); - var tolerance = Tolerance.Default; - propertiesComparer.CompareProperties = true; - // Add OneOf comparer to handle nested OneOf values (e.g., in Lists within Optional) - propertiesComparer.ExternalComparers.Add( - new OneOfEqualityAdapter(propertiesComparer) - ); - return propertiesComparer.AreEqual(xValue, yValue, ref tolerance); - } - ); - - return constraint; - } - - /// - /// EqualityAdapter for comparing IOneOf instances within NUnitEqualityComparer. - /// This enables recursive comparison of nested OneOf values within Optional types. - /// - private class OneOfEqualityAdapter : EqualityAdapter - { - private readonly NUnitEqualityComparer _comparer; - - public OneOfEqualityAdapter(NUnitEqualityComparer comparer) - { - _comparer = comparer; - } - - public override bool CanCompare(object? x, object? y) - { - return x is IOneOf && y is IOneOf; - } - - public override bool AreEqual(object? x, object? y) - { - var oneOfX = (IOneOf?)x; - var oneOfY = (IOneOf?)y; - - // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (oneOfX?.Value is null && oneOfY?.Value is null) - { - return true; - } - - if (oneOfX?.Value is null || oneOfY?.Value is null) - { - return false; - } - - var tolerance = Tolerance.Default; - return _comparer.AreEqual(oneOfX.Value, oneOfY.Value, ref tolerance); - } - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs b/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs deleted file mode 100644 index fc0b595a5e54..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi.Test/Utils/ReadOnlyMemoryComparer.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework.Constraints; - -namespace NUnit.Framework; - -/// -/// Extensions for NUnit constraints. -/// -public static class ReadOnlyMemoryComparerExtensions -{ - /// - /// Extension method for comparing ReadOnlyMemory<T> in NUnit tests. - /// - /// The type of elements in the ReadOnlyMemory. - /// The Is.EqualTo() constraint instance. - /// A constraint that can compare ReadOnlyMemory<T>. - public static EqualConstraint UsingReadOnlyMemoryComparer(this EqualConstraint constraint) - where T : IComparable - { - return constraint.Using(new ReadOnlyMemoryComparer()); - } -} - -/// -/// Comparer for ReadOnlyMemory<T>. Compares sequences by value. -/// -/// -/// The type of elements in the ReadOnlyMemory. -/// -public class ReadOnlyMemoryComparer : IComparer> - where T : IComparable -{ - /// - public int Compare(ReadOnlyMemory x, ReadOnlyMemory y) - { - // Check if sequences are equal - var xSpan = x.Span; - var ySpan = y.Span; - - // Optimized case for IEquatable implementations - if (typeof(IEquatable).IsAssignableFrom(typeof(T))) - { - var areEqual = xSpan.SequenceEqual(ySpan); - if (areEqual) - { - return 0; // Sequences are equal - } - } - else - { - // Manual equality check for non-IEquatable types - if (xSpan.Length == ySpan.Length) - { - var areEqual = true; - for (var i = 0; i < xSpan.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - areEqual = false; - break; - } - } - - if (areEqual) - { - return 0; // Sequences are equal - } - } - } - - // For non-equal sequences, we need to return a consistent ordering - // First compare lengths - if (x.Length != y.Length) - return x.Length.CompareTo(y.Length); - - // Same length but different content - compare first differing element - for (var i = 0; i < x.Length; i++) - { - if (!EqualityComparer.Default.Equals(xSpan[i], ySpan[i])) - { - return xSpan[i].CompareTo(ySpan[i]); - } - } - - // Should never reach here if not equal - return 0; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs deleted file mode 100644 index 838d0c00b960..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/ApiResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// The response object returned from the API. -/// -internal record ApiResponse -{ - internal required int StatusCode { get; init; } - - internal required HttpResponseMessage Raw { get; init; } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs deleted file mode 100644 index 9a3cabb806a3..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/BaseRequest.cs +++ /dev/null @@ -1,67 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; -using global::System.Text; - -namespace SeedApi.Core; - -internal abstract record BaseRequest -{ - internal string? BaseUrl { get; init; } - - internal required HttpMethod Method { get; init; } - - internal required string Path { get; init; } - - internal string? ContentType { get; init; } - - /// - /// The query string for this request (including the leading '?' if non-empty). - /// - internal string? QueryString { get; init; } - - internal Dictionary Headers { get; init; } = - new(StringComparer.OrdinalIgnoreCase); - - internal IRequestOptions? Options { get; init; } - - internal abstract HttpContent? CreateContent(); - - protected static ( - Encoding encoding, - string? charset, - string mediaType - ) ParseContentTypeOrDefault( - string? contentType, - Encoding encodingFallback, - string mediaTypeFallback - ) - { - var encoding = encodingFallback; - var mediaType = mediaTypeFallback; - string? charset = null; - if (string.IsNullOrEmpty(contentType)) - { - return (encoding, charset, mediaType); - } - - if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) - { - return (encoding, charset, mediaType); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) - { - charset = mediaTypeHeaderValue.CharSet; - encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) - { - mediaType = mediaTypeHeaderValue.MediaType; - } - - return (encoding, charset, mediaType); - } - - protected static Encoding Utf8NoBom => EncodingCache.Utf8NoBom; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs deleted file mode 100644 index b684f33d750e..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/CollectionItemSerializer.cs +++ /dev/null @@ -1,91 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Json collection converter. -/// -/// Type of item to convert. -/// Converter to use for individual items. -internal class CollectionItemSerializer - : JsonConverter> - where TConverterType : JsonConverter, new() -{ - private static readonly TConverterType _converter = new TConverterType(); - - /// - /// Reads a json string and deserializes it into an object. - /// - /// Json reader. - /// Type to convert. - /// Serializer options. - /// Created object. - public override IEnumerable? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - var returnValue = new List(); - - while (reader.TokenType != JsonTokenType.EndArray) - { - if (reader.TokenType != JsonTokenType.StartArray) - { - var item = (TDatatype)( - JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) - ?? throw new global::System.Exception( - $"Failed to deserialize collection item of type {typeof(TDatatype)}" - ) - ); - returnValue.Add(item); - } - - reader.Read(); - } - - return returnValue; - } - - /// - /// Writes a json string. - /// - /// Json writer. - /// Value to write. - /// Serializer options. - public override void Write( - Utf8JsonWriter writer, - IEnumerable? value, - JsonSerializerOptions options - ) - { - if (value is null) - { - writer.WriteNullValue(); - return; - } - - var jsonSerializerOptions = new JsonSerializerOptions(options); - jsonSerializerOptions.Converters.Clear(); - jsonSerializerOptions.Converters.Add(_converter); - - writer.WriteStartArray(); - - foreach (var data in value) - { - JsonSerializer.Serialize(writer, data, jsonSerializerOptions); - } - - writer.WriteEndArray(); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs deleted file mode 100644 index ccf4e963cc89..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi.Core; - -internal static class Constants -{ - public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; - public const string DateFormat = "yyyy-MM-dd"; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs deleted file mode 100644 index af61cc061ae5..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/DateOnlyConverter.cs +++ /dev/null @@ -1,747 +0,0 @@ -// ReSharper disable All -#pragma warning disable - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using global::System.Diagnostics; -using global::System.Diagnostics.CodeAnalysis; -using global::System.Globalization; -using global::System.Runtime.CompilerServices; -using global::System.Runtime.InteropServices; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -// ReSharper disable SuggestVarOrType_SimpleTypes -// ReSharper disable SuggestVarOrType_BuiltInTypes - -namespace SeedApi.Core -{ - /// - /// Custom converter for handling the data type with the System.Text.Json library. - /// - /// - /// This class backported from: - /// - /// System.Text.Json.Serialization.Converters.DateOnlyConverter - /// - public sealed class DateOnlyConverter : JsonConverter - { - private const int FormatLength = 10; // YYYY-MM-DD - - private const int MaxEscapedFormatLength = - FormatLength * JsonConstants.MaxExpansionFactorWhileEscaping; - - /// - public override DateOnly Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType != JsonTokenType.String) - { - ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType); - } - - return ReadCore(ref reader); - } - - /// - public override DateOnly ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - return ReadCore(ref reader); - } - - private static DateOnly ReadCore(ref Utf8JsonReader reader) - { - if ( - !JsonHelpers.IsInRangeInclusive( - reader.ValueLength(), - FormatLength, - MaxEscapedFormatLength - ) - ) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - scoped ReadOnlySpan source; - if (!reader.HasValueSequence && !reader.ValueIsEscaped) - { - source = reader.ValueSpan; - } - else - { - Span stackSpan = stackalloc byte[MaxEscapedFormatLength]; - int bytesWritten = reader.CopyString(stackSpan); - source = stackSpan.Slice(0, bytesWritten); - } - - if (!JsonHelpers.TryParseAsIso(source, out DateOnly value)) - { - ThrowHelper.ThrowFormatException(DataType.DateOnly); - } - - return value; - } - - /// - public override void Write( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WriteStringValue(buffer); - } - - /// - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options - ) - { -#if NET8_0_OR_GREATER - Span buffer = stackalloc byte[FormatLength]; -#else - Span buffer = stackalloc char[FormatLength]; -#endif - // ReSharper disable once RedundantAssignment - bool formattedSuccessfully = value.TryFormat( - buffer, - out int charsWritten, - "O".AsSpan(), - CultureInfo.InvariantCulture - ); - Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); - writer.WritePropertyName(buffer); - } - } - - internal static class JsonConstants - { - // The maximum number of fraction digits the Json DateTime parser allows - public const int DateTimeParseNumFractionDigits = 16; - - // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. - public const int MaxExpansionFactorWhileEscaping = 6; - - // The largest fraction expressible by TimeSpan and DateTime formats - public const int MaxDateTimeFraction = 9_999_999; - - // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. - public const int DateTimeNumFractionDigits = 7; - - public const byte UtcOffsetToken = (byte)'Z'; - - public const byte TimePrefix = (byte)'T'; - - public const byte Period = (byte)'.'; - - public const byte Hyphen = (byte)'-'; - - public const byte Colon = (byte)':'; - - public const byte Plus = (byte)'+'; - } - - // ReSharper disable SuggestVarOrType_Elsewhere - // ReSharper disable SuggestVarOrType_SimpleTypes - // ReSharper disable SuggestVarOrType_BuiltInTypes - - internal static class JsonHelpers - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => - (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); - - public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0'; - - [StructLayout(LayoutKind.Auto)] - private struct DateTimeParseData - { - public int Year; - public int Month; - public int Day; - public bool IsCalendarDateOnly; - public int Hour; - public int Minute; - public int Second; - public int Fraction; // This value should never be greater than 9_999_999. - public int OffsetHours; - public int OffsetMinutes; - - // ReSharper disable once NotAccessedField.Local - public byte OffsetToken; - } - - public static bool TryParseAsIso(ReadOnlySpan source, out DateOnly value) - { - if ( - TryParseDateTimeOffset(source, out DateTimeParseData parseData) - && parseData.IsCalendarDateOnly - && TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime) - ) - { - value = DateOnly.FromDateTime(dateTime); - return true; - } - - value = default; - return false; - } - - /// - /// ISO 8601 date time parser (ISO 8601-1:2019). - /// - /// The date/time to parse in UTF-8 format. - /// The parsed for the given . - /// - /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day - /// representations with optional specification of seconds and fractional seconds. - /// - /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2). - /// If unspecified they are considered to be local per spec. - /// - /// Examples: (TZD is either "Z" or hh:mm offset from UTC) - /// - /// YYYY-MM-DD (e.g. 1997-07-16) - /// YYYY-MM-DDThh:mm (e.g. 1997-07-16T19:20) - /// YYYY-MM-DDThh:mm:ss (e.g. 1997-07-16T19:20:30) - /// YYYY-MM-DDThh:mm:ss.s (e.g. 1997-07-16T19:20:30.45) - /// YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00) - /// YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:3001:00) - /// YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45Z) - /// - /// Generally speaking we always require the "extended" option when one exists (3.1.3.5). - /// The extended variants have separator characters between components ('-', ':', '.', etc.). - /// Spaces are not permitted. - /// - /// "true" if successfully parsed. - private static bool TryParseDateTimeOffset( - ReadOnlySpan source, - out DateTimeParseData parseData - ) - { - parseData = default; - - // too short datetime - Debug.Assert(source.Length >= 10); - - // Parse the calendar date - // ----------------------- - // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format" - // [dateX] = [year]["-"][month]["-"][day] - // [year] = [YYYY] [0000 - 9999] (4.3.2) - // [month] = [MM] [01 - 12] (4.3.3) - // [day] = [DD] [01 - 28, 29, 30, 31] (4.3.4) - // - // Note: 5.2.2.2 "Representations with reduced precision" allows for - // just [year]["-"][month] (a) and just [year] (b), but we currently - // don't permit it. - - { - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - uint digit3 = source[2] - (uint)'0'; - uint digit4 = source[3] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9) - { - return false; - } - - parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4); - } - - if ( - source[4] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month) - || source[7] != JsonConstants.Hyphen - || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day) - ) - { - return false; - } - - // We now have YYYY-MM-DD [dateX] - // ReSharper disable once ConvertIfStatementToSwitchStatement - if (source.Length == 10) - { - parseData.IsCalendarDateOnly = true; - return true; - } - - // Parse the time of day - // --------------------- - // - // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format" - // [timeX] = ["T"][hour][":"][min][":"][sec] - // [hour] = [hh] [00 - 23] (4.3.8a) - // [minute] = [mm] [00 - 59] (4.3.9a) - // [sec] = [ss] [00 - 59, 60 with a leap second] (4.3.10a) - // - // ISO 8601-1:2019 5.3.3 "UTC of day" - // [timeX]["Z"] - // - // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between - // local timescale and UTC" (Extended format) - // - // [shiftX] = ["+"|"-"][hour][":"][min] - // - // Notes: - // - // "T" is optional per spec, but _only_ when times are used alone. In our - // case, we're reading out a complete date & time and as such require "T". - // (5.4.2.1b). - // - // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations - // with reduced precision". 5.3.1.3b allows just specifying the hour, but - // we currently don't permit this. - // - // Decimal fractions are allowed for hours, minutes and seconds (5.3.14). - // We only allow fractions for seconds currently. Lower order components - // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be - // one digit, but the max number of digits is implementation defined. We - // currently allow up to 16 digits of fractional seconds only. While we - // support 16 fractional digits we only parse the first seven, anything - // past that is considered a zero. This is to stay compatible with the - // DateTime implementation which is limited to this resolution. - - if (source.Length < 16) - { - // Source does not have enough characters for YYYY-MM-DDThh:mm - return false; - } - - // Parse THH:MM (e.g. "T10:32") - if ( - source[10] != JsonConstants.TimePrefix - || source[13] != JsonConstants.Colon - || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour) - || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm - Debug.Assert(source.Length >= 16); - if (source.Length == 16) - { - return true; - } - - byte curByte = source[16]; - int sourceIndex = 17; - - // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Colon: - break; - default: - return false; - } - - // Try reading the seconds - if ( - source.Length < 19 - || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss - Debug.Assert(source.Length >= 19); - if (source.Length == 19) - { - return true; - } - - curByte = source[19]; - sourceIndex = 20; - - // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - case JsonConstants.Period: - break; - default: - return false; - } - - // Source does not have enough characters for second fractions (i.e. ".s") - // YYYY-MM-DDThh:mm:ss.s - if (source.Length < 21) - { - return false; - } - - // Parse fraction. This value should never be greater than 9_999_999 - int numDigitsRead = 0; - int fractionEnd = Math.Min( - sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, - source.Length - ); - - while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex])) - { - if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction = parseData.Fraction * 10 + (int)(curByte - (uint)'0'); - numDigitsRead++; - } - - sourceIndex++; - } - - if (parseData.Fraction != 0) - { - while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits) - { - parseData.Fraction *= 10; - numDigitsRead++; - } - } - - // We now have YYYY-MM-DDThh:mm:ss.s - Debug.Assert(sourceIndex <= source.Length); - if (sourceIndex == source.Length) - { - return true; - } - - curByte = source[sourceIndex++]; - - // TZD ['Z'|'+'|'-'] is valid at this point - switch (curByte) - { - case JsonConstants.UtcOffsetToken: - parseData.OffsetToken = JsonConstants.UtcOffsetToken; - return sourceIndex == source.Length; - case JsonConstants.Plus: - case JsonConstants.Hyphen: - parseData.OffsetToken = curByte; - return ParseOffset(ref parseData, source.Slice(sourceIndex)); - default: - return false; - } - - static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan offsetData) - { - // Parse the hours for the offset - if ( - offsetData.Length < 2 - || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours) - ) - { - return false; - } - - // We now have YYYY-MM-DDThh:mm:ss.s+|-hh - - if (offsetData.Length == 2) - { - // Just hours offset specified - return true; - } - - // Ensure we have enough for ":mm" - return offsetData.Length == 5 - && offsetData[2] == JsonConstants.Colon - && TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - // ReSharper disable once RedundantAssignment - private static bool TryGetNextTwoDigits(ReadOnlySpan source, ref int value) - { - Debug.Assert(source.Length == 2); - - uint digit1 = source[0] - (uint)'0'; - uint digit2 = source[1] - (uint)'0'; - - if (digit1 > 9 || digit2 > 9) - { - value = 0; - return false; - } - - value = (int)(digit1 * 10 + digit2); - return true; - } - - // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs - - /// - /// Overflow-safe DateTime factory. - /// - private static bool TryCreateDateTime( - DateTimeParseData parseData, - DateTimeKind kind, - out DateTime value - ) - { - if (parseData.Year == 0) - { - value = default; - return false; - } - - Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted. - - if ((uint)parseData.Month - 1 >= 12) - { - value = default; - return false; - } - - uint dayMinusOne = (uint)parseData.Day - 1; - if ( - dayMinusOne >= 28 - && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month) - ) - { - value = default; - return false; - } - - if ((uint)parseData.Hour > 23) - { - value = default; - return false; - } - - if ((uint)parseData.Minute > 59) - { - value = default; - return false; - } - - // This needs to allow leap seconds when appropriate. - // See https://github.com/dotnet/runtime/issues/30135. - if ((uint)parseData.Second > 59) - { - value = default; - return false; - } - - Debug.Assert(parseData.Fraction is >= 0 and <= JsonConstants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted. - - ReadOnlySpan days = DateTime.IsLeapYear(parseData.Year) - ? DaysToMonth366 - : DaysToMonth365; - int yearMinusOne = parseData.Year - 1; - int totalDays = - yearMinusOne * 365 - + yearMinusOne / 4 - - yearMinusOne / 100 - + yearMinusOne / 400 - + days[parseData.Month - 1] - + parseData.Day - - 1; - long ticks = totalDays * TimeSpan.TicksPerDay; - int totalSeconds = parseData.Hour * 3600 + parseData.Minute * 60 + parseData.Second; - ticks += totalSeconds * TimeSpan.TicksPerSecond; - ticks += parseData.Fraction; - value = new DateTime(ticks: ticks, kind: kind); - return true; - } - - private static ReadOnlySpan DaysToMonth365 => - [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; - private static ReadOnlySpan DaysToMonth366 => - [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; - } - - internal static class ThrowHelper - { - private const string ExceptionSourceValueToRethrowAsJsonException = - "System.Text.Json.Rethrowable"; - - [DoesNotReturn] - public static void ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) - { - throw GetInvalidOperationException("string", tokenType); - } - - public static void ThrowFormatException(DataType dataType) - { - throw new FormatException(SR.Format(SR.UnsupportedFormat, dataType)) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - - private static global::System.Exception GetInvalidOperationException( - string message, - JsonTokenType tokenType - ) - { - return GetInvalidOperationException(SR.Format(SR.InvalidCast, tokenType, message)); - } - - private static InvalidOperationException GetInvalidOperationException(string message) - { - return new InvalidOperationException(message) - { - Source = ExceptionSourceValueToRethrowAsJsonException, - }; - } - } - - internal static class Utf8JsonReaderExtensions - { - internal static int ValueLength(this Utf8JsonReader reader) => - reader.HasValueSequence - ? checked((int)reader.ValueSequence.Length) - : reader.ValueSpan.Length; - } - - internal enum DataType - { - TimeOnly, - DateOnly, - } - - [SuppressMessage("ReSharper", "InconsistentNaming")] - internal static class SR - { - private static readonly bool s_usingResourceKeys = - AppContext.TryGetSwitch( - "System.Resources.UseSystemResourceKeys", - out bool usingResourceKeys - ) && usingResourceKeys; - - public static string UnsupportedFormat => Strings.UnsupportedFormat; - - public static string InvalidCast => Strings.InvalidCast; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1) - : string.Format(resourceFormat, p1); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string Format(string resourceFormat, object? p1, object? p2) => - s_usingResourceKeys - ? string.Join(", ", resourceFormat, p1, p2) - : string.Format(resourceFormat, p1, p2); - } - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute( - "System.Resources.Tools.StronglyTypedResourceBuilder", - "17.0.0.0" - )] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings - { - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( - "Microsoft.Performance", - "CA1811:AvoidUncalledPrivateCode" - )] - internal Strings() { } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = - new global::System.Resources.ResourceManager( - "System.Text.Json.Resources.Strings", - typeof(Strings).Assembly - ); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute( - global::System.ComponentModel.EditorBrowsableState.Advanced - )] - internal static global::System.Globalization.CultureInfo Culture - { - get { return resourceCulture; } - set { resourceCulture = value; } - } - - /// - /// Looks up a localized string similar to Cannot get the value of a token type '{0}' as a {1}.. - /// - internal static string InvalidCast - { - get { return ResourceManager.GetString("InvalidCast", resourceCulture); } - } - - /// - /// Looks up a localized string similar to The JSON value is not in a supported {0} format.. - /// - internal static string UnsupportedFormat - { - get { return ResourceManager.GetString("UnsupportedFormat", resourceCulture); } - } - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs deleted file mode 100644 index d7dedc7f165b..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/DateTimeSerializer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using global::System.Globalization; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -internal class DateTimeSerializer : JsonConverter -{ - public override DateTime Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); - } - - public override DateTime ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - DateTime value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.ToString(Constants.DateTimeFormat)); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs deleted file mode 100644 index d14fc3bfa37e..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/EmptyRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// The request object to send without a request body. -/// -internal record EmptyRequest : BaseRequest -{ - internal override HttpContent? CreateContent() => null; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs deleted file mode 100644 index 2dae8b535a18..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/EncodingCache.cs +++ /dev/null @@ -1,11 +0,0 @@ -using global::System.Text; - -namespace SeedApi.Core; - -internal static class EncodingCache -{ - internal static readonly Encoding Utf8NoBom = new UTF8Encoding( - encoderShouldEmitUTF8Identifier: false, - throwOnInvalidBytes: true - ); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs deleted file mode 100644 index 7338b20e748c..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Extensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using global::System.Diagnostics.CodeAnalysis; -using global::System.Runtime.Serialization; - -namespace SeedApi.Core; - -internal static class Extensions -{ - public static string Stringify(this Enum value) - { - var field = value.GetType().GetField(value.ToString()); - if (field is not null) - { - var attribute = (EnumMemberAttribute?) - global::System.Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); - return attribute?.Value ?? value.ToString(); - } - return value.ToString(); - } - - /// - /// Asserts that a condition is true, throwing an exception with the specified message if it is false. - /// - /// The condition to assert. - /// The exception message if the assertion fails. - /// Thrown when the condition is false. - internal static void Assert(this object value, bool condition, string message) - { - if (!condition) - { - throw new global::System.Exception(message); - } - } - - /// - /// Asserts that a value is not null, throwing an exception with the specified message if it is null. - /// - /// The type of the value to assert. - /// The value to assert is not null. - /// The exception message if the assertion fails. - /// The non-null value. - /// Thrown when the value is null. - internal static TValue Assert( - this object _unused, - [NotNull] TValue? value, - string message - ) - where TValue : class - { - if (value is null) - { - throw new global::System.Exception(message); - } - return value; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs deleted file mode 100644 index 343c13716c24..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/FormUrlEncoder.cs +++ /dev/null @@ -1,33 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// Encodes an object into a form URL-encoded content. -/// -public static class FormUrlEncoder -{ - /// - /// Encodes an object into a form URL-encoded content using Deep Object notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsDeepObject(object value) => - new(QueryStringConverter.ToDeepObject(value)); - - /// - /// Encodes an object into a form URL-encoded content using Exploded Form notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsExplodedForm(object value) => - new(QueryStringConverter.ToExplodedForm(value)); - - /// - /// Encodes an object into a form URL-encoded content using Form notation without exploding parameters. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - internal static FormUrlEncodedContent EncodeAsForm(object value) => - new(QueryStringConverter.ToForm(value)); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs deleted file mode 100644 index e908825e31f1..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/HeaderValue.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace SeedApi.Core; - -internal sealed class HeaderValue -{ - private readonly Func> _resolver; - - public HeaderValue(string value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value); - } - - public HeaderValue(Func value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); - } - - public HeaderValue(Func> value) - { - _resolver = value; - } - - public HeaderValue(Func> value) - { - _resolver = () => new global::System.Threading.Tasks.ValueTask(value()); - } - - public static implicit operator HeaderValue(string value) => new(value); - - public static implicit operator HeaderValue(Func value) => new(value); - - public static implicit operator HeaderValue( - Func> value - ) => new(value); - - public static implicit operator HeaderValue( - Func> value - ) => new(value); - - public static HeaderValue FromString(string value) => new(value); - - public static HeaderValue FromFunc(Func value) => new(value); - - public static HeaderValue FromValueTaskFunc( - Func> value - ) => new(value); - - public static HeaderValue FromTaskFunc( - Func> value - ) => new(value); - - internal global::System.Threading.Tasks.ValueTask ResolveAsync() => _resolver(); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs deleted file mode 100644 index 5b2bfc62f423..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Headers.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Represents the headers sent with the request. -/// -internal sealed class Headers : Dictionary -{ - internal Headers() { } - - /// - /// Initializes a new instance of the Headers class with the specified value. - /// - /// - internal Headers(Dictionary value) - { - foreach (var kvp in value) - { - this[kvp.Key] = kvp.Value; - } - } - - /// - /// Initializes a new instance of the Headers class with the specified value. - /// - /// - internal Headers(IEnumerable> value) - : base(value.ToDictionary(e => e.Key, e => e.Value)) { } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs deleted file mode 100644 index 734b7ff41065..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/HeadersBuilder.cs +++ /dev/null @@ -1,197 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Fluent builder for constructing HTTP headers with support for merging from multiple sources. -/// Provides a clean API for building headers with proper precedence handling. -/// -internal static class HeadersBuilder -{ - /// - /// Fluent builder for constructing HTTP headers. - /// - public sealed class Builder - { - private readonly Dictionary _headers; - - /// - /// Initializes a new instance with default capacity. - /// Uses case-insensitive header name comparison. - /// - public Builder() - { - _headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - /// - /// Initializes a new instance with the specified initial capacity. - /// Uses case-insensitive header name comparison. - /// - public Builder(int capacity) - { - _headers = new Dictionary( - capacity, - StringComparer.OrdinalIgnoreCase - ); - } - - /// - /// Adds a header with the specified key and value. - /// If a header with the same key already exists, it will be overwritten. - /// Null values are ignored. - /// - /// The header name. - /// The header value. Null values are ignored. - /// This builder instance for method chaining. - public Builder Add(string key, string? value) - { - if (value is not null) - { - _headers[key] = (value); - } - return this; - } - - /// - /// Adds a header with the specified key and object value. - /// The value will be converted to string using ValueConvert for consistent serialization. - /// If a header with the same key already exists, it will be overwritten. - /// Null values are ignored. - /// - /// The header name. - /// The header value. Null values are ignored. - /// This builder instance for method chaining. - public Builder Add(string key, object? value) - { - if (value is null) - { - return this; - } - - // Use ValueConvert for consistent serialization across headers, query params, and path params - var stringValue = ValueConvert.ToString(value); - if (stringValue is not null) - { - _headers[key] = (stringValue); - } - return this; - } - - /// - /// Adds multiple headers from a Headers dictionary. - /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. - /// Overwrites any existing headers with the same key. - /// Null entries are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(Headers? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - _headers[header.Key] = header.Value; - } - - return this; - } - - /// - /// Adds multiple headers from a Headers dictionary, excluding the Authorization header. - /// This is useful for endpoints that don't require authentication, to avoid triggering - /// lazy auth token resolution. - /// HeaderValue instances are stored and will be resolved when BuildAsync() is called. - /// Overwrites any existing headers with the same key. - /// Null entries are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder AddWithoutAuth(Headers? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - _headers[header.Key] = header.Value; - } - - return this; - } - - /// - /// Adds multiple headers from a key-value pair collection. - /// Overwrites any existing headers with the same key. - /// Null values are ignored. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(IEnumerable>? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - if (header.Value is not null) - { - _headers[header.Key] = (header.Value); - } - } - - return this; - } - - /// - /// Adds multiple headers from a dictionary. - /// Overwrites any existing headers with the same key. - /// - /// The headers to add. Null is treated as empty. - /// This builder instance for method chaining. - public Builder Add(Dictionary? headers) - { - if (headers is null) - { - return this; - } - - foreach (var header in headers) - { - _headers[header.Key] = (header.Value); - } - - return this; - } - - /// - /// Asynchronously builds the final headers dictionary containing all merged headers. - /// Resolves all HeaderValue instances that may contain async operations. - /// Returns a case-insensitive dictionary. - /// - /// A task that represents the asynchronous operation, containing a case-insensitive dictionary of headers. - public async global::System.Threading.Tasks.Task> BuildAsync() - { - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var kvp in _headers) - { - var value = await kvp.Value.ResolveAsync().ConfigureAwait(false); - if (value is not null) - { - headers[kvp.Key] = value; - } - } - return headers; - } - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs deleted file mode 100644 index 4295da097dc9..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/HttpContentExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -#if !NET5_0_OR_GREATER -namespace SeedApi.Core; - -/// -/// Polyfill extension providing a ReadAsStringAsync(CancellationToken) overload -/// for target frameworks older than .NET 5, where only the parameterless -/// ReadAsStringAsync() is available. -/// -internal static class HttpContentExtensions -{ - internal static Task ReadAsStringAsync( - this HttpContent httpContent, - CancellationToken cancellationToken - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return httpContent.ReadAsStringAsync(); - } -} -#endif diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs deleted file mode 100644 index cedb977973d1..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/HttpMethodExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -internal static class HttpMethodExtensions -{ - public static readonly HttpMethod Patch = new("PATCH"); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs deleted file mode 100644 index 1a5d48064427..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/IIsRetryableContent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -public interface IIsRetryableContent -{ - public bool IsRetryable { get; } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs deleted file mode 100644 index 4562c1723cf9..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/IRequestOptions.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace SeedApi.Core; - -internal interface IRequestOptions -{ - /// - /// The Base URL for the API. - /// - public string? BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The http client used to make requests. - /// - public HttpClient? HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional headers to be sent with the request. - /// Headers previously set with matching keys will be overwritten. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The max number of retries to attempt. - /// - public int? MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The timeout for the request. - /// - public TimeSpan? Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional query parameters sent with the request. - /// - public IEnumerable> AdditionalQueryParameters { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional body properties sent with the request. - /// This is only applied to JSON requests. - /// - public object? AdditionalBodyProperties { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs deleted file mode 100644 index 93dcc6dd6bca..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonAccessAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace SeedApi.Core; - -[global::System.AttributeUsage( - global::System.AttributeTargets.Property | global::System.AttributeTargets.Field -)] -internal class JsonAccessAttribute(JsonAccessType accessType) : global::System.Attribute -{ - internal JsonAccessType AccessType { get; init; } = accessType; -} - -internal enum JsonAccessType -{ - ReadOnly, - WriteOnly, -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs deleted file mode 100644 index 2fa8cfb6ad8c..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonConfiguration.cs +++ /dev/null @@ -1,275 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Encodings.Web; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using global::System.Text.Json.Serialization; -using global::System.Text.Json.Serialization.Metadata; - -namespace SeedApi.Core; - -internal static partial class JsonOptions -{ - internal static readonly JsonSerializerOptions JsonSerializerOptions; - internal static readonly JsonSerializerOptions JsonSerializerOptionsRelaxedEscaping; - - static JsonOptions() - { - var options = new JsonSerializerOptions - { - Converters = - { - new DateTimeSerializer(), -#if USE_PORTABLE_DATE_ONLY - new DateOnlyConverter(), -#endif - new OneOfSerializer(), - new OptionalJsonConverterFactory(), - }, -#if DEBUG - WriteIndented = true, -#endif - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - TypeInfoResolver = new DefaultJsonTypeInfoResolver - { - Modifiers = - { - NullableOptionalModifier, - JsonAccessAndIgnoreModifier, - HandleExtensionDataFields, - }, - }, - }; - ConfigureJsonSerializerOptions(options); - JsonSerializerOptions = options; - - var relaxedOptions = new JsonSerializerOptions(options) - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; - JsonSerializerOptionsRelaxedEscaping = relaxedOptions; - } - - private static void NullableOptionalModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var property in typeInfo.Properties) - { - var propertyInfo = property.AttributeProvider as global::System.Reflection.PropertyInfo; - - if (propertyInfo is null) - continue; - - // Check for ReadOnly JsonAccessAttribute - it overrides Optional/Nullable behavior - var jsonAccessAttribute = propertyInfo.GetCustomAttribute(); - if (jsonAccessAttribute?.AccessType == JsonAccessType.ReadOnly) - { - // ReadOnly means "never serialize", which completely overrides Optional/Nullable. - // Skip Optional/Nullable processing since JsonAccessAndIgnoreModifier - // will set ShouldSerialize = false anyway. - continue; - } - // Note: WriteOnly doesn't conflict with Optional/Nullable since it only - // affects deserialization (Set), not serialization (ShouldSerialize) - - var isOptionalType = - property.PropertyType.IsGenericType - && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>); - - var hasOptionalAttribute = - propertyInfo.GetCustomAttribute() is not null; - var hasNullableAttribute = - propertyInfo.GetCustomAttribute() is not null; - - if (isOptionalType && hasOptionalAttribute) - { - var originalGetter = property.Get; - if (originalGetter is not null) - { - var capturedIsNullable = hasNullableAttribute; - - property.ShouldSerialize = (obj, value) => - { - var optionalValue = originalGetter(obj); - if (optionalValue is not IOptional optional) - return false; - - if (!optional.IsDefined) - return false; - - if (!capturedIsNullable) - { - var innerValue = optional.GetBoxedValue(); - if (innerValue is null) - return false; - } - - return true; - }; - } - } - else if (hasNullableAttribute) - { - // Force serialization of nullable properties even when null - property.ShouldSerialize = (obj, value) => true; - } - } - } - - private static void JsonAccessAndIgnoreModifier(JsonTypeInfo typeInfo) - { - if (typeInfo.Kind != JsonTypeInfoKind.Object) - return; - - foreach (var propertyInfo in typeInfo.Properties) - { - var jsonAccessAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonAccessAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonAccessAttribute is not null) - { - propertyInfo.IsRequired = false; - switch (jsonAccessAttribute.AccessType) - { - case JsonAccessType.ReadOnly: - propertyInfo.ShouldSerialize = (_, _) => false; - break; - case JsonAccessType.WriteOnly: - propertyInfo.Set = null; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - var jsonIgnoreAttribute = propertyInfo - .AttributeProvider?.GetCustomAttributes(typeof(JsonIgnoreAttribute), true) - .OfType() - .FirstOrDefault(); - - if (jsonIgnoreAttribute is not null) - { - propertyInfo.IsRequired = false; - } - } - } - - private static void HandleExtensionDataFields(JsonTypeInfo typeInfo) - { - if ( - typeInfo.Kind == JsonTypeInfoKind.Object - && typeInfo.Properties.All(prop => !prop.IsExtensionData) - ) - { - var extensionProp = typeInfo - .Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) - .FirstOrDefault(prop => - prop.GetCustomAttribute() is not null - ); - - if (extensionProp is not null) - { - var jsonPropertyInfo = typeInfo.CreateJsonPropertyInfo( - extensionProp.FieldType, - extensionProp.Name - ); - jsonPropertyInfo.Get = extensionProp.GetValue; - jsonPropertyInfo.Set = extensionProp.SetValue; - jsonPropertyInfo.IsExtensionData = true; - typeInfo.Properties.Add(jsonPropertyInfo); - } - } - } - - static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); -} - -internal static class JsonUtils -{ - internal static string Serialize(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); - - internal static string Serialize(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptions); - - internal static string SerializeRelaxedEscaping(T obj) => - JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static string SerializeRelaxedEscaping(object obj, global::System.Type type) => - JsonSerializer.Serialize(obj, type, JsonOptions.JsonSerializerOptionsRelaxedEscaping); - - internal static JsonElement SerializeToElement(T obj) => - JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonElement SerializeToElement(object obj, global::System.Type type) => - JsonSerializer.SerializeToElement(obj, type, JsonOptions.JsonSerializerOptions); - - internal static JsonDocument SerializeToDocument(T obj) => - JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); - - internal static JsonNode? SerializeToNode(T obj) => - JsonSerializer.SerializeToNode(obj, JsonOptions.JsonSerializerOptions); - - internal static byte[] SerializeToUtf8Bytes(T obj) => - JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions.JsonSerializerOptions); - - internal static string SerializeWithAdditionalProperties( - T obj, - object? additionalProperties = null - ) - { - if (additionalProperties is null) - { - return Serialize(obj); - } - var additionalPropertiesJsonNode = SerializeToNode(additionalProperties); - if (additionalPropertiesJsonNode is not JsonObject additionalPropertiesJsonObject) - { - throw new InvalidOperationException( - "The additional properties must serialize to a JSON object." - ); - } - var jsonNode = SerializeToNode(obj); - if (jsonNode is not JsonObject jsonObject) - { - throw new InvalidOperationException( - "The serialized object must be a JSON object to add properties." - ); - } - MergeJsonObjects(jsonObject, additionalPropertiesJsonObject); - return jsonObject.ToJsonString(JsonOptions.JsonSerializerOptions); - } - - private static void MergeJsonObjects(JsonObject baseObject, JsonObject overrideObject) - { - foreach (var property in overrideObject) - { - if (!baseObject.TryGetPropertyValue(property.Key, out JsonNode? existingValue)) - { - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - continue; - } - if ( - existingValue is JsonObject nestedBaseObject - && property.Value is JsonObject nestedOverrideObject - ) - { - // If both values are objects, recursively merge them. - MergeJsonObjects(nestedBaseObject, nestedOverrideObject); - continue; - } - // Otherwise, the overrideObject takes precedence. - baseObject[property.Key] = property.Value is not null - ? JsonNode.Parse(property.Value.ToJsonString()) - : null; - } - } - - internal static T Deserialize(string json) => - JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs deleted file mode 100644 index 0a85891304f1..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/JsonRequest.cs +++ /dev/null @@ -1,36 +0,0 @@ -using global::System.Net.Http; - -namespace SeedApi.Core; - -/// -/// The request object to be sent for JSON APIs. -/// -internal record JsonRequest : BaseRequest -{ - internal object? Body { get; init; } - - internal override HttpContent? CreateContent() - { - if (Body is null && Options?.AdditionalBodyProperties is null) - { - return null; - } - - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - ContentType, - Utf8NoBom, - "application/json" - ); - var content = new StringContent( - JsonUtils.SerializeWithAdditionalProperties(Body, Options?.AdditionalBodyProperties), - encoding, - mediaType - ); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - return content; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs deleted file mode 100644 index bf72225d461b..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/MultipartFormRequest.cs +++ /dev/null @@ -1,294 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; - -namespace SeedApi.Core; - -/// -/// The request object to be sent for multipart form data. -/// -internal record MultipartFormRequest : BaseRequest -{ - private readonly List> _partAdders = []; - - internal void AddJsonPart(string name, object? value) => AddJsonPart(name, value, null); - - internal void AddJsonPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - contentType, - Utf8NoBom, - "application/json" - ); - var content = new StringContent(JsonUtils.Serialize(value), encoding, mediaType); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - form.Add(content, name); - }); - } - - internal void AddJsonParts(string name, IEnumerable? value) => - AddJsonParts(name, value, null); - - internal void AddJsonParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddJsonPart(name, item, contentType); - } - } - - internal void AddJsonParts(string name, IEnumerable? value) => - AddJsonParts(name, value, null); - - internal void AddJsonParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddJsonPart(name, item, contentType); - } - } - - internal void AddStringPart(string name, object? value) => AddStringPart(name, value, null); - - internal void AddStringPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - AddStringPart(name, ValueConvert.ToString(value), contentType); - } - - internal void AddStringPart(string name, string? value) => AddStringPart(name, value, null); - - internal void AddStringPart(string name, string? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var (encoding, charset, mediaType) = ParseContentTypeOrDefault( - contentType, - Utf8NoBom, - "text/plain" - ); - var content = new StringContent(value, encoding, mediaType); - if (string.IsNullOrEmpty(charset) && content.Headers.ContentType is not null) - { - content.Headers.ContentType.CharSet = ""; - } - - form.Add(content, name); - }); - } - - internal void AddStringParts(string name, IEnumerable? value) => - AddStringParts(name, value, null); - - internal void AddStringParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - AddStringPart(name, ValueConvert.ToString(value), contentType); - } - - internal void AddStringParts(string name, IEnumerable? value) => - AddStringParts(name, value, null); - - internal void AddStringParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddStringPart(name, item, contentType); - } - } - - internal void AddStreamPart(string name, Stream? stream, string? fileName) => - AddStreamPart(name, stream, fileName, null); - - internal void AddStreamPart(string name, Stream? stream, string? fileName, string? contentType) - { - if (stream is null) - { - return; - } - - _partAdders.Add(form => - { - var content = new StreamContent(stream) - { - Headers = - { - ContentType = MediaTypeHeaderValue.Parse( - contentType ?? "application/octet-stream" - ), - }, - }; - - if (fileName is not null) - { - form.Add(content, name, fileName); - } - else - { - form.Add(content, name); - } - }); - } - - internal void AddFileParameterPart(string name, Stream? stream) => - AddStreamPart(name, stream, null, null); - - internal void AddFileParameterPart(string name, FileParameter? file) => - AddFileParameterPart(name, file, null); - - internal void AddFileParameterPart( - string name, - FileParameter? file, - string? fallbackContentType - ) => - AddStreamPart(name, file?.Stream, file?.FileName, file?.ContentType ?? fallbackContentType); - - internal void AddFileParameterParts(string name, IEnumerable? files) => - AddFileParameterParts(name, files, null); - - internal void AddFileParameterParts( - string name, - IEnumerable? files, - string? fallbackContentType - ) - { - if (files is null) - { - return; - } - - foreach (var file in files) - { - AddFileParameterPart(name, file, fallbackContentType); - } - } - - internal void AddFormEncodedPart(string name, object? value) => - AddFormEncodedPart(name, value, null); - - internal void AddFormEncodedPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var content = FormUrlEncoder.EncodeAsForm(value); - if (!string.IsNullOrEmpty(contentType)) - { - content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); - } - - form.Add(content, name); - }); - } - - internal void AddFormEncodedParts(string name, IEnumerable? value) => - AddFormEncodedParts(name, value, null); - - internal void AddFormEncodedParts(string name, IEnumerable? value, string? contentType) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddFormEncodedPart(name, item, contentType); - } - } - - internal void AddExplodedFormEncodedPart(string name, object? value) => - AddExplodedFormEncodedPart(name, value, null); - - internal void AddExplodedFormEncodedPart(string name, object? value, string? contentType) - { - if (value is null) - { - return; - } - - _partAdders.Add(form => - { - var content = FormUrlEncoder.EncodeAsExplodedForm(value); - if (!string.IsNullOrEmpty(contentType)) - { - content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); - } - - form.Add(content, name); - }); - } - - internal void AddExplodedFormEncodedParts(string name, IEnumerable? value) => - AddExplodedFormEncodedParts(name, value, null); - - internal void AddExplodedFormEncodedParts( - string name, - IEnumerable? value, - string? contentType - ) - { - if (value is null) - { - return; - } - - foreach (var item in value) - { - AddExplodedFormEncodedPart(name, item, contentType); - } - } - - internal override HttpContent CreateContent() - { - var form = new MultipartFormDataContent(); - foreach (var adder in _partAdders) - { - adder(form); - } - - return form; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs deleted file mode 100644 index a1d30328bf9a..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/NullableAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as nullable in the OpenAPI specification. -/// When applied to Optional properties, this indicates that null values should be -/// written to JSON when the optional is defined with null. -/// -/// -/// For regular (required) properties: -/// - Without [Nullable]: null values are invalid (omit from JSON at runtime) -/// - With [Nullable]: null values are written to JSON -/// -/// For Optional properties (also marked with [Optional]): -/// - Without [Nullable]: Optional.Of(null) → omit from JSON (runtime edge case) -/// - With [Nullable]: Optional.Of(null) → write null to JSON -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class NullableAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs deleted file mode 100644 index 6eeb68fcba46..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/OneOfSerializer.cs +++ /dev/null @@ -1,145 +0,0 @@ -using global::System.Reflection; -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using OneOf; - -namespace SeedApi.Core; - -internal class OneOfSerializer : JsonConverter -{ - public override IOneOf? Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType is JsonTokenType.Null) - return default; - - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - var readerCopy = reader; - var result = JsonSerializer.Deserialize(ref readerCopy, type, options); - reader.Skip(); - return (IOneOf)cast.Invoke(null, [result])!; - } - catch (JsonException) { } - } - - throw new JsonException( - $"Cannot deserialize into one of the supported types for {typeToConvert}" - ); - } - - public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value.Value, options); - } - - public override IOneOf ReadAsPropertyName( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = reader.GetString(); - if (stringValue == null) - throw new JsonException("Cannot deserialize null property name into OneOf type"); - - // Try to deserialize the string value into one of the supported types - foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) - { - try - { - // For primitive types, try direct conversion - if (type == typeof(string)) - { - return (IOneOf)cast.Invoke(null, [stringValue])!; - } - - // For other types, try to deserialize from JSON string - var result = JsonSerializer.Deserialize($"\"{stringValue}\"", type, options); - if (result != null) - { - return (IOneOf)cast.Invoke(null, [result])!; - } - } - catch { } - } - - // If no type-specific deserialization worked, default to string if available - var stringType = GetOneOfTypes(typeToConvert).FirstOrDefault(t => t.type == typeof(string)); - if (stringType != default) - { - return (IOneOf)stringType.cast.Invoke(null, [stringValue])!; - } - - throw new JsonException( - $"Cannot deserialize dictionary key '{stringValue}' into one of the supported types for {typeToConvert}" - ); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - IOneOf value, - JsonSerializerOptions options - ) - { - // Serialize the underlying value to a string suitable for use as a dictionary key - var stringValue = value.Value?.ToString() ?? "null"; - writer.WritePropertyName(stringValue); - } - - private static (global::System.Type type, MethodInfo cast)[] GetOneOfTypes( - global::System.Type typeToConvert - ) - { - var type = typeToConvert; - if (Nullable.GetUnderlyingType(type) is { } underlyingType) - { - type = underlyingType; - } - - var casts = type.GetRuntimeMethods() - .Where(m => m.IsSpecialName && m.Name == "op_Implicit") - .ToArray(); - while (type is not null) - { - if ( - type.IsGenericType - && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) - ) - { - var genericArguments = type.GetGenericArguments(); - if (genericArguments.Length == 1) - { - return [(genericArguments[0], casts[0])]; - } - - // if object type is present, make sure it is last - var indexOfObjectType = Array.IndexOf(genericArguments, typeof(object)); - if (indexOfObjectType != -1 && genericArguments.Length - 1 != indexOfObjectType) - { - genericArguments = genericArguments - .OrderBy(t => t == typeof(object) ? 1 : 0) - .ToArray(); - } - - return genericArguments - .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) - .ToArray(); - } - - type = type.BaseType; - } - - throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); - } - - public override bool CanConvert(global::System.Type typeToConvert) - { - return typeof(IOneOf).IsAssignableFrom(typeToConvert); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs deleted file mode 100644 index d174943cb2cf..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Optional.cs +++ /dev/null @@ -1,474 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; - -namespace SeedApi.Core; - -/// -/// Non-generic interface for Optional types to enable reflection-free checks. -/// -public interface IOptional -{ - /// - /// Returns true if the value is defined (set), even if the value is null. - /// - bool IsDefined { get; } - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - object? GetBoxedValue(); -} - -/// -/// Represents a field that can be "not set" (undefined) vs "explicitly set" (defined). -/// Use this for HTTP PATCH requests where you need to distinguish between: -/// -/// Undefined: Don't send this field (leave it unchanged on the server) -/// Defined with null: Send null (clear the field on the server) -/// Defined with value: Send the value (update the field on the server) -/// -/// -/// The type of the value. Use nullable types (T?) for fields that can be null. -/// -/// For nullable string fields, use Optional<string?>: -/// -/// public class UpdateUserRequest -/// { -/// public Optional<string?> Name { get; set; } = Optional<string?>.Undefined; -/// } -/// -/// var request = new UpdateUserRequest -/// { -/// Name = "John" // Will send: { "name": "John" } -/// }; -/// -/// var request2 = new UpdateUserRequest -/// { -/// Name = Optional<string?>.Of(null) // Will send: { "name": null } -/// }; -/// -/// var request3 = new UpdateUserRequest(); // Will send: {} (name not included) -/// -/// -public readonly struct Optional : IOptional, IEquatable> -{ - private readonly T _value; - private readonly bool _isDefined; - - private Optional(T value, bool isDefined) - { - _value = value; - _isDefined = isDefined; - } - - /// - /// Creates an undefined value - the field will not be included in the HTTP request. - /// Use this as the default value for optional fields. - /// - /// - /// - /// public Optional<string?> Email { get; set; } = Optional<string?>.Undefined; - /// - /// - public static Optional Undefined => new(default!, false); - - /// - /// Creates a defined value - the field will be included in the HTTP request. - /// The value can be null if T is a nullable type. - /// - /// The value to set. Can be null if T is nullable (e.g., string?, int?). - /// - /// - /// // Set to a value - /// request.Name = Optional<string?>.Of("John"); - /// - /// // Set to null (clears the field) - /// request.Email = Optional<string?>.Of(null); - /// - /// // Or use implicit conversion - /// request.Name = "John"; // Same as Of("John") - /// request.Email = null; // Same as Of(null) - /// - /// - public static Optional Of(T value) => new(value, true); - - /// - /// Returns true if the field is defined (set), even if the value is null. - /// Use this to determine if the field should be included in the HTTP request. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// requestBody["name"] = request.Name.Value; // Include in request (can be null) - /// } - /// - /// - public bool IsDefined => _isDefined; - - /// - /// Returns true if the field is undefined (not set). - /// Use this to check if the field should be excluded from the HTTP request. - /// - /// - /// - /// if (request.Email.IsUndefined) - /// { - /// // Don't include email in the request - leave it unchanged - /// } - /// - /// - public bool IsUndefined => !_isDefined; - - /// - /// Gets the value. The value may be null if T is a nullable type. - /// - /// Thrown if the value is undefined. - /// - /// Always check before accessing Value, or use instead. - /// - /// - /// - /// if (request.Name.IsDefined) - /// { - /// string? name = request.Name.Value; // Safe - can be null if Optional<string?> - /// } - /// - /// // Or check for null explicitly - /// if (request.Email.IsDefined && request.Email.Value is null) - /// { - /// // Email is explicitly set to null (clear it) - /// } - /// - /// - public T Value - { - get - { - if (!_isDefined) - throw new InvalidOperationException("Optional value is undefined"); - return _value; - } - } - - /// - /// Gets the value if defined, otherwise returns the specified default value. - /// Note: If the value is defined as null, this returns null (not the default). - /// - /// The value to return if undefined. - /// The actual value if defined (can be null), otherwise the default value. - /// - /// - /// string name = request.Name.GetValueOrDefault("Anonymous"); - /// // If Name is undefined: returns "Anonymous" - /// // If Name is Of(null): returns null - /// // If Name is Of("John"): returns "John" - /// - /// - public T GetValueOrDefault(T defaultValue = default!) - { - return _isDefined ? _value : defaultValue; - } - - /// - /// Tries to get the value. Returns true if the value is defined (even if null). - /// - /// - /// When this method returns, contains the value if defined, or default(T) if undefined. - /// The value may be null if T is nullable. - /// - /// True if the value is defined; otherwise, false. - /// - /// - /// if (request.Email.TryGetValue(out var email)) - /// { - /// requestBody["email"] = email; // email can be null - /// } - /// else - /// { - /// // Email is undefined - don't include in request - /// } - /// - /// - public bool TryGetValue(out T value) - { - if (_isDefined) - { - value = _value; - return true; - } - value = default!; - return false; - } - - /// - /// Implicitly converts a value to Optional<T>.Of(value). - /// This allows natural assignment: request.Name = "John" instead of request.Name = Optional<string?>.Of("John"). - /// - /// The value to convert (can be null if T is nullable). - public static implicit operator Optional(T value) => Of(value); - - /// - /// Returns a string representation of this Optional value. - /// - /// "Undefined" if not set, or "Defined(value)" if set. - public override string ToString() => _isDefined ? $"Defined({_value})" : "Undefined"; - - /// - /// Gets the boxed value. Returns null if undefined or if the value is null. - /// - public object? GetBoxedValue() - { - if (!_isDefined) - return null; - return _value; - } - - /// - public bool Equals(Optional other) => - _isDefined == other._isDefined && EqualityComparer.Default.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is Optional other && Equals(other); - - /// - public override int GetHashCode() - { - if (!_isDefined) - return 0; - unchecked - { - int hash = 17; - hash = hash * 31 + 1; // _isDefined = true - hash = hash * 31 + (_value is null ? 0 : _value.GetHashCode()); - return hash; - } - } - - /// - /// Determines whether two Optional values are equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are equal; otherwise, false. - public static bool operator ==(Optional left, Optional right) => left.Equals(right); - - /// - /// Determines whether two Optional values are not equal. - /// - /// The first Optional to compare. - /// The second Optional to compare. - /// True if the Optional values are not equal; otherwise, false. - public static bool operator !=(Optional left, Optional right) => !left.Equals(right); -} - -/// -/// Extension methods for Optional to simplify common operations. -/// -public static class OptionalExtensions -{ - /// - /// Adds the value to a dictionary if the optional is defined (even if the value is null). - /// This is useful for building JSON request payloads where null values should be included. - /// - /// The type of the optional value. - /// The optional value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Name.AddTo(dict, "name"); // Adds only if Name.IsDefined - /// request.Email.AddTo(dict, "email"); // Adds only if Email.IsDefined - /// - /// - public static void AddTo( - this Optional optional, - Dictionary dictionary, - string key - ) - { - if (optional.IsDefined) - { - dictionary[key] = optional.Value; - } - } - - /// - /// Executes an action if the optional is defined. - /// - /// The type of the optional value. - /// The optional value. - /// The action to execute with the value. - /// - /// - /// request.Name.IfDefined(name => Console.WriteLine($"Name: {name}")); - /// - /// - public static void IfDefined(this Optional optional, Action action) - { - if (optional.IsDefined) - { - action(optional.Value); - } - } - - /// - /// Maps the value to a new type if the optional is defined, otherwise returns undefined. - /// - /// The type of the original value. - /// The type to map to. - /// The optional value to map. - /// The mapping function. - /// An optional containing the mapped value if defined, otherwise undefined. - /// - /// - /// Optional<string?> name = Optional<string?>.Of("John"); - /// Optional<int> length = name.Map(n => n?.Length ?? 0); // Optional.Of(4) - /// - /// - public static Optional Map( - this Optional optional, - Func mapper - ) - { - return optional.IsDefined - ? Optional.Of(mapper(optional.Value)) - : Optional.Undefined; - } - - /// - /// Adds a nullable value to a dictionary only if it is not null. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The type of the value (must be a reference type or Nullable). - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Description.AddIfNotNull(dict, "description"); // Only adds if not null - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if not null - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : class - { - if (value is not null) - { - dictionary[key] = value; - } - } - - /// - /// Adds a nullable value type to a dictionary only if it has a value. - /// This is useful for regular nullable properties where null means "omit from request". - /// - /// The underlying value type. - /// The nullable value to add. - /// The dictionary to add to. - /// The key to use in the dictionary. - /// - /// - /// var dict = new Dictionary<string, object?>(); - /// request.Age.AddIfNotNull(dict, "age"); // Only adds if HasValue - /// request.Score.AddIfNotNull(dict, "score"); // Only adds if HasValue - /// - /// - public static void AddIfNotNull( - this T? value, - Dictionary dictionary, - string key - ) - where T : struct - { - if (value.HasValue) - { - dictionary[key] = value.Value; - } - } -} - -/// -/// JSON converter factory for Optional that handles undefined vs null correctly. -/// Uses a TypeInfoResolver to conditionally include/exclude properties based on Optional.IsDefined. -/// -public class OptionalJsonConverterFactory : JsonConverterFactory -{ - public override bool CanConvert(global::System.Type typeToConvert) - { - if (!typeToConvert.IsGenericType) - return false; - - return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>); - } - - public override JsonConverter? CreateConverter( - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - var valueType = typeToConvert.GetGenericArguments()[0]; - var converterType = typeof(OptionalJsonConverter<>).MakeGenericType(valueType); - return (JsonConverter?)global::System.Activator.CreateInstance(converterType); - } -} - -/// -/// JSON converter for Optional that unwraps the value during serialization. -/// The actual property skipping is handled by the OptionalTypeInfoResolver. -/// -public class OptionalJsonConverter : JsonConverter> -{ - public override Optional Read( - ref Utf8JsonReader reader, - global::System.Type typeToConvert, - JsonSerializerOptions options - ) - { - if (reader.TokenType == JsonTokenType.Null) - { - return Optional.Of(default!); - } - - var value = JsonSerializer.Deserialize(ref reader, options); - return Optional.Of(value!); - } - - public override void Write( - Utf8JsonWriter writer, - Optional value, - JsonSerializerOptions options - ) - { - // This will be called by the serializer - // We need to unwrap and serialize the inner value - // The TypeInfoResolver will handle skipping undefined values - - if (value.IsUndefined) - { - // This shouldn't be called for undefined values due to ShouldSerialize - // But if it is, write null and let the resolver filter it - writer.WriteNullValue(); - return; - } - - // Get the inner value - var innerValue = value.Value; - - // Write null directly if the value is null (don't use JsonSerializer.Serialize for null) - if (innerValue is null) - { - writer.WriteNullValue(); - return; - } - - // Serialize the unwrapped value - JsonSerializer.Serialize(writer, innerValue, options); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs deleted file mode 100644 index 4c4c4073a0ae..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/OptionalAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SeedApi.Core; - -/// -/// Marks a property as optional in the OpenAPI specification. -/// Optional properties use the Optional type and can be undefined (not present in JSON). -/// -/// -/// Properties marked with [Optional] should use the Optional type: -/// - Undefined: Optional.Undefined → omitted from JSON -/// - Defined: Optional.Of(value) → written to JSON -/// -/// Combine with [Nullable] to allow null values: -/// - [Optional, Nullable] Optional → can be undefined, null, or a value -/// - [Optional] Optional → can be undefined or a value (null is invalid) -/// -[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)] -public class OptionalAttribute : global::System.Attribute { } diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs deleted file mode 100644 index 8b43322350bd..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/AdditionalProperties.cs +++ /dev/null @@ -1,353 +0,0 @@ -using global::System.Collections; -using global::System.Collections.ObjectModel; -using global::System.Text.Json; -using global::System.Text.Json.Nodes; -using SeedApi.Core; - -namespace SeedApi; - -public record ReadOnlyAdditionalProperties : ReadOnlyAdditionalProperties -{ - internal ReadOnlyAdditionalProperties() { } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record ReadOnlyAdditionalProperties : IReadOnlyDictionary -{ - private readonly Dictionary _extensionData = new(); - private readonly Dictionary _convertedCache = new(); - - internal ReadOnlyAdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - internal ReadOnlyAdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - if (kvp.Value is JsonElement element) - { - _extensionData.Add(kvp.Key, element); - } - else - { - _extensionData[kvp.Key] = JsonUtils.SerializeToElement(kvp.Value); - } - - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(JsonElement value) - { - if (typeof(T) == typeof(JsonElement)) - { - return (T)(object)value; - } - - return value.Deserialize(JsonOptions.JsonSerializerOptions)!; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var cached)) - { - return cached; - } - - var value = ConvertToT(_extensionData[key]); - _convertedCache[key] = value; - return value; - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public int Count => _extensionData.Count; - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var element)) - { - value = ConvertToT(element); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public T this[string key] => GetCached(key); - - public IEnumerable Keys => _extensionData.Keys; - - public IEnumerable Values => Keys.Select(GetCached); -} - -public record AdditionalProperties : AdditionalProperties -{ - public AdditionalProperties() { } - - public AdditionalProperties(IDictionary properties) - : base(properties) { } -} - -public record AdditionalProperties : IDictionary -{ - private readonly Dictionary _extensionData; - private readonly Dictionary _convertedCache; - - public AdditionalProperties() - { - _extensionData = new Dictionary(); - _convertedCache = new Dictionary(); - } - - public AdditionalProperties(IDictionary properties) - { - _extensionData = new Dictionary(properties.Count); - _convertedCache = new Dictionary(properties.Count); - foreach (var kvp in properties) - { - _extensionData[kvp.Key] = kvp.Value; - _convertedCache[kvp.Key] = kvp.Value; - } - } - - private static T ConvertToT(object? extensionDataValue) - { - return extensionDataValue switch - { - T value => value, - JsonElement jsonElement => jsonElement.Deserialize( - JsonOptions.JsonSerializerOptions - )!, - JsonNode jsonNode => jsonNode.Deserialize(JsonOptions.JsonSerializerOptions)!, - _ => JsonUtils - .SerializeToElement(extensionDataValue) - .Deserialize(JsonOptions.JsonSerializerOptions)!, - }; - } - - internal void CopyFromExtensionData(IDictionary extensionData) - { - _extensionData.Clear(); - _convertedCache.Clear(); - foreach (var kvp in extensionData) - { - _extensionData[kvp.Key] = kvp.Value; - if (kvp.Value is T value) - { - _convertedCache[kvp.Key] = value; - } - } - } - - internal void CopyToExtensionData(IDictionary extensionData) - { - extensionData.Clear(); - foreach (var kvp in _extensionData) - { - extensionData[kvp.Key] = kvp.Value; - } - } - - public JsonObject ToJsonObject() => - ( - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ) - ).AsObject(); - - public JsonNode ToJsonNode() => - JsonUtils.SerializeToNode(_extensionData) - ?? throw new InvalidOperationException( - "Failed to serialize AdditionalProperties to JSON Node." - ); - - public JsonElement ToJsonElement() => JsonUtils.SerializeToElement(_extensionData); - - public JsonDocument ToJsonDocument() => JsonUtils.SerializeToDocument(_extensionData); - - public IReadOnlyDictionary ToJsonElementDictionary() - { - return new ReadOnlyDictionary( - _extensionData.ToDictionary( - kvp => kvp.Key, - kvp => - { - if (kvp.Value is JsonElement jsonElement) - { - return jsonElement; - } - - return JsonUtils.SerializeToElement(kvp.Value); - } - ) - ); - } - - public ICollection Keys => _extensionData.Keys; - - public ICollection Values - { - get - { - var values = new T[_extensionData.Count]; - var i = 0; - foreach (var key in Keys) - { - values[i++] = GetCached(key); - } - - return values; - } - } - - private T GetCached(string key) - { - if (_convertedCache.TryGetValue(key, out var value)) - { - return value; - } - - value = ConvertToT(_extensionData[key]); - _convertedCache.Add(key, value); - return value; - } - - private void SetCached(string key, T value) - { - _extensionData[key] = value; - _convertedCache[key] = value; - } - - private void AddCached(string key, T value) - { - _extensionData.Add(key, value); - _convertedCache.Add(key, value); - } - - private bool RemoveCached(string key) - { - var isRemoved = _extensionData.Remove(key); - _convertedCache.Remove(key); - return isRemoved; - } - - public int Count => _extensionData.Count; - public bool IsReadOnly => false; - - public T this[string key] - { - get => GetCached(key); - set => SetCached(key, value); - } - - public void Add(string key, T value) => AddCached(key, value); - - public void Add(KeyValuePair item) => AddCached(item.Key, item.Value); - - public bool Remove(string key) => RemoveCached(key); - - public bool Remove(KeyValuePair item) => RemoveCached(item.Key); - - public bool ContainsKey(string key) => _extensionData.ContainsKey(key); - - public bool Contains(KeyValuePair item) - { - return _extensionData.ContainsKey(item.Key) - && EqualityComparer.Default.Equals(GetCached(item.Key), item.Value); - } - - public bool TryGetValue(string key, out T value) - { - if (_convertedCache.TryGetValue(key, out value!)) - { - return true; - } - - if (_extensionData.TryGetValue(key, out var extensionDataValue)) - { - value = ConvertToT(extensionDataValue); - _convertedCache[key] = value; - return true; - } - - return false; - } - - public void Clear() - { - _extensionData.Clear(); - _convertedCache.Clear(); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array is null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (arrayIndex < 0 || arrayIndex > array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } - - if (array.Length - arrayIndex < _extensionData.Count) - { - throw new ArgumentException( - "The array does not have enough space to copy the elements." - ); - } - - foreach (var kvp in _extensionData) - { - array[arrayIndex++] = new KeyValuePair(kvp.Key, GetCached(kvp.Key)); - } - } - - public IEnumerator> GetEnumerator() - { - return _extensionData - .Select(kvp => new KeyValuePair(kvp.Key, GetCached(kvp.Key))) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs deleted file mode 100644 index 837716a987f2..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/ClientOptions.cs +++ /dev/null @@ -1,84 +0,0 @@ -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public partial class ClientOptions -{ - /// - /// The http headers sent with the request. - /// - internal Headers Headers { get; init; } = new(); - - /// - /// The Base URL for the API. - /// - public string BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = SeedApiEnvironment.Default; - - /// - /// The http client used to make requests. - /// - public HttpClient HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = new HttpClient(); - - /// - /// Additional headers to be sent with HTTP requests. - /// Headers with matching keys will be overwritten by headers set on the request. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = []; - - /// - /// The max number of retries to attempt. - /// - public int MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = 2; - - /// - /// The timeout for the request. - /// - public TimeSpan Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = TimeSpan.FromSeconds(30); - - /// - /// Clones this and returns a new instance - /// - internal ClientOptions Clone() - { - return new ClientOptions - { - BaseUrl = BaseUrl, - HttpClient = HttpClient, - MaxRetries = MaxRetries, - Timeout = Timeout, - Headers = new Headers(new Dictionary(Headers)), - AdditionalHeaders = AdditionalHeaders, - }; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs deleted file mode 100644 index f33d49028884..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/FileParameter.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace SeedApi; - -/// -/// File parameter for uploading files. -/// -public record FileParameter : IDisposable -#if NET6_0_OR_GREATER - , IAsyncDisposable -#endif -{ - private bool _disposed; - - /// - /// The name of the file to be uploaded. - /// - public string? FileName { get; set; } - - /// - /// The content type of the file to be uploaded. - /// - public string? ContentType { get; set; } - - /// - /// The content of the file to be uploaded. - /// - public required Stream Stream { get; set; } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - if (disposing) - { - Stream.Dispose(); - } - - _disposed = true; - } - -#if NET6_0_OR_GREATER - /// - public async ValueTask DisposeAsync() - { - if (!_disposed) - { - await Stream.DisposeAsync().ConfigureAwait(false); - _disposed = true; - } - - GC.SuppressFinalize(this); - } -#endif - - public static implicit operator FileParameter(Stream stream) => new() { Stream = stream }; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs deleted file mode 100644 index f711c7f774d2..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RawResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using global::System.Net; - -namespace SeedApi; - -/// -/// Contains HTTP response metadata including status code, URL, and headers. -/// -public record RawResponse -{ - /// - /// The HTTP status code of the response. - /// - public required HttpStatusCode StatusCode { get; init; } - - /// - /// The request URL that generated this response. - /// - public required Uri Url { get; init; } - - /// - /// The HTTP response headers. - /// - public required Core.ResponseHeaders Headers { get; init; } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs deleted file mode 100644 index ea7db76b032f..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/RequestOptions.cs +++ /dev/null @@ -1,86 +0,0 @@ -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public partial class RequestOptions : IRequestOptions -{ - /// - /// The Base URL for the API. - /// - public string? BaseUrl { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The http client used to make requests. - /// - public HttpClient? HttpClient { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional headers to be sent with the request. - /// Headers previously set with matching keys will be overwritten. - /// - public IEnumerable> AdditionalHeaders { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = []; - - /// - /// The max number of retries to attempt. - /// - public int? MaxRetries { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// The timeout for the request. - /// - public TimeSpan? Timeout { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } - - /// - /// Additional query parameters sent with the request. - /// - public IEnumerable> AdditionalQueryParameters { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } = Enumerable.Empty>(); - - /// - /// Additional body properties sent with the request. - /// This is only applied to JSON requests. - /// - public object? AdditionalBodyProperties { get; -#if NET5_0_OR_GREATER - init; -#else - set; -#endif - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs deleted file mode 100644 index 94afb43fa0a3..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiApiException.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace SeedApi; - -/// -/// This exception type will be thrown for any non-2XX API responses. -/// -public class SeedApiApiException( - string message, - int statusCode, - object body, - Exception? innerException = null -) : SeedApiException(message, innerException) -{ - /// - /// The error code of the response that triggered the exception. - /// - public int StatusCode => statusCode; - - /// - /// The body of the response that triggered the exception. - /// - public object Body => body; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs deleted file mode 100644 index d30f17ce0829..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiEnvironment.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -[Serializable] -public class SeedApiEnvironment -{ - public const string Default = "https://api.example.com"; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs deleted file mode 100644 index 90e03e71e695..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/SeedApiException.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -/// -/// Base exception class for all exceptions thrown by the SDK. -/// -public class SeedApiException(string message, Exception? innerException = null) - : Exception(message, innerException); diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs deleted file mode 100644 index 3d210b7e0b4c..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/Version.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeedApi; - -[Serializable] -internal class Version -{ - public const string Current = "0.0.1"; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs deleted file mode 100644 index 4c173fb2c115..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponse.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace SeedApi; - -/// -/// Wraps a parsed response value with its raw HTTP response metadata. -/// -/// The type of the parsed response data. -public readonly struct WithRawResponse -{ - /// - /// The parsed response data. - /// - public required T Data { get; init; } - - /// - /// The raw HTTP response metadata. - /// - public required RawResponse RawResponse { get; init; } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs deleted file mode 100644 index 7c939859cb9f..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/Public/WithRawResponseTask.cs +++ /dev/null @@ -1,144 +0,0 @@ -using global::System.Runtime.CompilerServices; - -namespace SeedApi; - -/// -/// A task-like type that wraps Task<WithRawResponse<T>> and provides dual-mode awaiting: -/// - Direct await yields just T (zero-allocation path for common case) -/// - .WithRawResponse() yields WithRawResponse<T> (when raw response metadata is needed) -/// -/// The type of the parsed response data. -public readonly struct WithRawResponseTask -{ - private readonly global::System.Threading.Tasks.Task> _task; - - /// - /// Creates a new WithRawResponseTask wrapping the given task. - /// - public WithRawResponseTask(global::System.Threading.Tasks.Task> task) - { - _task = task; - } - - /// - /// Returns the underlying task that yields both the data and raw response metadata. - /// - public global::System.Threading.Tasks.Task> WithRawResponse() => _task; - - /// - /// Gets the custom awaiter that unwraps to just T when awaited. - /// - public Awaiter GetAwaiter() => new(_task.GetAwaiter()); - - /// - /// Configures the awaiter to continue on the captured context or not. - /// - public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => - new(_task.ConfigureAwait(continueOnCapturedContext)); - - /// - /// Implicitly converts WithRawResponseTask<T> to global::System.Threading.Tasks.Task<T> for backward compatibility. - /// The resulting task will yield just the data when awaited. - /// - public static implicit operator global::System.Threading.Tasks.Task( - WithRawResponseTask task - ) - { - return task._task.ContinueWith( - t => t.Result.Data, - TaskContinuationOptions.ExecuteSynchronously - ); - } - - /// - /// Custom awaiter that unwraps WithRawResponse<T> to just T. - /// - public readonly struct Awaiter : ICriticalNotifyCompletion - { - private readonly TaskAwaiter> _awaiter; - - internal Awaiter(TaskAwaiter> awaiter) - { - _awaiter = awaiter; - } - - /// - /// Gets whether the underlying task has completed. - /// - public bool IsCompleted => _awaiter.IsCompleted; - - /// - /// Gets the result, unwrapping to just the data. - /// - public T GetResult() => _awaiter.GetResult().Data; - - /// - /// Schedules the continuation action. - /// - public void OnCompleted(global::System.Action continuation) => - _awaiter.OnCompleted(continuation); - - /// - /// Schedules the continuation action without capturing the execution context. - /// - public void UnsafeOnCompleted(global::System.Action continuation) => - _awaiter.UnsafeOnCompleted(continuation); - } - - /// - /// Awaitable type returned by ConfigureAwait that unwraps to just T. - /// - public readonly struct ConfiguredTaskAwaitable - { - private readonly ConfiguredTaskAwaitable> _configuredTask; - - internal ConfiguredTaskAwaitable(ConfiguredTaskAwaitable> configuredTask) - { - _configuredTask = configuredTask; - } - - /// - /// Gets the configured awaiter that unwraps to just T. - /// - public ConfiguredAwaiter GetAwaiter() => new(_configuredTask.GetAwaiter()); - - /// - /// Custom configured awaiter that unwraps WithRawResponse<T> to just T. - /// - public readonly struct ConfiguredAwaiter : ICriticalNotifyCompletion - { - private readonly ConfiguredTaskAwaitable< - WithRawResponse - >.ConfiguredTaskAwaiter _awaiter; - - internal ConfiguredAwaiter( - ConfiguredTaskAwaitable>.ConfiguredTaskAwaiter awaiter - ) - { - _awaiter = awaiter; - } - - /// - /// Gets whether the underlying task has completed. - /// - public bool IsCompleted => _awaiter.IsCompleted; - - /// - /// Gets the result, unwrapping to just the data. - /// - public T GetResult() => _awaiter.GetResult().Data; - - /// - /// Schedules the continuation action. - /// - public void OnCompleted(global::System.Action continuation) => - _awaiter.OnCompleted(continuation); - - /// - /// Schedules the continuation action without capturing the execution context. - /// - public void UnsafeOnCompleted(global::System.Action continuation) => - _awaiter.UnsafeOnCompleted(continuation); - } - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs deleted file mode 100644 index 9498488a311b..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringBuilder.cs +++ /dev/null @@ -1,654 +0,0 @@ -using global::System.Buffers; -using global::System.Runtime.CompilerServices; -#if !NET6_0_OR_GREATER -using global::System.Text; -#endif - -namespace SeedApi.Core; - -/// -/// High-performance query string builder with RFC 3986 compliant percent-encoding. -/// Uses span-based APIs on .NET 6+ and StringBuilder fallback for older targets. -/// -/// RFC 3986 defines the following relevant productions: -/// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -/// query = *( pchar / "/" / "?" ) -/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -/// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" -/// -/// Three encoding contexts are distinguished: -/// Path segment (pchar): unreserved + sub-delims + ":" + "@" -/// Query key: query chars minus "&", "=", "+", "#" -/// Query value: query chars minus "&", "+", "#" -/// -internal static class QueryStringBuilder -{ - // ────────────────────────────────────────────────────────────────────── - // RFC 3986 character sets - // - // Query key safe: unreserved + (sub-delims \ {& = +}) + : @ / ? - // Query value safe: unreserved + (sub-delims \ {& +}) + : @ / ? - // Path segment safe: unreserved + sub-delims + : @ - // ────────────────────────────────────────────────────────────────────── - -#if NET8_0_OR_GREATER - private static readonly SearchValues SafeQueryKeyChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?" - ); - - private static readonly SearchValues SafeQueryValueChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?" - ); - - private static readonly SearchValues SafePathChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@" - ); -#else - private const string SafeQueryKeyChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?"; - - private const string SafeQueryValueChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?"; - - private const string SafePathChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@"; -#endif - -#if NET7_0_OR_GREATER - private static ReadOnlySpan UpperHexChars => "0123456789ABCDEF"u8; -#else - private static readonly byte[] UpperHexChars = - { - (byte)'0', - (byte)'1', - (byte)'2', - (byte)'3', - (byte)'4', - (byte)'5', - (byte)'6', - (byte)'7', - (byte)'8', - (byte)'9', - (byte)'A', - (byte)'B', - (byte)'C', - (byte)'D', - (byte)'E', - (byte)'F', - }; -#endif - - private enum EncodingContext - { - QueryKey, - QueryValue, - Path, - } - - /// - /// Percent-encodes a path segment value per RFC 3986 section 3.3 (pchar). - /// Allowed unencoded: unreserved / sub-delims / ":" / "@" - /// - public static string EncodePathSegment(string value) - { - if (string.IsNullOrEmpty(value)) - return value; - -#if NET6_0_OR_GREATER - if (!NeedsEncoding(value.AsSpan(), EncodingContext.Path)) - return value; - - var buffer = ArrayPool.Shared.Rent(value.Length * 3); - try - { - var written = EncodeSlow(value.AsSpan(), buffer.AsSpan(), EncodingContext.Path); - return new string(buffer.AsSpan(0, written)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } -#else - var sb = new StringBuilder(value.Length); - AppendEncoded(sb, value, EncodingContext.Path); - return sb.ToString(); -#endif - } - - /// - /// Builds a query string from the provided parameters. - /// -#if NET6_0_OR_GREATER - public static string Build(ReadOnlySpan> parameters) - { - if (parameters.IsEmpty) - return string.Empty; - - var estimatedLength = EstimateLength(parameters); - if (estimatedLength == 0) - return string.Empty; - - var bufferSize = Math.Min(estimatedLength * 3, 8192); - var buffer = ArrayPool.Shared.Rent(bufferSize); - - try - { - var written = BuildCore(parameters, buffer); - return new string(buffer.AsSpan(0, written)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - private static int EstimateLength(ReadOnlySpan> parameters) - { - var estimatedLength = 0; - foreach (var kvp in parameters) - { - estimatedLength += kvp.Key.Length + kvp.Value.Length + 2; - } - return estimatedLength; - } -#endif - - /// - /// Builds a query string from the provided parameters. - /// - public static string Build(IEnumerable> parameters) - { -#if NET6_0_OR_GREATER - // Try to get span access for collections that support it - if (parameters is ICollection> collection) - { - if (collection.Count == 0) - return string.Empty; - - var array = ArrayPool>.Shared.Rent(collection.Count); - try - { - collection.CopyTo(array, 0); - return Build(array.AsSpan(0, collection.Count)); - } - finally - { - ArrayPool>.Shared.Return(array); - } - } - - // Fallback for non-collection enumerables - using var enumerator = parameters.GetEnumerator(); - if (!enumerator.MoveNext()) - return string.Empty; - - var buffer = ArrayPool.Shared.Rent(4096); - try - { - var position = 0; - var first = true; - - do - { - var kvp = enumerator.Current; - - // Ensure capacity (worst case: 3x for encoding + separators) - var required = (kvp.Key.Length + kvp.Value.Length + 2) * 3; - if (position + required > buffer.Length) - { - var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); - buffer.AsSpan(0, position).CopyTo(newBuffer); - ArrayPool.Shared.Return(buffer); - buffer = newBuffer; - } - - buffer[position++] = first ? '?' : '&'; - first = false; - - position += EncodeWithCharSet( - kvp.Key.AsSpan(), - buffer.AsSpan(position), - EncodingContext.QueryKey - ); - buffer[position++] = '='; - position += EncodeWithCharSet( - kvp.Value.AsSpan(), - buffer.AsSpan(position), - EncodingContext.QueryValue - ); - } while (enumerator.MoveNext()); - - return first ? string.Empty : new string(buffer.AsSpan(0, position)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } -#else - // netstandard2.0 / net462 fallback using StringBuilder - var sb = new StringBuilder(); - var first = true; - - foreach (var kvp in parameters) - { - sb.Append(first ? '?' : '&'); - first = false; - - AppendEncoded(sb, kvp.Key, EncodingContext.QueryKey); - sb.Append('='); - AppendEncoded(sb, kvp.Value, EncodingContext.QueryValue); - } - - return sb.ToString(); -#endif - } - -#if NET6_0_OR_GREATER - private static int BuildCore( - ReadOnlySpan> parameters, - Span buffer - ) - { - var position = 0; - var first = true; - - foreach (var kvp in parameters) - { - buffer[position++] = first ? '?' : '&'; - first = false; - - position += EncodeWithCharSet( - kvp.Key.AsSpan(), - buffer.Slice(position), - EncodingContext.QueryKey - ); - buffer[position++] = '='; - position += EncodeWithCharSet( - kvp.Value.AsSpan(), - buffer.Slice(position), - EncodingContext.QueryValue - ); - } - - return position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int EncodeWithCharSet( - ReadOnlySpan input, - Span output, - EncodingContext context - ) - { - if (!NeedsEncoding(input, context)) - { - input.CopyTo(output); - return input.Length; - } - - return EncodeSlow(input, output, context); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool NeedsEncoding(ReadOnlySpan value, EncodingContext context) - { - return context switch - { - EncodingContext.QueryKey => value.ContainsAnyExcept(SafeQueryKeyChars), - EncodingContext.QueryValue => value.ContainsAnyExcept(SafeQueryValueChars), - EncodingContext.Path => value.ContainsAnyExcept(SafePathChars), - _ => true, - }; - } - - private static int EncodeSlow( - ReadOnlySpan input, - Span output, - EncodingContext context - ) - { - var position = 0; - - foreach (var c in input) - { - if (IsSafeChar(c, context)) - { - output[position++] = c; - } - else if (c == ' ') - { - output[position++] = '%'; - output[position++] = '2'; - output[position++] = '0'; - } - else if (char.IsAscii(c)) - { - position += EncodeAscii((byte)c, output.Slice(position)); - } - else - { - position += EncodeUtf8(c, output.Slice(position)); - } - } - - return position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int EncodeAscii(byte value, Span output) - { - output[0] = '%'; - output[1] = (char)UpperHexChars[value >> 4]; - output[2] = (char)UpperHexChars[value & 0xF]; - return 3; - } - - private static int EncodeUtf8(char c, Span output) - { - Span utf8Bytes = stackalloc byte[4]; - Span singleChar = stackalloc char[1] { c }; - var byteCount = global::System.Text.Encoding.UTF8.GetBytes(singleChar, utf8Bytes); - - var position = 0; - for (var i = 0; i < byteCount; i++) - { - output[position++] = '%'; - output[position++] = (char)UpperHexChars[utf8Bytes[i] >> 4]; - output[position++] = (char)UpperHexChars[utf8Bytes[i] & 0xF]; - } - - return position; - } -#else - // netstandard2.0 / net462 StringBuilder-based encoding - private static void AppendEncoded(StringBuilder sb, string value, EncodingContext context) - { - foreach (var c in value) - { - if (IsSafeChar(c, context)) - { - sb.Append(c); - } - else if (c == ' ') - { - sb.Append("%20"); - } - else if (c <= 127) - { - AppendPercentEncoded(sb, (byte)c); - } - else - { - var bytes = Encoding.UTF8.GetBytes(new[] { c }); - foreach (var b in bytes) - { - AppendPercentEncoded(sb, b); - } - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AppendPercentEncoded(StringBuilder sb, byte value) - { - sb.Append('%'); - sb.Append((char)UpperHexChars[value >> 4]); - sb.Append((char)UpperHexChars[value & 0xF]); - } -#endif - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeChar(char c, EncodingContext context) - { - return context switch - { - EncodingContext.QueryKey => IsSafeQueryKeyChar(c), - EncodingContext.QueryValue => IsSafeQueryValueChar(c), - EncodingContext.Path => IsSafePathChar(c), - _ => false, - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeQueryKeyChar(char c) - { -#if NET8_0_OR_GREATER - return SafeQueryKeyChars.Contains(c); -#else - // query = *( pchar / "/" / "?" ) minus "&", "=", "+", "#" - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') - || c == '-' - || c == '_' - || c == '.' - || c == '~' - || c == '!' - || c == '$' - || c == (char)39 // single quote - || c == '(' - || c == ')' - || c == '*' - || c == ',' - || c == ';' - || c == ':' - || c == '@' - || c == '/' - || c == '?'; -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeQueryValueChar(char c) - { -#if NET8_0_OR_GREATER - return SafeQueryValueChars.Contains(c); -#else - // query = *( pchar / "/" / "?" ) minus "&", "+", "#" - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') - || c == '-' - || c == '_' - || c == '.' - || c == '~' - || c == '!' - || c == '$' - || c == (char)39 // single quote - || c == '(' - || c == ')' - || c == '*' - || c == ',' - || c == ';' - || c == '=' - || c == ':' - || c == '@' - || c == '/' - || c == '?'; -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafePathChar(char c) - { -#if NET8_0_OR_GREATER - return SafePathChars.Contains(c); -#else - // pchar = unreserved / sub-delims / ":" / "@" - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') - || c == '-' - || c == '_' - || c == '.' - || c == '~' - || c == '!' - || c == '$' - || c == '&' - || c == (char)39 // single quote - || c == '(' - || c == ')' - || c == '*' - || c == '+' - || c == ',' - || c == ';' - || c == '=' - || c == ':' - || c == '@'; -#endif - } - - /// - /// Fluent builder for constructing query strings with support for simple parameters and deep object notation. - /// - public sealed class Builder - { - private readonly List> _params; - - /// - /// Initializes a new instance with default capacity. - /// - public Builder() - { - _params = new List>(); - } - - /// - /// Initializes a new instance with the specified initial capacity. - /// - public Builder(int capacity) - { - _params = new List>(capacity); - } - - /// - /// Adds a simple parameter. For collections, adds multiple key-value pairs (one per element). - /// - public Builder Add(string key, object? value) - { - if (value is null) - { - return this; - } - - // Handle string separately since it implements IEnumerable - if (value is string stringValue) - { - _params.Add(new KeyValuePair(key, stringValue)); - return this; - } - - // Handle collections (arrays, lists, etc.) - add each element as a separate key-value pair - if ( - value - is global::System.Collections.IEnumerable enumerable - and not global::System.Collections.IDictionary - ) - { - foreach (var item in enumerable) - { - if (item is not null) - { - _params.Add( - new KeyValuePair( - key, - ValueConvert.ToQueryStringValue(item) - ) - ); - } - } - return this; - } - - // Handle scalar values - _params.Add( - new KeyValuePair(key, ValueConvert.ToQueryStringValue(value)) - ); - return this; - } - - /// - /// Sets a parameter, removing any existing parameters with the same key before adding the new value. - /// For collections, removes all existing parameters with the key, then adds multiple key-value pairs (one per element). - /// This allows overriding parameters set earlier in the builder. - /// - public Builder Set(string key, object? value) - { - // Remove all existing parameters with this key - _params.RemoveAll(kv => kv.Key == key); - - // Add the new value(s) - return Add(key, value); - } - - /// - /// Merges additional query parameters with override semantics. - /// Groups parameters by key and calls Set() once per unique key. - /// This ensures that parameters with the same key are properly merged: - /// - If a key appears once, it's added as a single value - /// - If a key appears multiple times, all values are added as an array - /// - All parameters override any existing parameters with the same key - /// - public Builder MergeAdditional( - global::System.Collections.Generic.IEnumerable>? additionalParameters - ) - { - if (additionalParameters is null) - { - return this; - } - - // Group by key to handle multiple values for the same key correctly - var grouped = additionalParameters - .GroupBy(kv => kv.Key) - .Select(g => new global::System.Collections.Generic.KeyValuePair( - g.Key, - g.Count() == 1 ? (object)g.First().Value : g.Select(kv => kv.Value).ToArray() - )); - - foreach (var param in grouped) - { - Set(param.Key, param.Value); - } - - return this; - } - - /// - /// Adds a complex object using deep object notation with a prefix. - /// Deep object notation nests properties with brackets: prefix[key][nested]=value - /// - public Builder AddDeepObject(string prefix, object? value) - { - if (value is not null) - { - _params.AddRange(QueryStringConverter.ToDeepObject(prefix, value)); - } - return this; - } - - /// - /// Adds a complex object using exploded form notation with an optional prefix. - /// Exploded form flattens properties: prefix[key]=value (no deep nesting). - /// - public Builder AddExploded(string prefix, object? value) - { - if (value is not null) - { - _params.AddRange(QueryStringConverter.ToExplodedForm(prefix, value)); - } - return this; - } - - /// - /// Builds the final query string. - /// - public string Build() - { - return QueryStringBuilder.Build(_params); - } - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs deleted file mode 100644 index be39e2f51aa4..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/QueryStringConverter.cs +++ /dev/null @@ -1,259 +0,0 @@ -using global::System.Text.Json; - -namespace SeedApi.Core; - -/// -/// Converts an object into a query string collection. -/// -internal static class QueryStringConverter -{ - /// - /// Converts an object into a query string collection using Deep Object notation with a prefix. - /// - /// The prefix to prepend to all keys (e.g., "session_settings"). Pass empty string for no prefix. - /// Object to form URL-encode. Can be an object, array of objects, or dictionary. - /// Throws when passing in a string or primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToDeepObject( - string prefix, - object value - ) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - JsonToDeepObject(json, prefix, queryCollection); - return queryCollection; - } - - /// - /// Converts an object into a query string collection using Deep Object notation. - /// - /// Object to form URL-encode. Can be an object, array of objects, or dictionary. - /// Throws when passing in a string or primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToDeepObject(object value) - { - return ToDeepObject("", value); - } - - /// - /// Converts an object into a query string collection using Exploded Form notation with a prefix. - /// - /// The prefix to prepend to all keys. Pass empty string for no prefix. - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToExplodedForm( - string prefix, - object value - ) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - AssertRootJson(json); - JsonToFormExploded(json, prefix, queryCollection); - return queryCollection; - } - - /// - /// Converts an object into a query string collection using Exploded Form notation. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToExplodedForm(object value) - { - return ToExplodedForm("", value); - } - - /// - /// Converts an object into a query string collection using Form notation without exploding parameters. - /// - /// Object to form URL-encode. You can pass in an object or dictionary, but not lists, strings, or primitives. - /// Throws when passing in a list, a string, or a primitive value. - /// A collection of key value pairs. The keys and values are not URL encoded. - internal static IEnumerable> ToForm(object value) - { - var queryCollection = new List>(); - var json = JsonUtils.SerializeToElement(value); - AssertRootJson(json); - JsonToForm(json, "", queryCollection); - return queryCollection; - } - - private static void AssertRootJson(JsonElement json) - { - switch (json.ValueKind) - { - case JsonValueKind.Object: - break; - case JsonValueKind.Array: - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - case JsonValueKind.Null: - default: - throw new global::System.Exception( - $"Only objects can be converted to query string collections. Given type is {json.ValueKind}." - ); - } - } - - private static void JsonToForm( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToForm(property.Value, newPrefix, parameters); - } - break; - case JsonValueKind.Array: - var arrayValues = element.EnumerateArray().Select(ValueToString).ToArray(); - parameters.Add( - new KeyValuePair(prefix, string.Join(",", arrayValues)) - ); - break; - case JsonValueKind.Null: - break; - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static void JsonToFormExploded( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToFormExploded(property.Value, newPrefix, parameters); - } - - break; - case JsonValueKind.Array: - foreach (var item in element.EnumerateArray()) - { - if ( - item.ValueKind != JsonValueKind.Object - && item.ValueKind != JsonValueKind.Array - ) - { - parameters.Add( - new KeyValuePair(prefix, ValueToString(item)) - ); - } - else - { - JsonToFormExploded(item, prefix, parameters); - } - } - - break; - case JsonValueKind.Null: - break; - case JsonValueKind.Undefined: - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static void JsonToDeepObject( - JsonElement element, - string prefix, - List> parameters - ) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - foreach (var property in element.EnumerateObject()) - { - var newPrefix = string.IsNullOrEmpty(prefix) - ? property.Name - : $"{prefix}[{property.Name}]"; - - JsonToDeepObject(property.Value, newPrefix, parameters); - } - - break; - case JsonValueKind.Array: - var index = 0; - foreach (var item in element.EnumerateArray()) - { - var newPrefix = $"{prefix}[{index++}]"; - - if ( - item.ValueKind != JsonValueKind.Object - && item.ValueKind != JsonValueKind.Array - ) - { - parameters.Add( - new KeyValuePair(newPrefix, ValueToString(item)) - ); - } - else - { - JsonToDeepObject(item, newPrefix, parameters); - } - } - - break; - case JsonValueKind.Null: - case JsonValueKind.Undefined: - // Skip null and undefined values - don't add parameters for them - break; - case JsonValueKind.String: - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - default: - parameters.Add(new KeyValuePair(prefix, ValueToString(element))); - break; - } - } - - private static string ValueToString(JsonElement element) - { - return element.ValueKind switch - { - JsonValueKind.String => element.GetString() ?? "", - JsonValueKind.Number => element.GetRawText(), - JsonValueKind.True => "true", - JsonValueKind.False => "false", - JsonValueKind.Null => "", - _ => element.GetRawText(), - }; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs deleted file mode 100644 index edf973008a15..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/RawClient.cs +++ /dev/null @@ -1,343 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; -using global::System.Text; -using SystemTask = global::System.Threading.Tasks.Task; - -namespace SeedApi.Core; - -/// -/// Utility class for making raw HTTP requests to the API. -/// -internal partial class RawClient(ClientOptions clientOptions) -{ - private const int MaxRetryDelayMs = 60000; - private const double JitterFactor = 0.2; -#if NET6_0_OR_GREATER - // Use Random.Shared for thread-safe random number generation on .NET 6+ -#else - private static readonly object JitterLock = new(); - private static readonly Random JitterRandom = new(); -#endif - internal int BaseRetryDelay { get; set; } = 1000; - - /// - /// The client options applied on every request. - /// - internal readonly ClientOptions Options = clientOptions; - - internal async global::System.Threading.Tasks.Task SendRequestAsync( - global::SeedApi.Core.BaseRequest request, - CancellationToken cancellationToken = default - ) - { - // Apply the request timeout. - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var timeout = request.Options?.Timeout ?? Options.Timeout; - cts.CancelAfter(timeout); - - var httpRequest = await CreateHttpRequestAsync(request).ConfigureAwait(false); - // Send the request. - return await SendWithRetriesAsync(httpRequest, request.Options, cts.Token) - .ConfigureAwait(false); - } - - internal async global::System.Threading.Tasks.Task SendRequestAsync( - HttpRequestMessage request, - IRequestOptions? options, - CancellationToken cancellationToken = default - ) - { - // Apply the request timeout. - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var timeout = options?.Timeout ?? Options.Timeout; - cts.CancelAfter(timeout); - - // Send the request. - return await SendWithRetriesAsync(request, options, cts.Token).ConfigureAwait(false); - } - - private static async global::System.Threading.Tasks.Task CloneRequestAsync( - HttpRequestMessage request - ) - { - var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri); - clonedRequest.Version = request.Version; - - if (request.Content != null) - { - switch (request.Content) - { - case MultipartContent oldMultipartFormContent: - var originalBoundary = - oldMultipartFormContent - .Headers.ContentType?.Parameters.First(p => - p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase) - ) - .Value?.Trim('"') ?? Guid.NewGuid().ToString(); - var newMultipartContent = oldMultipartFormContent switch - { - MultipartFormDataContent => new MultipartFormDataContent(originalBoundary), - _ => new MultipartContent(), - }; - foreach (var content in oldMultipartFormContent) - { - var ms = new MemoryStream(); - await content.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - var newPart = new StreamContent(ms); - foreach (var header in content.Headers) - { - newPart.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - newMultipartContent.Add(newPart); - } - - clonedRequest.Content = newMultipartContent; - break; - default: - var bodyStream = new MemoryStream(); - await request.Content.CopyToAsync(bodyStream).ConfigureAwait(false); - bodyStream.Position = 0; - var clonedContent = new StreamContent(bodyStream); - foreach (var header in request.Content.Headers) - { - clonedContent.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - clonedRequest.Content = clonedContent; - break; - } - } - - foreach (var header in request.Headers) - { - clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - return clonedRequest; - } - - /// - /// Sends the request with retries, unless the request content is not retryable, - /// such as stream requests and multipart form data with stream content. - /// - private async global::System.Threading.Tasks.Task SendWithRetriesAsync( - HttpRequestMessage request, - IRequestOptions? options, - CancellationToken cancellationToken - ) - { - var httpClient = options?.HttpClient ?? Options.HttpClient; - var maxRetries = options?.MaxRetries ?? Options.MaxRetries; - var response = await httpClient - .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) - .ConfigureAwait(false); - var isRetryableContent = IsRetryableContent(request); - - if (!isRetryableContent) - { - return new global::SeedApi.Core.ApiResponse - { - StatusCode = (int)response.StatusCode, - Raw = response, - }; - } - - for (var i = 0; i < maxRetries; i++) - { - if (!ShouldRetry(response)) - { - break; - } - - var delayMs = GetRetryDelayFromHeaders(response, i); - await SystemTask.Delay(delayMs, cancellationToken).ConfigureAwait(false); - using var retryRequest = await CloneRequestAsync(request).ConfigureAwait(false); - response = await httpClient - .SendAsync( - retryRequest, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken - ) - .ConfigureAwait(false); - } - - return new global::SeedApi.Core.ApiResponse - { - StatusCode = (int)response.StatusCode, - Raw = response, - }; - } - - private static bool ShouldRetry(HttpResponseMessage response) - { - var statusCode = (int)response.StatusCode; - return statusCode is 408 or 429 or >= 500; - } - - private static int AddPositiveJitter(int delayMs) - { -#if NET6_0_OR_GREATER - var random = Random.Shared.NextDouble(); -#else - double random; - lock (JitterLock) - { - random = JitterRandom.NextDouble(); - } -#endif - var jitterMultiplier = 1 + random * JitterFactor; - return (int)(delayMs * jitterMultiplier); - } - - private static int AddSymmetricJitter(int delayMs) - { -#if NET6_0_OR_GREATER - var random = Random.Shared.NextDouble(); -#else - double random; - lock (JitterLock) - { - random = JitterRandom.NextDouble(); - } -#endif - var jitterMultiplier = 1 + (random - 0.5) * JitterFactor; - return (int)(delayMs * jitterMultiplier); - } - - private int GetRetryDelayFromHeaders(HttpResponseMessage response, int retryAttempt) - { - if (response.Headers.TryGetValues("Retry-After", out var retryAfterValues)) - { - var retryAfter = retryAfterValues.FirstOrDefault(); - if (!string.IsNullOrEmpty(retryAfter)) - { - if (int.TryParse(retryAfter, out var retryAfterSeconds) && retryAfterSeconds > 0) - { - return Math.Min(retryAfterSeconds * 1000, MaxRetryDelayMs); - } - - if (DateTimeOffset.TryParse(retryAfter, out var retryAfterDate)) - { - var delay = (int)(retryAfterDate - DateTimeOffset.UtcNow).TotalMilliseconds; - if (delay > 0) - { - return Math.Min(delay, MaxRetryDelayMs); - } - } - } - } - - if (response.Headers.TryGetValues("X-RateLimit-Reset", out var rateLimitResetValues)) - { - var rateLimitReset = rateLimitResetValues.FirstOrDefault(); - if ( - !string.IsNullOrEmpty(rateLimitReset) - && long.TryParse(rateLimitReset, out var resetTime) - ) - { - var resetDateTime = DateTimeOffset.FromUnixTimeSeconds(resetTime); - var delay = (int)(resetDateTime - DateTimeOffset.UtcNow).TotalMilliseconds; - if (delay > 0) - { - return AddPositiveJitter(Math.Min(delay, MaxRetryDelayMs)); - } - } - } - - var exponentialDelay = Math.Min(BaseRetryDelay * (1 << retryAttempt), MaxRetryDelayMs); - return AddSymmetricJitter(exponentialDelay); - } - - private static bool IsRetryableContent(HttpRequestMessage request) - { - return request.Content switch - { - IIsRetryableContent c => c.IsRetryable, - StreamContent => false, - MultipartContent content => !content.Any(c => c is StreamContent), - _ => true, - }; - } - - internal async global::System.Threading.Tasks.Task CreateHttpRequestAsync( - global::SeedApi.Core.BaseRequest request - ) - { - var url = BuildUrl(request); - var httpRequest = new HttpRequestMessage(request.Method, url); - httpRequest.Content = request.CreateContent(); - SetHeaders(httpRequest, request.Headers); - - return httpRequest; - } - - private string BuildUrl(global::SeedApi.Core.BaseRequest request) - { - var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl ?? Options.BaseUrl; - - var trimmedBaseUrl = baseUrl.TrimEnd('/'); - var trimmedBasePath = request.Path.TrimStart('/'); - var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; - - // Append query string if present - if (!string.IsNullOrEmpty(request.QueryString)) - { - return url + request.QueryString; - } - - return url; - } - - private void SetHeaders(HttpRequestMessage httpRequest, Dictionary? headers) - { - if (headers is null) - { - return; - } - - foreach (var kv in headers) - { - if (kv.Value is null) - { - continue; - } - - httpRequest.Headers.TryAddWithoutValidation(kv.Key, kv.Value); - } - } - - private static (Encoding encoding, string? charset, string mediaType) ParseContentTypeOrDefault( - string? contentType, - Encoding encodingFallback, - string mediaTypeFallback - ) - { - var encoding = encodingFallback; - var mediaType = mediaTypeFallback; - string? charset = null; - if (string.IsNullOrEmpty(contentType)) - { - return (encoding, charset, mediaType); - } - - if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) - { - return (encoding, charset, mediaType); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) - { - charset = mediaTypeHeaderValue.CharSet; - encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); - } - - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.MediaType)) - { - mediaType = mediaTypeHeaderValue.MediaType; - } - - return (encoding, charset, mediaType); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs deleted file mode 100644 index 5fc790dcc6f1..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/RawResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using global::System.Net; - -namespace SeedApi.Core; - -/// -/// Contains HTTP response metadata including status code, URL, and headers. -/// -public record RawResponse -{ - /// - /// The HTTP status code of the response. - /// - public required HttpStatusCode StatusCode { get; init; } - - /// - /// The request URL that generated this response. - /// - public required Uri Url { get; init; } - - /// - /// The HTTP response headers. - /// - public required Core.ResponseHeaders Headers { get; init; } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs deleted file mode 100644 index 2c394dd9a7a9..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/ResponseHeaders.cs +++ /dev/null @@ -1,108 +0,0 @@ -using global::System.Collections; -using global::System.Net.Http.Headers; - -namespace SeedApi.Core; - -/// -/// Represents HTTP response headers with case-insensitive lookup. -/// -public readonly struct ResponseHeaders : IEnumerable -{ - private readonly HttpResponseHeaders? _headers; - private readonly HttpContentHeaders? _contentHeaders; - - private ResponseHeaders(HttpResponseHeaders headers, HttpContentHeaders? contentHeaders) - { - _headers = headers; - _contentHeaders = contentHeaders; - } - - /// - /// Gets the Content-Type header value, if present. - /// - public string? ContentType => _contentHeaders?.ContentType?.ToString(); - - /// - /// Gets the Content-Length header value, if present. - /// - public long? ContentLength => _contentHeaders?.ContentLength; - - /// - /// Creates a ResponseHeaders instance from an HttpResponseMessage. - /// - public static ResponseHeaders FromHttpResponseMessage(HttpResponseMessage response) - { - return new ResponseHeaders(response.Headers, response.Content?.Headers); - } - - /// - /// Tries to get a single header value. Returns the first value if multiple values exist. - /// - public bool TryGetValue(string name, out string? value) - { - if (TryGetValues(name, out var values) && values is not null) - { - value = values.FirstOrDefault(); - return true; - } - - value = null; - return false; - } - - /// - /// Tries to get all values for a header. - /// - public bool TryGetValues(string name, out IEnumerable? values) - { - if (_headers?.TryGetValues(name, out values) == true) - { - return true; - } - - if (_contentHeaders?.TryGetValues(name, out values) == true) - { - return true; - } - - values = null; - return false; - } - - /// - /// Checks if the headers contain a specific header name. - /// - public bool Contains(string name) - { - return _headers?.Contains(name) == true || _contentHeaders?.Contains(name) == true; - } - - /// - /// Gets an enumerator for all headers. - /// - public IEnumerator GetEnumerator() - { - if (_headers is not null) - { - foreach (var header in _headers) - { - yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); - } - } - - if (_contentHeaders is not null) - { - foreach (var header in _contentHeaders) - { - yield return new HttpHeader(header.Key, string.Join(", ", header.Value)); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} - -/// -/// Represents a single HTTP header. -/// -public readonly record struct HttpHeader(string Name, string Value); diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs deleted file mode 100644 index 54076b686602..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/StreamRequest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using global::System.Net.Http; -using global::System.Net.Http.Headers; - -namespace SeedApi.Core; - -/// -/// The request object to be sent for streaming uploads. -/// -internal record StreamRequest : BaseRequest -{ - internal Stream? Body { get; init; } - - internal override HttpContent? CreateContent() - { - if (Body is null) - { - return null; - } - - var content = new StreamContent(Body) - { - Headers = - { - ContentType = MediaTypeHeaderValue.Parse(ContentType ?? "application/octet-stream"), - }, - }; - return content; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs deleted file mode 100644 index 9f1f4a1c1181..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnum.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -public interface IStringEnum : IEquatable -{ - public string Value { get; } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs deleted file mode 100644 index 704cb6836ab8..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/StringEnumExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SeedApi.Core; - -internal static class StringEnumExtensions -{ - public static string Stringify(this IStringEnum stringEnum) => stringEnum.Value; -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs b/seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs deleted file mode 100644 index ba1f818399e2..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Core/ValueConvert.cs +++ /dev/null @@ -1,115 +0,0 @@ -using global::System.Globalization; - -namespace SeedApi.Core; - -/// -/// Convert values to string for path and query parameters. -/// -public static class ValueConvert -{ - internal static string ToPathParameterString(T value) => ToString(value); - - internal static string ToPathParameterString(bool v) => ToString(v); - - internal static string ToPathParameterString(int v) => ToString(v); - - internal static string ToPathParameterString(long v) => ToString(v); - - internal static string ToPathParameterString(float v) => ToString(v); - - internal static string ToPathParameterString(double v) => ToString(v); - - internal static string ToPathParameterString(decimal v) => ToString(v); - - internal static string ToPathParameterString(short v) => ToString(v); - - internal static string ToPathParameterString(ushort v) => ToString(v); - - internal static string ToPathParameterString(uint v) => ToString(v); - - internal static string ToPathParameterString(ulong v) => ToString(v); - - internal static string ToPathParameterString(string v) => - QueryStringBuilder.EncodePathSegment(v); - - internal static string ToPathParameterString(char v) => ToString(v); - - internal static string ToPathParameterString(Guid v) => ToString(v); - - internal static string ToQueryStringValue(T value) => value is null ? "" : ToString(value); - - internal static string ToQueryStringValue(bool v) => ToString(v); - - internal static string ToQueryStringValue(int v) => ToString(v); - - internal static string ToQueryStringValue(long v) => ToString(v); - - internal static string ToQueryStringValue(float v) => ToString(v); - - internal static string ToQueryStringValue(double v) => ToString(v); - - internal static string ToQueryStringValue(decimal v) => ToString(v); - - internal static string ToQueryStringValue(short v) => ToString(v); - - internal static string ToQueryStringValue(ushort v) => ToString(v); - - internal static string ToQueryStringValue(uint v) => ToString(v); - - internal static string ToQueryStringValue(ulong v) => ToString(v); - - internal static string ToQueryStringValue(string v) => v is null ? "" : v; - - internal static string ToQueryStringValue(char v) => ToString(v); - - internal static string ToQueryStringValue(Guid v) => ToString(v); - - internal static string ToString(T value) - { - return value switch - { - null => "null", - string str => str, - true => "true", - false => "false", - int i => ToString(i), - long l => ToString(l), - float f => ToString(f), - double d => ToString(d), - decimal dec => ToString(dec), - short s => ToString(s), - ushort u => ToString(u), - uint u => ToString(u), - ulong u => ToString(u), - char c => ToString(c), - Guid guid => ToString(guid), - _ => JsonUtils.SerializeRelaxedEscaping(value, value.GetType()).Trim('"'), - }; - } - - internal static string ToString(bool v) => v ? "true" : "false"; - - internal static string ToString(int v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(long v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(float v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(double v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(decimal v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(short v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(ushort v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(uint v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(ulong v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(char v) => v.ToString(CultureInfo.InvariantCulture); - - internal static string ToString(string v) => v; - - internal static string ToString(Guid v) => v.ToString("D"); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs b/seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs deleted file mode 100644 index 4b1eb3e650c8..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/ISeedApiClient.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace SeedApi; - -public partial interface ISeedApiClient -{ - WithRawResponseTask SearchRuleTypesAsync( - SearchRuleTypesRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask CreateRuleAsync( - RuleCreateRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask ListUsersAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask GetEntityAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); - - WithRawResponseTask GetOrganizationAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ); -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs deleted file mode 100644 index 6437574c29a8..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Requests/RuleCreateRequest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleCreateRequest -{ - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("executionContext")] - public required RuleExecutionContext ExecutionContext { get; set; } - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs b/seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs deleted file mode 100644 index 3c1562150a13..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Requests/SearchRuleTypesRequest.cs +++ /dev/null @@ -1,17 +0,0 @@ -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record SearchRuleTypesRequest -{ - [JsonIgnore] - public string? Query { get; set; } - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props b/seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props deleted file mode 100644 index 17a84cada530..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/SeedApi.Custom.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj b/seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj deleted file mode 100644 index 59a0ea188a9c..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/SeedApi.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - net462;net8.0;net9.0;netstandard2.0 - enable - 12 - enable - 0.0.1 - $(Version) - $(Version) - README.md - https://github.com/allof/fern - true - - - false - - - $(DefineConstants);USE_PORTABLE_DATE_ONLY - true - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - <_Parameter1>SeedApi.Test - - - - diff --git a/seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs b/seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs deleted file mode 100644 index 16920940ab90..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/SeedApiClient.cs +++ /dev/null @@ -1,429 +0,0 @@ -using global::System.Text.Json; -using SeedApi.Core; - -namespace SeedApi; - -public partial class SeedApiClient : ISeedApiClient -{ - private readonly RawClient _client; - - public SeedApiClient(ClientOptions? clientOptions = null) - { - clientOptions ??= new ClientOptions(); - var platformHeaders = new Headers( - new Dictionary() - { - { "X-Fern-Language", "C#" }, - { "X-Fern-SDK-Name", "SeedApi" }, - { "X-Fern-SDK-Version", Version.Current }, - { "User-Agent", "Fernallof/0.0.1" }, - } - ); - foreach (var header in platformHeaders) - { - if (!clientOptions.Headers.ContainsKey(header.Key)) - { - clientOptions.Headers[header.Key] = header.Value; - } - } - _client = new RawClient(clientOptions); - } - - private async Task> SearchRuleTypesAsyncCore( - SearchRuleTypesRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _queryString = new SeedApi.Core.QueryStringBuilder.Builder(capacity: 1) - .Add("query", request.Query) - .MergeAdditional(options?.AdditionalQueryParameters) - .Build(); - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "rule-types", - QueryString = _queryString, - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> CreateRuleAsyncCore( - RuleCreateRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Post, - Path = "rules", - Body = request, - Headers = _headers, - ContentType = "application/json", - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> ListUsersAsyncCore( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "users", - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> GetEntityAsyncCore( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "entities", - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - private async Task> GetOrganizationAsyncCore( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - var _headers = await new SeedApi.Core.HeadersBuilder.Builder() - .Add(_client.Options.Headers) - .Add(_client.Options.AdditionalHeaders) - .Add(options?.AdditionalHeaders) - .BuildAsync() - .ConfigureAwait(false); - var response = await _client - .SendRequestAsync( - new JsonRequest - { - Method = HttpMethod.Get, - Path = "organizations", - Headers = _headers, - Options = options, - }, - cancellationToken - ) - .ConfigureAwait(false); - if (response.StatusCode is >= 200 and < 400) - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - try - { - var responseData = JsonUtils.Deserialize(responseBody)!; - return new WithRawResponse() - { - Data = responseData, - RawResponse = new RawResponse() - { - StatusCode = response.Raw.StatusCode, - Url = response.Raw.RequestMessage?.RequestUri ?? new Uri("about:blank"), - Headers = ResponseHeaders.FromHttpResponseMessage(response.Raw), - }, - }; - } - catch (JsonException e) - { - throw new SeedApiApiException( - "Failed to deserialize response", - response.StatusCode, - responseBody, - e - ); - } - } - { - var responseBody = await response - .Raw.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - throw new SeedApiApiException( - $"Error with status code {response.StatusCode}", - response.StatusCode, - responseBody - ); - } - } - - /// - /// await client.SearchRuleTypesAsync(new SearchRuleTypesRequest()); - /// - public WithRawResponseTask SearchRuleTypesAsync( - SearchRuleTypesRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - SearchRuleTypesAsyncCore(request, options, cancellationToken) - ); - } - - /// - /// await client.CreateRuleAsync( - /// new RuleCreateRequest { Name = "name", ExecutionContext = RuleExecutionContext.Prod } - /// ); - /// - public WithRawResponseTask CreateRuleAsync( - RuleCreateRequest request, - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - CreateRuleAsyncCore(request, options, cancellationToken) - ); - } - - /// - /// await client.ListUsersAsync(); - /// - public WithRawResponseTask ListUsersAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - ListUsersAsyncCore(options, cancellationToken) - ); - } - - /// - /// await client.GetEntityAsync(); - /// - public WithRawResponseTask GetEntityAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - GetEntityAsyncCore(options, cancellationToken) - ); - } - - /// - /// await client.GetOrganizationAsync(); - /// - public WithRawResponseTask GetOrganizationAsync( - RequestOptions? options = null, - CancellationToken cancellationToken = default - ) - { - return new WithRawResponseTask( - GetOrganizationAsyncCore(options, cancellationToken) - ); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs deleted file mode 100644 index c0d843281678..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/AuditInfo.cs +++ /dev/null @@ -1,56 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -/// -/// Common audit metadata. -/// -[Serializable] -public record AuditInfo : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs deleted file mode 100644 index eb944d0030da..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrg.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public BaseOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs deleted file mode 100644 index fcb0efec5fea..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/BaseOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record BaseOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from BaseOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Subscription tier. - /// - [JsonPropertyName("tier")] - public string? Tier { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs deleted file mode 100644 index 757711bdb703..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record CombinedEntity : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("status")] - public required CombinedEntityStatus Status { get; set; } - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Identifiable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs deleted file mode 100644 index 0ab2467f6bd7..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/CombinedEntityStatus.cs +++ /dev/null @@ -1,115 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(CombinedEntityStatus.CombinedEntityStatusSerializer))] -[Serializable] -public readonly record struct CombinedEntityStatus : IStringEnum -{ - public static readonly CombinedEntityStatus Active = new(Values.Active); - - public static readonly CombinedEntityStatus Archived = new(Values.Archived); - - public CombinedEntityStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static CombinedEntityStatus FromCustom(string value) - { - return new CombinedEntityStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(CombinedEntityStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(CombinedEntityStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(CombinedEntityStatus value) => value.Value; - - public static explicit operator CombinedEntityStatus(string value) => new(value); - - internal class CombinedEntityStatusSerializer : JsonConverter - { - public override CombinedEntityStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override CombinedEntityStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new CombinedEntityStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - CombinedEntityStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Archived = "archived"; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs deleted file mode 100644 index f521e9082be7..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/Describable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Describable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Display name from Describable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// - /// A short summary. - /// - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs deleted file mode 100644 index 7bc064682236..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrg.cs +++ /dev/null @@ -1,28 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrg : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("metadata")] - public DetailedOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs deleted file mode 100644 index 798903997238..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/DetailedOrgMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record DetailedOrgMetadata : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Deployment region from DetailedOrg. - /// - [JsonPropertyName("region")] - public required string Region { get; set; } - - /// - /// Custom domain name. - /// - [JsonPropertyName("domain")] - public string? Domain { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs deleted file mode 100644 index 05b3808b610b..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/Identifiable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Identifiable : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Unique identifier. - /// - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// - /// Display name from Identifiable. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs deleted file mode 100644 index e90644924f99..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/Organization.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record Organization : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("metadata")] - public BaseOrgMetadata? Metadata { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs deleted file mode 100644 index 9f4b1b5c0820..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/PaginatedResult.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PaginatedResult : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable Results { get; set; } = new List(); - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs deleted file mode 100644 index b1f0001a4733..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/PagingCursors.cs +++ /dev/null @@ -1,37 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record PagingCursors : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Cursor for the next page of results. - /// - [JsonPropertyName("next")] - public required string Next { get; set; } - - /// - /// Cursor for the previous page of results. - /// - [JsonPropertyName("previous")] - public string? Previous { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs deleted file mode 100644 index d22a26079f4e..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleExecutionContext.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleExecutionContext.RuleExecutionContextSerializer))] -[Serializable] -public readonly record struct RuleExecutionContext : IStringEnum -{ - public static readonly RuleExecutionContext Prod = new(Values.Prod); - - public static readonly RuleExecutionContext Staging = new(Values.Staging); - - public static readonly RuleExecutionContext Dev = new(Values.Dev); - - public RuleExecutionContext(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleExecutionContext FromCustom(string value) - { - return new RuleExecutionContext(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleExecutionContext value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleExecutionContext value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleExecutionContext value) => value.Value; - - public static explicit operator RuleExecutionContext(string value) => new(value); - - internal class RuleExecutionContextSerializer : JsonConverter - { - public override RuleExecutionContext Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleExecutionContext ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleExecutionContext(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleExecutionContext value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Prod = "prod"; - - public const string Staging = "staging"; - - public const string Dev = "dev"; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs deleted file mode 100644 index 6459faa88dfd..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponse.cs +++ /dev/null @@ -1,65 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("status")] - public required RuleResponseStatus Status { get; set; } - - [JsonPropertyName("executionContext")] - public RuleExecutionContext? ExecutionContext { get; set; } - - /// - /// The user who created this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdBy")] - public string? CreatedBy { get; set; } - - /// - /// When this resource was created. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("createdDateTime")] - public DateTime? CreatedDateTime { get; set; } - - /// - /// The user who last modified this resource. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedBy")] - public string? ModifiedBy { get; set; } - - /// - /// When this resource was last modified. - /// - [JsonAccess(JsonAccessType.ReadOnly)] - [JsonPropertyName("modifiedDateTime")] - public DateTime? ModifiedDateTime { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs deleted file mode 100644 index 9b587cdbcba0..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleResponseStatus.cs +++ /dev/null @@ -1,119 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[JsonConverter(typeof(RuleResponseStatus.RuleResponseStatusSerializer))] -[Serializable] -public readonly record struct RuleResponseStatus : IStringEnum -{ - public static readonly RuleResponseStatus Active = new(Values.Active); - - public static readonly RuleResponseStatus Inactive = new(Values.Inactive); - - public static readonly RuleResponseStatus Draft = new(Values.Draft); - - public RuleResponseStatus(string value) - { - Value = value; - } - - /// - /// The string value of the enum. - /// - public string Value { get; } - - /// - /// Create a string enum with the given value. - /// - public static RuleResponseStatus FromCustom(string value) - { - return new RuleResponseStatus(value); - } - - public bool Equals(string? other) - { - return Value.Equals(other); - } - - /// - /// Returns the string value of the enum. - /// - public override string ToString() - { - return Value; - } - - public static bool operator ==(RuleResponseStatus value1, string value2) => - value1.Value.Equals(value2); - - public static bool operator !=(RuleResponseStatus value1, string value2) => - !value1.Value.Equals(value2); - - public static explicit operator string(RuleResponseStatus value) => value.Value; - - public static explicit operator RuleResponseStatus(string value) => new(value); - - internal class RuleResponseStatusSerializer : JsonConverter - { - public override RuleResponseStatus Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON value could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void Write( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WriteStringValue(value.Value); - } - - public override RuleResponseStatus ReadAsPropertyName( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - var stringValue = - reader.GetString() - ?? throw new global::System.Exception( - "The JSON property name could not be read as a string." - ); - return new RuleResponseStatus(stringValue); - } - - public override void WriteAsPropertyName( - Utf8JsonWriter writer, - RuleResponseStatus value, - JsonSerializerOptions options - ) - { - writer.WritePropertyName(value.Value); - } - } - - /// - /// Constant strings for enum values - /// - [Serializable] - public static class Values - { - public const string Active = "active"; - - public const string Inactive = "inactive"; - - public const string Draft = "draft"; - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs deleted file mode 100644 index 578b90315dde..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleType.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleType : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("name")] - public required string Name { get; set; } - - [JsonPropertyName("description")] - public string? Description { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs deleted file mode 100644 index fa14fff1bde0..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/RuleTypeSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record RuleTypeSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/User.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/User.cs deleted file mode 100644 index abc389c0a6b6..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/User.cs +++ /dev/null @@ -1,31 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record User : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - [JsonPropertyName("id")] - public required string Id { get; set; } - - [JsonPropertyName("email")] - public required string Email { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs b/seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs deleted file mode 100644 index 942b822f3dc4..000000000000 --- a/seed/csharp-sdk/allof/src/SeedApi/Types/UserSearchResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using global::System.Text.Json; -using global::System.Text.Json.Serialization; -using SeedApi.Core; - -namespace SeedApi; - -[Serializable] -public record UserSearchResponse : IJsonOnDeserialized -{ - [JsonExtensionData] - private readonly IDictionary _extensionData = - new Dictionary(); - - /// - /// Current page of results from the requested resource. - /// - [JsonPropertyName("results")] - public IEnumerable? Results { get; set; } - - [JsonPropertyName("paging")] - public required PagingCursors Paging { get; set; } - - [JsonIgnore] - public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new(); - - void IJsonOnDeserialized.OnDeserialized() => - AdditionalProperties.CopyFromExtensionData(_extensionData); - - /// - public override string ToString() - { - return JsonUtils.Serialize(this); - } -} diff --git a/seed/go-model/allof-inline/.fern/metadata.json b/seed/go-model/allof-inline/.fern/metadata.json deleted file mode 100644 index d4a30f5a7178..000000000000 --- a/seed/go-model/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-go-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "v0.0.1" -} \ No newline at end of file diff --git a/seed/go-model/allof-inline/doc.go b/seed/go-model/allof-inline/doc.go deleted file mode 100644 index a57cc3f67402..000000000000 --- a/seed/go-model/allof-inline/doc.go +++ /dev/null @@ -1 +0,0 @@ -package api \ No newline at end of file diff --git a/seed/go-model/allof-inline/go.mod b/seed/go-model/allof-inline/go.mod deleted file mode 100644 index f064631d08ad..000000000000 --- a/seed/go-model/allof-inline/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/allof-inline/fern - -go 1.21 - -toolchain go1.23.8 - -require github.com/stretchr/testify v1.8.4 - -require gopkg.in/yaml.v3 v3.0.1 // indirect - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect -) diff --git a/seed/go-model/allof-inline/go.sum b/seed/go-model/allof-inline/go.sum deleted file mode 100644 index fa4b6e6825c4..000000000000 --- a/seed/go-model/allof-inline/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-model/allof-inline/internal/extra_properties.go b/seed/go-model/allof-inline/internal/extra_properties.go deleted file mode 100644 index 57517691f132..000000000000 --- a/seed/go-model/allof-inline/internal/extra_properties.go +++ /dev/null @@ -1,141 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "strings" -) - -// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. -func MarshalJSONWithExtraProperty(marshaler any, key string, value any) ([]byte, error) { - return MarshalJSONWithExtraProperties(marshaler, map[string]any{key: value}) -} - -// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. -func MarshalJSONWithExtraProperties(marshaler any, extraProperties map[string]any) ([]byte, error) { - bytes, err := json.Marshal(marshaler) - if err != nil { - return nil, err - } - if len(extraProperties) == 0 { - return bytes, nil - } - keys, err := getKeys(marshaler) - if err != nil { - return nil, err - } - for _, key := range keys { - if _, ok := extraProperties[key]; ok { - return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) - } - } - extraBytes, err := json.Marshal(extraProperties) - if err != nil { - return nil, err - } - if isEmptyJSON(bytes) { - if isEmptyJSON(extraBytes) { - return bytes, nil - } - return extraBytes, nil - } - result := bytes[:len(bytes)-1] - result = append(result, ',') - result = append(result, extraBytes[1:len(extraBytes)-1]...) - result = append(result, '}') - return result, nil -} - -// ExtractExtraProperties extracts any extra properties from the given value. -func ExtractExtraProperties(bytes []byte, value any, exclude ...string) (map[string]any, error) { - val := reflect.ValueOf(value) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil, fmt.Errorf("value must be non-nil to extract extra properties") - } - val = val.Elem() - } - if err := json.Unmarshal(bytes, &value); err != nil { - return nil, err - } - var extraProperties map[string]any - if err := json.Unmarshal(bytes, &extraProperties); err != nil { - return nil, err - } - for i := 0; i < val.Type().NumField(); i++ { - key := jsonKey(val.Type().Field(i)) - if key == "" || key == "-" { - continue - } - delete(extraProperties, key) - } - for _, key := range exclude { - delete(extraProperties, key) - } - if len(extraProperties) == 0 { - return nil, nil - } - return extraProperties, nil -} - -// getKeys returns the keys associated with the given value. The value must be a -// a struct or a map with string keys. -func getKeys(value any) ([]string, error) { - val := reflect.ValueOf(value) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - if !val.IsValid() { - return nil, nil - } - switch val.Kind() { - case reflect.Struct: - return getKeysForStructType(val.Type()), nil - case reflect.Map: - var keys []string - if val.Type().Key().Kind() != reflect.String { - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } - for _, key := range val.MapKeys() { - keys = append(keys, key.String()) - } - return keys, nil - default: - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } -} - -// getKeysForStructType returns all the keys associated with the given struct type, -// visiting embedded fields recursively. -func getKeysForStructType(structType reflect.Type) []string { - if structType.Kind() == reflect.Pointer { - structType = structType.Elem() - } - if structType.Kind() != reflect.Struct { - return nil - } - var keys []string - for i := 0; i < structType.NumField(); i++ { - field := structType.Field(i) - if field.Anonymous { - keys = append(keys, getKeysForStructType(field.Type)...) - continue - } - keys = append(keys, jsonKey(field)) - } - return keys -} - -// jsonKey returns the JSON key from the struct tag of the given field, -// excluding the omitempty flag (if any). -func jsonKey(field reflect.StructField) string { - return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") -} - -// isEmptyJSON returns true if the given data is empty, the empty JSON object, or -// an explicit null. -func isEmptyJSON(data []byte) bool { - return len(data) <= 2 || bytes.Equal(data, []byte("null")) -} diff --git a/seed/go-model/allof-inline/internal/extra_properties_test.go b/seed/go-model/allof-inline/internal/extra_properties_test.go deleted file mode 100644 index 0d46257763fb..000000000000 --- a/seed/go-model/allof-inline/internal/extra_properties_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package internal - -import ( - "encoding/json" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testMarshaler struct { - Name string `json:"name"` - BirthDate time.Time `json:"birthDate"` - CreatedAt time.Time `json:"created_at"` -} - -func (t *testMarshaler) MarshalJSON() ([]byte, error) { - type embed testMarshaler - var marshaler = struct { - embed - BirthDate string `json:"birthDate"` - CreatedAt string `json:"created_at"` - }{ - embed: embed(*t), - BirthDate: t.BirthDate.Format("2006-01-02"), - CreatedAt: t.CreatedAt.Format(time.RFC3339), - } - return MarshalJSONWithExtraProperty(marshaler, "type", "test") -} - -func TestMarshalJSONWithExtraProperties(t *testing.T) { - tests := []struct { - desc string - giveMarshaler any - giveExtraProperties map[string]any - wantBytes []byte - wantError string - }{ - { - desc: "invalid type", - giveMarshaler: []string{"invalid"}, - giveExtraProperties: map[string]any{"key": "overwrite"}, - wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, - }, - { - desc: "invalid key type", - giveMarshaler: map[int]any{42: "value"}, - giveExtraProperties: map[string]any{"key": "overwrite"}, - wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, - }, - { - desc: "invalid map overwrite", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{"key": "overwrite"}, - wantError: `cannot add extra property "key" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]any{"birthDate": "2000-01-01"}, - wantError: `cannot add extra property "birthDate" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite embedded type", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]any{"name": "bob"}, - wantError: `cannot add extra property "name" because it is already defined on the type`, - }, - { - desc: "nil", - giveMarshaler: nil, - giveExtraProperties: nil, - wantBytes: []byte(`null`), - }, - { - desc: "empty", - giveMarshaler: map[string]any{}, - giveExtraProperties: map[string]any{}, - wantBytes: []byte(`{}`), - }, - { - desc: "no extra properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "only extra properties", - giveMarshaler: map[string]any{}, - giveExtraProperties: map[string]any{"key": "value"}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "single extra property", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{"extra": "property"}, - wantBytes: []byte(`{"key":"value","extra":"property"}`), - }, - { - desc: "multiple extra properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{"one": 1, "two": 2}, - wantBytes: []byte(`{"key":"value","one":1,"two":2}`), - }, - { - desc: "nested properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{ - "user": map[string]any{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), - }, - { - desc: "multiple nested properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{ - "metadata": map[string]any{ - "ip": "127.0.0.1", - }, - "user": map[string]any{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), - }, - { - desc: "custom marshaler", - giveMarshaler: &testMarshaler{ - Name: "alice", - BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - }, - giveExtraProperties: map[string]any{ - "extra": "property", - }, - wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) - if tt.wantError != "" { - require.EqualError(t, err, tt.wantError) - assert.Nil(t, tt.wantBytes) - return - } - require.NoError(t, err) - assert.Equal(t, tt.wantBytes, bytes) - - value := make(map[string]any) - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -func TestExtractExtraProperties(t *testing.T) { - t.Run("none", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) - - t.Run("non-nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) - }) - - t.Run("nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value *user - _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - assert.EqualError(t, err, "value must be non-nil to extract extra properties") - }) - - t.Run("non-zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) - }) - - t.Run("zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value user - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) - }) - - t.Run("exclude", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) -} diff --git a/seed/go-model/allof-inline/internal/stringer.go b/seed/go-model/allof-inline/internal/stringer.go deleted file mode 100644 index 0be54d1b5359..000000000000 --- a/seed/go-model/allof-inline/internal/stringer.go +++ /dev/null @@ -1,13 +0,0 @@ -package internal - -import "encoding/json" - -// StringifyJSON returns a pretty JSON string representation of -// the given value. -func StringifyJSON(value any) (string, error) { - bytes, err := json.MarshalIndent(value, "", " ") - if err != nil { - return "", err - } - return string(bytes), nil -} diff --git a/seed/go-model/allof-inline/internal/time.go b/seed/go-model/allof-inline/internal/time.go deleted file mode 100644 index 57f901a35ed8..000000000000 --- a/seed/go-model/allof-inline/internal/time.go +++ /dev/null @@ -1,165 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "time" -) - -const dateFormat = "2006-01-02" - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date (e.g. 2006-01-02). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type Date struct { - t *time.Time -} - -// NewDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewDate(t time.Time) *Date { - return &Date{t: &t} -} - -// NewOptionalDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDate(t *time.Time) *Date { - if t == nil { - return nil - } - return &Date{t: t} -} - -// Time returns the Date's underlying time, if any. If the -// date is nil, the zero value is returned. -func (d *Date) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the Date's underlying time.Time, if any. -func (d *Date) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *Date) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(dateFormat)) -} - -func (d *Date) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - parsedTime, err := time.Parse(dateFormat, raw) - if err != nil { - return err - } - - *d = Date{t: &parsedTime} - return nil -} - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type DateTime struct { - t *time.Time -} - -// NewDateTime returns a new *DateTime. -func NewDateTime(t time.Time) *DateTime { - return &DateTime{t: &t} -} - -// NewOptionalDateTime returns a new *DateTime. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDateTime(t *time.Time) *DateTime { - if t == nil { - return nil - } - return &DateTime{t: t} -} - -// Time returns the DateTime's underlying time, if any. If the -// date-time is nil, the zero value is returned. -func (d *DateTime) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. -func (d *DateTime) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *DateTime) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(time.RFC3339)) -} - -func (d *DateTime) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - // If the value is not a string, check if it is a number (unix epoch seconds). - var epoch int64 - if numErr := json.Unmarshal(data, &epoch); numErr == nil { - t := time.Unix(epoch, 0).UTC() - *d = DateTime{t: &t} - return nil - } - return err - } - - // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). - parsedTime, err := time.Parse(time.RFC3339Nano, raw) - if err == nil { - *d = DateTime{t: &parsedTime} - return nil - } - rfc3339NanoErr := err - - // Fall back to ISO 8601 without timezone (assume UTC). - parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - iso8601Err := err - - // Fall back to date-only format. - parsedTime, err = time.Parse("2006-01-02", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - dateOnlyErr := err - - return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) -} diff --git a/seed/go-model/allof-inline/snippet.json b/seed/go-model/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/go-model/allof-inline/types.go b/seed/go-model/allof-inline/types.go deleted file mode 100644 index 51b323994e16..000000000000 --- a/seed/go-model/allof-inline/types.go +++ /dev/null @@ -1,1246 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - json "encoding/json" - fmt "fmt" - time "time" - - internal "github.com/allof-inline/fern/internal" -) - -type PaginatedResult struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []any `json:"results" url:"results"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (p *PaginatedResult) GetPaging() *PagingCursors { - if p == nil { - return nil - } - return p.Paging -} - -func (p *PaginatedResult) GetResults() []any { - if p == nil { - return nil - } - return p.Results -} - -func (p *PaginatedResult) GetExtraProperties() map[string]any { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PaginatedResult) UnmarshalJSON( - data []byte, -) error { - type unmarshaler PaginatedResult - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PaginatedResult(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PaginatedResult) String() string { - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -type PagingCursors struct { - // Cursor for the next page of results. - Next string `json:"next" url:"next"` - // Cursor for the previous page of results. - Previous *string `json:"previous,omitempty" url:"previous,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (p *PagingCursors) GetNext() string { - if p == nil { - return "" - } - return p.Next -} - -func (p *PagingCursors) GetPrevious() *string { - if p == nil { - return nil - } - return p.Previous -} - -func (p *PagingCursors) GetExtraProperties() map[string]any { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PagingCursors) UnmarshalJSON( - data []byte, -) error { - type unmarshaler PagingCursors - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PagingCursors(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PagingCursors) String() string { - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -// Execution environment for a rule. -type RuleExecutionContext string - -const ( - RuleExecutionContextProd = "prod" - RuleExecutionContextStaging = "staging" - RuleExecutionContextDev = "dev" -) - -func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { - switch s { - case "prod": - return RuleExecutionContextProd, nil - case "staging": - return RuleExecutionContextStaging, nil - case "dev": - return RuleExecutionContextDev, nil - } - var t RuleExecutionContext - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleExecutionContext) Ptr() *RuleExecutionContext { - return &r -} - -// Common audit metadata. -type AuditInfo struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (a *AuditInfo) GetCreatedBy() *string { - if a == nil { - return nil - } - return a.CreatedBy -} - -func (a *AuditInfo) GetCreatedDateTime() *time.Time { - if a == nil { - return nil - } - return a.CreatedDateTime -} - -func (a *AuditInfo) GetModifiedBy() *string { - if a == nil { - return nil - } - return a.ModifiedBy -} - -func (a *AuditInfo) GetModifiedDateTime() *time.Time { - if a == nil { - return nil - } - return a.ModifiedDateTime -} - -func (a *AuditInfo) GetExtraProperties() map[string]any { - if a == nil { - return nil - } - return a.extraProperties -} - -func (a *AuditInfo) UnmarshalJSON( - data []byte, -) error { - type embed AuditInfo - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*a), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *a = AuditInfo(unmarshaler.embed) - a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *a) - if err != nil { - return err - } - a.extraProperties = extraProperties - a.rawJSON = json.RawMessage(data) - return nil -} - -func (a *AuditInfo) MarshalJSON() ([]byte, error) { - type embed AuditInfo - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*a), - CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), - } - return json.Marshal(marshaler) -} - -func (a *AuditInfo) String() string { - if len(a.rawJSON) > 0 { - if value, err := internal.StringifyJSON(a.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(a); err == nil { - return value - } - return fmt.Sprintf("%#v", a) -} - -type RuleType struct { - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Description *string `json:"description,omitempty" url:"description,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (r *RuleType) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleType) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleType) GetDescription() *string { - if r == nil { - return nil - } - return r.Description -} - -func (r *RuleType) GetExtraProperties() map[string]any { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleType) UnmarshalJSON( - data []byte, -) error { - type unmarshaler RuleType - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleType(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleType) String() string { - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type RuleTypeSearchResponse struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { - if r == nil { - return nil - } - return r.Paging -} - -func (r *RuleTypeSearchResponse) GetResults() []*RuleType { - if r == nil { - return nil - } - return r.Results -} - -func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]any { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleTypeSearchResponse) UnmarshalJSON( - data []byte, -) error { - type unmarshaler RuleTypeSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleTypeSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleTypeSearchResponse) String() string { - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type User struct { - ID string `json:"id" url:"id"` - Email string `json:"email" url:"email"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (u *User) GetID() string { - if u == nil { - return "" - } - return u.ID -} - -func (u *User) GetEmail() string { - if u == nil { - return "" - } - return u.Email -} - -func (u *User) GetExtraProperties() map[string]any { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *User) UnmarshalJSON( - data []byte, -) error { - type unmarshaler User - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = User(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *User) String() string { - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} - -type UserSearchResponse struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []*User `json:"results,omitempty" url:"results,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (u *UserSearchResponse) GetPaging() *PagingCursors { - if u == nil { - return nil - } - return u.Paging -} - -func (u *UserSearchResponse) GetResults() []*User { - if u == nil { - return nil - } - return u.Results -} - -func (u *UserSearchResponse) GetExtraProperties() map[string]any { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *UserSearchResponse) UnmarshalJSON( - data []byte, -) error { - type unmarshaler UserSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = UserSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *UserSearchResponse) String() string { - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} - -type RuleResponseStatus string - -const ( - RuleResponseStatusActive = "active" - RuleResponseStatusInactive = "inactive" - RuleResponseStatusDraft = "draft" -) - -func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { - switch s { - case "active": - return RuleResponseStatusActive, nil - case "inactive": - return RuleResponseStatusInactive, nil - case "draft": - return RuleResponseStatusDraft, nil - } - var t RuleResponseStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleResponseStatus) Ptr() *RuleResponseStatus { - return &r -} - -type RuleResponse struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Status *RuleResponseStatus `json:"status" url:"status"` - ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (r *RuleResponse) GetCreatedBy() *string { - if r == nil { - return nil - } - return r.CreatedBy -} - -func (r *RuleResponse) GetCreatedDateTime() *time.Time { - if r == nil { - return nil - } - return r.CreatedDateTime -} - -func (r *RuleResponse) GetModifiedBy() *string { - if r == nil { - return nil - } - return r.ModifiedBy -} - -func (r *RuleResponse) GetModifiedDateTime() *time.Time { - if r == nil { - return nil - } - return r.ModifiedDateTime -} - -func (r *RuleResponse) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleResponse) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleResponse) GetStatus() *RuleResponseStatus { - if r == nil { - return nil - } - return r.Status -} - -func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { - if r == nil { - return nil - } - return r.ExecutionContext -} - -func (r *RuleResponse) GetExtraProperties() map[string]any { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleResponse) UnmarshalJSON( - data []byte, -) error { - type embed RuleResponse - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*r), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *r = RuleResponse(unmarshaler.embed) - r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleResponse) MarshalJSON() ([]byte, error) { - type embed RuleResponse - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*r), - CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), - } - return json.Marshal(marshaler) -} - -func (r *RuleResponse) String() string { - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type Identifiable struct { - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Identifiable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (i *Identifiable) GetID() string { - if i == nil { - return "" - } - return i.ID -} - -func (i *Identifiable) GetName() *string { - if i == nil { - return nil - } - return i.Name -} - -func (i *Identifiable) GetExtraProperties() map[string]any { - if i == nil { - return nil - } - return i.extraProperties -} - -func (i *Identifiable) UnmarshalJSON( - data []byte, -) error { - type unmarshaler Identifiable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *i = Identifiable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *i) - if err != nil { - return err - } - i.extraProperties = extraProperties - i.rawJSON = json.RawMessage(data) - return nil -} - -func (i *Identifiable) String() string { - if len(i.rawJSON) > 0 { - if value, err := internal.StringifyJSON(i.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(i); err == nil { - return value - } - return fmt.Sprintf("%#v", i) -} - -type Describable struct { - // Display name from Describable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (d *Describable) GetName() *string { - if d == nil { - return nil - } - return d.Name -} - -func (d *Describable) GetSummary() *string { - if d == nil { - return nil - } - return d.Summary -} - -func (d *Describable) GetExtraProperties() map[string]any { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *Describable) UnmarshalJSON( - data []byte, -) error { - type unmarshaler Describable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = Describable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *Describable) String() string { - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -type CombinedEntityStatus string - -const ( - CombinedEntityStatusActive = "active" - CombinedEntityStatusArchived = "archived" -) - -func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { - switch s { - case "active": - return CombinedEntityStatusActive, nil - case "archived": - return CombinedEntityStatusArchived, nil - } - var t CombinedEntityStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { - return &c -} - -type CombinedEntity struct { - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Describable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - Status *CombinedEntityStatus `json:"status" url:"status"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (c *CombinedEntity) GetID() string { - if c == nil { - return "" - } - return c.ID -} - -func (c *CombinedEntity) GetName() *string { - if c == nil { - return nil - } - return c.Name -} - -func (c *CombinedEntity) GetSummary() *string { - if c == nil { - return nil - } - return c.Summary -} - -func (c *CombinedEntity) GetStatus() *CombinedEntityStatus { - if c == nil { - return nil - } - return c.Status -} - -func (c *CombinedEntity) GetExtraProperties() map[string]any { - if c == nil { - return nil - } - return c.extraProperties -} - -func (c *CombinedEntity) UnmarshalJSON( - data []byte, -) error { - type unmarshaler CombinedEntity - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *c = CombinedEntity(value) - extraProperties, err := internal.ExtractExtraProperties(data, *c) - if err != nil { - return err - } - c.extraProperties = extraProperties - c.rawJSON = json.RawMessage(data) - return nil -} - -func (c *CombinedEntity) String() string { - if len(c.rawJSON) > 0 { - if value, err := internal.StringifyJSON(c.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(c); err == nil { - return value - } - return fmt.Sprintf("%#v", c) -} - -type BaseOrgMetadata struct { - // Deployment region from BaseOrg. - Region string `json:"region" url:"region"` - // Subscription tier. - Tier *string `json:"tier,omitempty" url:"tier,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (b *BaseOrgMetadata) GetRegion() string { - if b == nil { - return "" - } - return b.Region -} - -func (b *BaseOrgMetadata) GetTier() *string { - if b == nil { - return nil - } - return b.Tier -} - -func (b *BaseOrgMetadata) GetExtraProperties() map[string]any { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrgMetadata) UnmarshalJSON( - data []byte, -) error { - type unmarshaler BaseOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrgMetadata) String() string { - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -type BaseOrg struct { - ID string `json:"id" url:"id"` - Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (b *BaseOrg) GetID() string { - if b == nil { - return "" - } - return b.ID -} - -func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { - if b == nil { - return nil - } - return b.Metadata -} - -func (b *BaseOrg) GetExtraProperties() map[string]any { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrg) UnmarshalJSON( - data []byte, -) error { - type unmarshaler BaseOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrg) String() string { - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -type DetailedOrgMetadata struct { - // Deployment region from DetailedOrg. - Region string `json:"region" url:"region"` - // Custom domain name. - Domain *string `json:"domain,omitempty" url:"domain,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (d *DetailedOrgMetadata) GetRegion() string { - if d == nil { - return "" - } - return d.Region -} - -func (d *DetailedOrgMetadata) GetDomain() *string { - if d == nil { - return nil - } - return d.Domain -} - -func (d *DetailedOrgMetadata) GetExtraProperties() map[string]any { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrgMetadata) UnmarshalJSON( - data []byte, -) error { - type unmarshaler DetailedOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrgMetadata) String() string { - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -type DetailedOrg struct { - Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { - if d == nil { - return nil - } - return d.Metadata -} - -func (d *DetailedOrg) GetExtraProperties() map[string]any { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrg) UnmarshalJSON( - data []byte, -) error { - type unmarshaler DetailedOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrg) String() string { - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -type OrganizationMetadata struct { - // Deployment region from DetailedOrg. - Region string `json:"region" url:"region"` - // Custom domain name. - Domain *string `json:"domain,omitempty" url:"domain,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (o *OrganizationMetadata) GetRegion() string { - if o == nil { - return "" - } - return o.Region -} - -func (o *OrganizationMetadata) GetDomain() *string { - if o == nil { - return nil - } - return o.Domain -} - -func (o *OrganizationMetadata) GetExtraProperties() map[string]any { - if o == nil { - return nil - } - return o.extraProperties -} - -func (o *OrganizationMetadata) UnmarshalJSON( - data []byte, -) error { - type unmarshaler OrganizationMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *o = OrganizationMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *o) - if err != nil { - return err - } - o.extraProperties = extraProperties - o.rawJSON = json.RawMessage(data) - return nil -} - -func (o *OrganizationMetadata) String() string { - if len(o.rawJSON) > 0 { - if value, err := internal.StringifyJSON(o.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(o); err == nil { - return value - } - return fmt.Sprintf("%#v", o) -} - -type Organization struct { - ID string `json:"id" url:"id"` - Metadata *OrganizationMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - Name string `json:"name" url:"name"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (o *Organization) GetID() string { - if o == nil { - return "" - } - return o.ID -} - -func (o *Organization) GetMetadata() *OrganizationMetadata { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *Organization) GetName() string { - if o == nil { - return "" - } - return o.Name -} - -func (o *Organization) GetExtraProperties() map[string]any { - if o == nil { - return nil - } - return o.extraProperties -} - -func (o *Organization) UnmarshalJSON( - data []byte, -) error { - type unmarshaler Organization - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *o = Organization(value) - extraProperties, err := internal.ExtractExtraProperties(data, *o) - if err != nil { - return err - } - o.extraProperties = extraProperties - o.rawJSON = json.RawMessage(data) - return nil -} - -func (o *Organization) String() string { - if len(o.rawJSON) > 0 { - if value, err := internal.StringifyJSON(o.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(o); err == nil { - return value - } - return fmt.Sprintf("%#v", o) -} diff --git a/seed/go-model/allof/.fern/metadata.json b/seed/go-model/allof/.fern/metadata.json deleted file mode 100644 index d4a30f5a7178..000000000000 --- a/seed/go-model/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-go-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "v0.0.1" -} \ No newline at end of file diff --git a/seed/go-model/allof/doc.go b/seed/go-model/allof/doc.go deleted file mode 100644 index a57cc3f67402..000000000000 --- a/seed/go-model/allof/doc.go +++ /dev/null @@ -1 +0,0 @@ -package api \ No newline at end of file diff --git a/seed/go-model/allof/go.mod b/seed/go-model/allof/go.mod deleted file mode 100644 index 6e24f1795543..000000000000 --- a/seed/go-model/allof/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/allof/fern - -go 1.21 - -toolchain go1.23.8 - -require github.com/stretchr/testify v1.8.4 - -require gopkg.in/yaml.v3 v3.0.1 // indirect - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect -) diff --git a/seed/go-model/allof/go.sum b/seed/go-model/allof/go.sum deleted file mode 100644 index fa4b6e6825c4..000000000000 --- a/seed/go-model/allof/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-model/allof/internal/extra_properties.go b/seed/go-model/allof/internal/extra_properties.go deleted file mode 100644 index 57517691f132..000000000000 --- a/seed/go-model/allof/internal/extra_properties.go +++ /dev/null @@ -1,141 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "strings" -) - -// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. -func MarshalJSONWithExtraProperty(marshaler any, key string, value any) ([]byte, error) { - return MarshalJSONWithExtraProperties(marshaler, map[string]any{key: value}) -} - -// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. -func MarshalJSONWithExtraProperties(marshaler any, extraProperties map[string]any) ([]byte, error) { - bytes, err := json.Marshal(marshaler) - if err != nil { - return nil, err - } - if len(extraProperties) == 0 { - return bytes, nil - } - keys, err := getKeys(marshaler) - if err != nil { - return nil, err - } - for _, key := range keys { - if _, ok := extraProperties[key]; ok { - return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) - } - } - extraBytes, err := json.Marshal(extraProperties) - if err != nil { - return nil, err - } - if isEmptyJSON(bytes) { - if isEmptyJSON(extraBytes) { - return bytes, nil - } - return extraBytes, nil - } - result := bytes[:len(bytes)-1] - result = append(result, ',') - result = append(result, extraBytes[1:len(extraBytes)-1]...) - result = append(result, '}') - return result, nil -} - -// ExtractExtraProperties extracts any extra properties from the given value. -func ExtractExtraProperties(bytes []byte, value any, exclude ...string) (map[string]any, error) { - val := reflect.ValueOf(value) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil, fmt.Errorf("value must be non-nil to extract extra properties") - } - val = val.Elem() - } - if err := json.Unmarshal(bytes, &value); err != nil { - return nil, err - } - var extraProperties map[string]any - if err := json.Unmarshal(bytes, &extraProperties); err != nil { - return nil, err - } - for i := 0; i < val.Type().NumField(); i++ { - key := jsonKey(val.Type().Field(i)) - if key == "" || key == "-" { - continue - } - delete(extraProperties, key) - } - for _, key := range exclude { - delete(extraProperties, key) - } - if len(extraProperties) == 0 { - return nil, nil - } - return extraProperties, nil -} - -// getKeys returns the keys associated with the given value. The value must be a -// a struct or a map with string keys. -func getKeys(value any) ([]string, error) { - val := reflect.ValueOf(value) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - if !val.IsValid() { - return nil, nil - } - switch val.Kind() { - case reflect.Struct: - return getKeysForStructType(val.Type()), nil - case reflect.Map: - var keys []string - if val.Type().Key().Kind() != reflect.String { - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } - for _, key := range val.MapKeys() { - keys = append(keys, key.String()) - } - return keys, nil - default: - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } -} - -// getKeysForStructType returns all the keys associated with the given struct type, -// visiting embedded fields recursively. -func getKeysForStructType(structType reflect.Type) []string { - if structType.Kind() == reflect.Pointer { - structType = structType.Elem() - } - if structType.Kind() != reflect.Struct { - return nil - } - var keys []string - for i := 0; i < structType.NumField(); i++ { - field := structType.Field(i) - if field.Anonymous { - keys = append(keys, getKeysForStructType(field.Type)...) - continue - } - keys = append(keys, jsonKey(field)) - } - return keys -} - -// jsonKey returns the JSON key from the struct tag of the given field, -// excluding the omitempty flag (if any). -func jsonKey(field reflect.StructField) string { - return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") -} - -// isEmptyJSON returns true if the given data is empty, the empty JSON object, or -// an explicit null. -func isEmptyJSON(data []byte) bool { - return len(data) <= 2 || bytes.Equal(data, []byte("null")) -} diff --git a/seed/go-model/allof/internal/extra_properties_test.go b/seed/go-model/allof/internal/extra_properties_test.go deleted file mode 100644 index 0d46257763fb..000000000000 --- a/seed/go-model/allof/internal/extra_properties_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package internal - -import ( - "encoding/json" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testMarshaler struct { - Name string `json:"name"` - BirthDate time.Time `json:"birthDate"` - CreatedAt time.Time `json:"created_at"` -} - -func (t *testMarshaler) MarshalJSON() ([]byte, error) { - type embed testMarshaler - var marshaler = struct { - embed - BirthDate string `json:"birthDate"` - CreatedAt string `json:"created_at"` - }{ - embed: embed(*t), - BirthDate: t.BirthDate.Format("2006-01-02"), - CreatedAt: t.CreatedAt.Format(time.RFC3339), - } - return MarshalJSONWithExtraProperty(marshaler, "type", "test") -} - -func TestMarshalJSONWithExtraProperties(t *testing.T) { - tests := []struct { - desc string - giveMarshaler any - giveExtraProperties map[string]any - wantBytes []byte - wantError string - }{ - { - desc: "invalid type", - giveMarshaler: []string{"invalid"}, - giveExtraProperties: map[string]any{"key": "overwrite"}, - wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, - }, - { - desc: "invalid key type", - giveMarshaler: map[int]any{42: "value"}, - giveExtraProperties: map[string]any{"key": "overwrite"}, - wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, - }, - { - desc: "invalid map overwrite", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{"key": "overwrite"}, - wantError: `cannot add extra property "key" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]any{"birthDate": "2000-01-01"}, - wantError: `cannot add extra property "birthDate" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite embedded type", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]any{"name": "bob"}, - wantError: `cannot add extra property "name" because it is already defined on the type`, - }, - { - desc: "nil", - giveMarshaler: nil, - giveExtraProperties: nil, - wantBytes: []byte(`null`), - }, - { - desc: "empty", - giveMarshaler: map[string]any{}, - giveExtraProperties: map[string]any{}, - wantBytes: []byte(`{}`), - }, - { - desc: "no extra properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "only extra properties", - giveMarshaler: map[string]any{}, - giveExtraProperties: map[string]any{"key": "value"}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "single extra property", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{"extra": "property"}, - wantBytes: []byte(`{"key":"value","extra":"property"}`), - }, - { - desc: "multiple extra properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{"one": 1, "two": 2}, - wantBytes: []byte(`{"key":"value","one":1,"two":2}`), - }, - { - desc: "nested properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{ - "user": map[string]any{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), - }, - { - desc: "multiple nested properties", - giveMarshaler: map[string]any{"key": "value"}, - giveExtraProperties: map[string]any{ - "metadata": map[string]any{ - "ip": "127.0.0.1", - }, - "user": map[string]any{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), - }, - { - desc: "custom marshaler", - giveMarshaler: &testMarshaler{ - Name: "alice", - BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - }, - giveExtraProperties: map[string]any{ - "extra": "property", - }, - wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) - if tt.wantError != "" { - require.EqualError(t, err, tt.wantError) - assert.Nil(t, tt.wantBytes) - return - } - require.NoError(t, err) - assert.Equal(t, tt.wantBytes, bytes) - - value := make(map[string]any) - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -func TestExtractExtraProperties(t *testing.T) { - t.Run("none", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) - - t.Run("non-nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) - }) - - t.Run("nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value *user - _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - assert.EqualError(t, err, "value must be non-nil to extract extra properties") - }) - - t.Run("non-zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) - }) - - t.Run("zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value user - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]any{"age": float64(42)}, extraProperties) - }) - - t.Run("exclude", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) -} diff --git a/seed/go-model/allof/internal/stringer.go b/seed/go-model/allof/internal/stringer.go deleted file mode 100644 index 0be54d1b5359..000000000000 --- a/seed/go-model/allof/internal/stringer.go +++ /dev/null @@ -1,13 +0,0 @@ -package internal - -import "encoding/json" - -// StringifyJSON returns a pretty JSON string representation of -// the given value. -func StringifyJSON(value any) (string, error) { - bytes, err := json.MarshalIndent(value, "", " ") - if err != nil { - return "", err - } - return string(bytes), nil -} diff --git a/seed/go-model/allof/internal/time.go b/seed/go-model/allof/internal/time.go deleted file mode 100644 index 57f901a35ed8..000000000000 --- a/seed/go-model/allof/internal/time.go +++ /dev/null @@ -1,165 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "time" -) - -const dateFormat = "2006-01-02" - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date (e.g. 2006-01-02). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type Date struct { - t *time.Time -} - -// NewDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewDate(t time.Time) *Date { - return &Date{t: &t} -} - -// NewOptionalDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDate(t *time.Time) *Date { - if t == nil { - return nil - } - return &Date{t: t} -} - -// Time returns the Date's underlying time, if any. If the -// date is nil, the zero value is returned. -func (d *Date) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the Date's underlying time.Time, if any. -func (d *Date) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *Date) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(dateFormat)) -} - -func (d *Date) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - parsedTime, err := time.Parse(dateFormat, raw) - if err != nil { - return err - } - - *d = Date{t: &parsedTime} - return nil -} - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type DateTime struct { - t *time.Time -} - -// NewDateTime returns a new *DateTime. -func NewDateTime(t time.Time) *DateTime { - return &DateTime{t: &t} -} - -// NewOptionalDateTime returns a new *DateTime. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDateTime(t *time.Time) *DateTime { - if t == nil { - return nil - } - return &DateTime{t: t} -} - -// Time returns the DateTime's underlying time, if any. If the -// date-time is nil, the zero value is returned. -func (d *DateTime) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. -func (d *DateTime) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *DateTime) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(time.RFC3339)) -} - -func (d *DateTime) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - // If the value is not a string, check if it is a number (unix epoch seconds). - var epoch int64 - if numErr := json.Unmarshal(data, &epoch); numErr == nil { - t := time.Unix(epoch, 0).UTC() - *d = DateTime{t: &t} - return nil - } - return err - } - - // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). - parsedTime, err := time.Parse(time.RFC3339Nano, raw) - if err == nil { - *d = DateTime{t: &parsedTime} - return nil - } - rfc3339NanoErr := err - - // Fall back to ISO 8601 without timezone (assume UTC). - parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - iso8601Err := err - - // Fall back to date-only format. - parsedTime, err = time.Parse("2006-01-02", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - dateOnlyErr := err - - return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) -} diff --git a/seed/go-model/allof/snippet.json b/seed/go-model/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/go-model/allof/types.go b/seed/go-model/allof/types.go deleted file mode 100644 index dca6f261ecfb..000000000000 --- a/seed/go-model/allof/types.go +++ /dev/null @@ -1,1185 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - json "encoding/json" - fmt "fmt" - time "time" - - internal "github.com/allof/fern/internal" -) - -type PaginatedResult struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []any `json:"results" url:"results"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (p *PaginatedResult) GetPaging() *PagingCursors { - if p == nil { - return nil - } - return p.Paging -} - -func (p *PaginatedResult) GetResults() []any { - if p == nil { - return nil - } - return p.Results -} - -func (p *PaginatedResult) GetExtraProperties() map[string]any { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PaginatedResult) UnmarshalJSON( - data []byte, -) error { - type unmarshaler PaginatedResult - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PaginatedResult(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PaginatedResult) String() string { - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -type PagingCursors struct { - // Cursor for the next page of results. - Next string `json:"next" url:"next"` - // Cursor for the previous page of results. - Previous *string `json:"previous,omitempty" url:"previous,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (p *PagingCursors) GetNext() string { - if p == nil { - return "" - } - return p.Next -} - -func (p *PagingCursors) GetPrevious() *string { - if p == nil { - return nil - } - return p.Previous -} - -func (p *PagingCursors) GetExtraProperties() map[string]any { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PagingCursors) UnmarshalJSON( - data []byte, -) error { - type unmarshaler PagingCursors - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PagingCursors(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PagingCursors) String() string { - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -// Execution environment for a rule. -type RuleExecutionContext string - -const ( - RuleExecutionContextProd = "prod" - RuleExecutionContextStaging = "staging" - RuleExecutionContextDev = "dev" -) - -func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { - switch s { - case "prod": - return RuleExecutionContextProd, nil - case "staging": - return RuleExecutionContextStaging, nil - case "dev": - return RuleExecutionContextDev, nil - } - var t RuleExecutionContext - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleExecutionContext) Ptr() *RuleExecutionContext { - return &r -} - -// Common audit metadata. -type AuditInfo struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (a *AuditInfo) GetCreatedBy() *string { - if a == nil { - return nil - } - return a.CreatedBy -} - -func (a *AuditInfo) GetCreatedDateTime() *time.Time { - if a == nil { - return nil - } - return a.CreatedDateTime -} - -func (a *AuditInfo) GetModifiedBy() *string { - if a == nil { - return nil - } - return a.ModifiedBy -} - -func (a *AuditInfo) GetModifiedDateTime() *time.Time { - if a == nil { - return nil - } - return a.ModifiedDateTime -} - -func (a *AuditInfo) GetExtraProperties() map[string]any { - if a == nil { - return nil - } - return a.extraProperties -} - -func (a *AuditInfo) UnmarshalJSON( - data []byte, -) error { - type embed AuditInfo - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*a), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *a = AuditInfo(unmarshaler.embed) - a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *a) - if err != nil { - return err - } - a.extraProperties = extraProperties - a.rawJSON = json.RawMessage(data) - return nil -} - -func (a *AuditInfo) MarshalJSON() ([]byte, error) { - type embed AuditInfo - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*a), - CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), - } - return json.Marshal(marshaler) -} - -func (a *AuditInfo) String() string { - if len(a.rawJSON) > 0 { - if value, err := internal.StringifyJSON(a.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(a); err == nil { - return value - } - return fmt.Sprintf("%#v", a) -} - -type RuleType struct { - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Description *string `json:"description,omitempty" url:"description,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (r *RuleType) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleType) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleType) GetDescription() *string { - if r == nil { - return nil - } - return r.Description -} - -func (r *RuleType) GetExtraProperties() map[string]any { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleType) UnmarshalJSON( - data []byte, -) error { - type unmarshaler RuleType - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleType(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleType) String() string { - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type RuleTypeSearchResponse struct { - // Current page of results from the requested resource. - Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` - Paging *PagingCursors `json:"paging" url:"paging"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (r *RuleTypeSearchResponse) GetResults() []*RuleType { - if r == nil { - return nil - } - return r.Results -} - -func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { - if r == nil { - return nil - } - return r.Paging -} - -func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]any { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleTypeSearchResponse) UnmarshalJSON( - data []byte, -) error { - type unmarshaler RuleTypeSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleTypeSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleTypeSearchResponse) String() string { - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type User struct { - ID string `json:"id" url:"id"` - Email string `json:"email" url:"email"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (u *User) GetID() string { - if u == nil { - return "" - } - return u.ID -} - -func (u *User) GetEmail() string { - if u == nil { - return "" - } - return u.Email -} - -func (u *User) GetExtraProperties() map[string]any { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *User) UnmarshalJSON( - data []byte, -) error { - type unmarshaler User - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = User(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *User) String() string { - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} - -type UserSearchResponse struct { - // Current page of results from the requested resource. - Results []*User `json:"results,omitempty" url:"results,omitempty"` - Paging *PagingCursors `json:"paging" url:"paging"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (u *UserSearchResponse) GetResults() []*User { - if u == nil { - return nil - } - return u.Results -} - -func (u *UserSearchResponse) GetPaging() *PagingCursors { - if u == nil { - return nil - } - return u.Paging -} - -func (u *UserSearchResponse) GetExtraProperties() map[string]any { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *UserSearchResponse) UnmarshalJSON( - data []byte, -) error { - type unmarshaler UserSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = UserSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *UserSearchResponse) String() string { - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} - -type RuleResponseStatus string - -const ( - RuleResponseStatusActive = "active" - RuleResponseStatusInactive = "inactive" - RuleResponseStatusDraft = "draft" -) - -func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { - switch s { - case "active": - return RuleResponseStatusActive, nil - case "inactive": - return RuleResponseStatusInactive, nil - case "draft": - return RuleResponseStatusDraft, nil - } - var t RuleResponseStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleResponseStatus) Ptr() *RuleResponseStatus { - return &r -} - -type RuleResponse struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Status *RuleResponseStatus `json:"status" url:"status"` - ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (r *RuleResponse) GetCreatedBy() *string { - if r == nil { - return nil - } - return r.CreatedBy -} - -func (r *RuleResponse) GetCreatedDateTime() *time.Time { - if r == nil { - return nil - } - return r.CreatedDateTime -} - -func (r *RuleResponse) GetModifiedBy() *string { - if r == nil { - return nil - } - return r.ModifiedBy -} - -func (r *RuleResponse) GetModifiedDateTime() *time.Time { - if r == nil { - return nil - } - return r.ModifiedDateTime -} - -func (r *RuleResponse) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleResponse) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleResponse) GetStatus() *RuleResponseStatus { - if r == nil { - return nil - } - return r.Status -} - -func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { - if r == nil { - return nil - } - return r.ExecutionContext -} - -func (r *RuleResponse) GetExtraProperties() map[string]any { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleResponse) UnmarshalJSON( - data []byte, -) error { - type embed RuleResponse - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*r), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *r = RuleResponse(unmarshaler.embed) - r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleResponse) MarshalJSON() ([]byte, error) { - type embed RuleResponse - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime"` - }{ - embed: embed(*r), - CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), - } - return json.Marshal(marshaler) -} - -func (r *RuleResponse) String() string { - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type Identifiable struct { - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Identifiable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (i *Identifiable) GetID() string { - if i == nil { - return "" - } - return i.ID -} - -func (i *Identifiable) GetName() *string { - if i == nil { - return nil - } - return i.Name -} - -func (i *Identifiable) GetExtraProperties() map[string]any { - if i == nil { - return nil - } - return i.extraProperties -} - -func (i *Identifiable) UnmarshalJSON( - data []byte, -) error { - type unmarshaler Identifiable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *i = Identifiable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *i) - if err != nil { - return err - } - i.extraProperties = extraProperties - i.rawJSON = json.RawMessage(data) - return nil -} - -func (i *Identifiable) String() string { - if len(i.rawJSON) > 0 { - if value, err := internal.StringifyJSON(i.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(i); err == nil { - return value - } - return fmt.Sprintf("%#v", i) -} - -type Describable struct { - // Display name from Describable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (d *Describable) GetName() *string { - if d == nil { - return nil - } - return d.Name -} - -func (d *Describable) GetSummary() *string { - if d == nil { - return nil - } - return d.Summary -} - -func (d *Describable) GetExtraProperties() map[string]any { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *Describable) UnmarshalJSON( - data []byte, -) error { - type unmarshaler Describable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = Describable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *Describable) String() string { - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -type CombinedEntityStatus string - -const ( - CombinedEntityStatusActive = "active" - CombinedEntityStatusArchived = "archived" -) - -func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { - switch s { - case "active": - return CombinedEntityStatusActive, nil - case "archived": - return CombinedEntityStatusArchived, nil - } - var t CombinedEntityStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { - return &c -} - -type CombinedEntity struct { - Status *CombinedEntityStatus `json:"status" url:"status"` - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Identifiable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (c *CombinedEntity) GetStatus() *CombinedEntityStatus { - if c == nil { - return nil - } - return c.Status -} - -func (c *CombinedEntity) GetID() string { - if c == nil { - return "" - } - return c.ID -} - -func (c *CombinedEntity) GetName() *string { - if c == nil { - return nil - } - return c.Name -} - -func (c *CombinedEntity) GetSummary() *string { - if c == nil { - return nil - } - return c.Summary -} - -func (c *CombinedEntity) GetExtraProperties() map[string]any { - if c == nil { - return nil - } - return c.extraProperties -} - -func (c *CombinedEntity) UnmarshalJSON( - data []byte, -) error { - type unmarshaler CombinedEntity - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *c = CombinedEntity(value) - extraProperties, err := internal.ExtractExtraProperties(data, *c) - if err != nil { - return err - } - c.extraProperties = extraProperties - c.rawJSON = json.RawMessage(data) - return nil -} - -func (c *CombinedEntity) String() string { - if len(c.rawJSON) > 0 { - if value, err := internal.StringifyJSON(c.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(c); err == nil { - return value - } - return fmt.Sprintf("%#v", c) -} - -type BaseOrgMetadata struct { - // Deployment region from BaseOrg. - Region string `json:"region" url:"region"` - // Subscription tier. - Tier *string `json:"tier,omitempty" url:"tier,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (b *BaseOrgMetadata) GetRegion() string { - if b == nil { - return "" - } - return b.Region -} - -func (b *BaseOrgMetadata) GetTier() *string { - if b == nil { - return nil - } - return b.Tier -} - -func (b *BaseOrgMetadata) GetExtraProperties() map[string]any { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrgMetadata) UnmarshalJSON( - data []byte, -) error { - type unmarshaler BaseOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrgMetadata) String() string { - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -type BaseOrg struct { - ID string `json:"id" url:"id"` - Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (b *BaseOrg) GetID() string { - if b == nil { - return "" - } - return b.ID -} - -func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { - if b == nil { - return nil - } - return b.Metadata -} - -func (b *BaseOrg) GetExtraProperties() map[string]any { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrg) UnmarshalJSON( - data []byte, -) error { - type unmarshaler BaseOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrg) String() string { - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -type DetailedOrgMetadata struct { - // Deployment region from DetailedOrg. - Region string `json:"region" url:"region"` - // Custom domain name. - Domain *string `json:"domain,omitempty" url:"domain,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (d *DetailedOrgMetadata) GetRegion() string { - if d == nil { - return "" - } - return d.Region -} - -func (d *DetailedOrgMetadata) GetDomain() *string { - if d == nil { - return nil - } - return d.Domain -} - -func (d *DetailedOrgMetadata) GetExtraProperties() map[string]any { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrgMetadata) UnmarshalJSON( - data []byte, -) error { - type unmarshaler DetailedOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrgMetadata) String() string { - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -type DetailedOrg struct { - Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { - if d == nil { - return nil - } - return d.Metadata -} - -func (d *DetailedOrg) GetExtraProperties() map[string]any { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrg) UnmarshalJSON( - data []byte, -) error { - type unmarshaler DetailedOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrg) String() string { - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -type Organization struct { - Name string `json:"name" url:"name"` - ID string `json:"id" url:"id"` - Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - extraProperties map[string]any - rawJSON json.RawMessage -} - -func (o *Organization) GetName() string { - if o == nil { - return "" - } - return o.Name -} - -func (o *Organization) GetID() string { - if o == nil { - return "" - } - return o.ID -} - -func (o *Organization) GetMetadata() *BaseOrgMetadata { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *Organization) GetExtraProperties() map[string]any { - if o == nil { - return nil - } - return o.extraProperties -} - -func (o *Organization) UnmarshalJSON( - data []byte, -) error { - type unmarshaler Organization - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *o = Organization(value) - extraProperties, err := internal.ExtractExtraProperties(data, *o) - if err != nil { - return err - } - o.extraProperties = extraProperties - o.rawJSON = json.RawMessage(data) - return nil -} - -func (o *Organization) String() string { - if len(o.rawJSON) > 0 { - if value, err := internal.StringifyJSON(o.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(o); err == nil { - return value - } - return fmt.Sprintf("%#v", o) -} diff --git a/seed/go-sdk/allof-inline/.fern/metadata.json b/seed/go-sdk/allof-inline/.fern/metadata.json deleted file mode 100644 index 711224a2aaf7..000000000000 --- a/seed/go-sdk/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-go-sdk", - "generatorVersion": "local", - "generatorConfig": { - "enableWireTests": false - }, - "originGitCommit": "DUMMY", - "sdkVersion": "v0.0.1" -} \ No newline at end of file diff --git a/seed/go-sdk/allof-inline/.github/workflows/ci.yml b/seed/go-sdk/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 1097e6a18acc..000000000000 --- a/seed/go-sdk/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up go - uses: actions/setup-go@v4 - - - name: Compile - run: go build ./... - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up go - uses: actions/setup-go@v4 - - - name: Lint - uses: golangci/golangci-lint-action@v9 - with: - version: v2.10.1 - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up go - uses: actions/setup-go@v4 - - - name: Setup wiremock server - run: | - PROJECT_NAME="wiremock-$(basename $(dirname $(pwd)) | tr -d '.')" - echo "PROJECT_NAME=$PROJECT_NAME" >> $GITHUB_ENV - if [ -f wiremock/docker-compose.test.yml ]; then - docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down - docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml up -d - WIREMOCK_PORT=$(docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml port wiremock 8080 | cut -d: -f2) - echo "WIREMOCK_URL=http://localhost:$WIREMOCK_PORT" >> $GITHUB_ENV - fi - - - name: Test - run: go test ./... - - - name: Teardown wiremock server - if: always() - run: | - if [ -f wiremock/docker-compose.test.yml ]; then - docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down - fi diff --git a/seed/go-sdk/allof-inline/README.md b/seed/go-sdk/allof-inline/README.md deleted file mode 100644 index b38d709447cd..000000000000 --- a/seed/go-sdk/allof-inline/README.md +++ /dev/null @@ -1,195 +0,0 @@ -# Seed Go Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FGo) - -The Seed Go library provides convenient access to the Seed APIs from Go. - -## Table of Contents - -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Request Options](#request-options) -- [Advanced](#advanced) - - [Response Headers](#response-headers) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Explicit Null](#explicit-null) -- [Contributing](#contributing) - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```go -package example - -import ( - context "context" - - fern "github.com/allof-inline/fern" - client "github.com/allof-inline/fern/client" -) - -func do() { - client := client.NewClient() - request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } - client.CreateRule( - context.TODO(), - request, - ) -} -``` - -## Environments - -You can choose between different environments by using the `option.WithBaseURL` option. You can configure any arbitrary base -URL, which is particularly useful in test environments. - -```go -client := client.NewClient( - option.WithBaseURL(api.Environments.Default), -) -``` - -## Errors - -Structured error types are returned from API calls that return non-success status codes. These errors are compatible -with the `errors.Is` and `errors.As` APIs, so you can access the error like so: - -```go -response, err := client.CreateRule(...) -if err != nil { - var apiError *core.APIError - if errors.As(err, apiError) { - // Do something with the API error ... - } - return err -} -``` - -## Request Options - -A variety of request options are included to adapt the behavior of the library, which includes configuring -authorization tokens, or providing your own instrumented `*http.Client`. - -These request options can either be -specified on the client so that they're applied on every request, or for an individual request, like so: - -> Providing your own `*http.Client` is recommended. Otherwise, the `http.DefaultClient` will be used, -> and your client will wait indefinitely for a response (unless the per-request, context-based timeout -> is used). - -```go -// Specify default options applied on every request. -client := client.NewClient( - option.WithToken(""), - option.WithHTTPClient( - &http.Client{ - Timeout: 5 * time.Second, - }, - ), -) - -// Specify options for an individual request. -response, err := client.CreateRule( - ..., - option.WithToken(""), -) -``` - -## Advanced - -### Response Headers - -You can access the raw HTTP response data by using the `WithRawResponse` field on the client. This is useful -when you need to examine the response headers received from the API call. (When the endpoint is paginated, -the raw HTTP response data will be included automatically in the Page response object.) - -```go -response, err := client.WithRawResponse.CreateRule(...) -if err != nil { - return err -} -fmt.Printf("Got response headers: %v", response.Header) -fmt.Printf("Got status code: %d", response.StatusCode) -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -If the `Retry-After` header is present in the response, the SDK will prioritize respecting its value exactly -over the default exponential backoff. - -Use the `option.WithMaxAttempts` option to configure this behavior for the entire client or an individual request: - -```go -client := client.NewClient( - option.WithMaxAttempts(1), -) - -response, err := client.CreateRule( - ..., - option.WithMaxAttempts(1), -) -``` - -### Timeouts - -Setting a timeout for each individual request is as simple as using the standard context library. Setting a one second timeout for an individual API call looks like the following: - -```go -ctx, cancel := context.WithTimeout(ctx, time.Second) -defer cancel() - -response, err := client.CreateRule(ctx, ...) -``` - -### Explicit Null - -If you want to send the explicit `null` JSON value through an optional parameter, you can use the setters\ -that come with every object. Calling a setter method for a property will flip a bit in the `explicitFields` -bitfield for that setter's object; during serialization, any property with a flipped bit will have its -omittable status stripped, so zero or `nil` values will be sent explicitly rather than omitted altogether: - -```go -type ExampleRequest struct { - // An optional string parameter. - Name *string `json:"name,omitempty" url:"-"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` -} - -request := &ExampleRequest{} -request.SetName(nil) - -response, err := client.CreateRule(ctx, request, ...) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/go-sdk/allof-inline/client/client.go b/seed/go-sdk/allof-inline/client/client.go deleted file mode 100644 index ced3e187e81b..000000000000 --- a/seed/go-sdk/allof-inline/client/client.go +++ /dev/null @@ -1,109 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package client - -import ( - context "context" - - fern "github.com/allof-inline/fern" - core "github.com/allof-inline/fern/core" - internal "github.com/allof-inline/fern/internal" - option "github.com/allof-inline/fern/option" -) - -type Client struct { - WithRawResponse *RawClient - - options *core.RequestOptions - baseURL string - caller *internal.Caller -} - -func NewClient(opts ...option.RequestOption) *Client { - options := core.NewRequestOptions(opts...) - return &Client{ - WithRawResponse: NewRawClient(options), - options: options, - baseURL: options.BaseURL, - caller: internal.NewCaller( - &internal.CallerParams{ - Client: options.HTTPClient, - MaxAttempts: options.MaxAttempts, - }, - ), - } -} - -func (c *Client) SearchRuleTypes( - ctx context.Context, - request *fern.SearchRuleTypesRequest, - opts ...option.RequestOption, -) (*fern.RuleTypeSearchResponse, error) { - response, err := c.WithRawResponse.SearchRuleTypes( - ctx, - request, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) CreateRule( - ctx context.Context, - request *fern.RuleCreateRequest, - opts ...option.RequestOption, -) (*fern.RuleResponse, error) { - response, err := c.WithRawResponse.CreateRule( - ctx, - request, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) ListUsers( - ctx context.Context, - opts ...option.RequestOption, -) (*fern.UserSearchResponse, error) { - response, err := c.WithRawResponse.ListUsers( - ctx, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) GetEntity( - ctx context.Context, - opts ...option.RequestOption, -) (*fern.CombinedEntity, error) { - response, err := c.WithRawResponse.GetEntity( - ctx, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) GetOrganization( - ctx context.Context, - opts ...option.RequestOption, -) (*fern.Organization, error) { - response, err := c.WithRawResponse.GetOrganization( - ctx, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} diff --git a/seed/go-sdk/allof-inline/client/client_test.go b/seed/go-sdk/allof-inline/client/client_test.go deleted file mode 100644 index a7a872235f09..000000000000 --- a/seed/go-sdk/allof-inline/client/client_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package client - -import ( - option "github.com/allof-inline/fern/option" - assert "github.com/stretchr/testify/assert" - http "net/http" - testing "testing" - time "time" -) - -func TestNewClient(t *testing.T) { - t.Run("default", func(t *testing.T) { - c := NewClient() - assert.Empty(t, c.baseURL) - }) - - t.Run("base url", func(t *testing.T) { - c := NewClient( - option.WithBaseURL("test.co"), - ) - assert.Equal(t, "test.co", c.baseURL) - }) - - t.Run("http client", func(t *testing.T) { - httpClient := &http.Client{ - Timeout: 5 * time.Second, - } - c := NewClient( - option.WithHTTPClient(httpClient), - ) - assert.Empty(t, c.baseURL) - }) - - t.Run("http header", func(t *testing.T) { - header := make(http.Header) - header.Set("X-API-Tenancy", "test") - c := NewClient( - option.WithHTTPHeader(header), - ) - assert.Empty(t, c.baseURL) - assert.Equal(t, "test", c.options.HTTPHeader.Get("X-API-Tenancy")) - }) -} diff --git a/seed/go-sdk/allof-inline/client/raw_client.go b/seed/go-sdk/allof-inline/client/raw_client.go deleted file mode 100644 index 79dc560be0a7..000000000000 --- a/seed/go-sdk/allof-inline/client/raw_client.go +++ /dev/null @@ -1,238 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package client - -import ( - context "context" - http "net/http" - - fern "github.com/allof-inline/fern" - core "github.com/allof-inline/fern/core" - internal "github.com/allof-inline/fern/internal" - option "github.com/allof-inline/fern/option" -) - -type RawClient struct { - baseURL string - caller *internal.Caller - options *core.RequestOptions -} - -func NewRawClient(options *core.RequestOptions) *RawClient { - return &RawClient{ - options: options, - baseURL: options.BaseURL, - caller: internal.NewCaller( - &internal.CallerParams{ - Client: options.HTTPClient, - MaxAttempts: options.MaxAttempts, - }, - ), - } -} - -func (r *RawClient) SearchRuleTypes( - ctx context.Context, - request *fern.SearchRuleTypesRequest, - opts ...option.RequestOption, -) (*core.Response[*fern.RuleTypeSearchResponse], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/rule-types" - queryParams, err := internal.QueryValues(request) - if err != nil { - return nil, err - } - if len(queryParams) > 0 { - endpointURL += "?" + queryParams.Encode() - } - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.RuleTypeSearchResponse - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.RuleTypeSearchResponse]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) CreateRule( - ctx context.Context, - request *fern.RuleCreateRequest, - opts ...option.RequestOption, -) (*core.Response[*fern.RuleResponse], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/rules" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - headers.Add("Content-Type", "application/json") - var response *fern.RuleResponse - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Request: request, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.RuleResponse]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) ListUsers( - ctx context.Context, - opts ...option.RequestOption, -) (*core.Response[*fern.UserSearchResponse], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/users" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.UserSearchResponse - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.UserSearchResponse]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) GetEntity( - ctx context.Context, - opts ...option.RequestOption, -) (*core.Response[*fern.CombinedEntity], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/entities" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.CombinedEntity - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.CombinedEntity]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) GetOrganization( - ctx context.Context, - opts ...option.RequestOption, -) (*core.Response[*fern.Organization], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/organizations" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.Organization - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.Organization]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} diff --git a/seed/go-sdk/allof-inline/core/api_error.go b/seed/go-sdk/allof-inline/core/api_error.go deleted file mode 100644 index 6168388541b4..000000000000 --- a/seed/go-sdk/allof-inline/core/api_error.go +++ /dev/null @@ -1,47 +0,0 @@ -package core - -import ( - "fmt" - "net/http" -) - -// APIError is a lightweight wrapper around the standard error -// interface that preserves the status code from the RPC, if any. -type APIError struct { - err error - - StatusCode int `json:"-"` - Header http.Header `json:"-"` -} - -// NewAPIError constructs a new API error. -func NewAPIError(statusCode int, header http.Header, err error) *APIError { - return &APIError{ - err: err, - Header: header, - StatusCode: statusCode, - } -} - -// Unwrap returns the underlying error. This also makes the error compatible -// with errors.As and errors.Is. -func (a *APIError) Unwrap() error { - if a == nil { - return nil - } - return a.err -} - -// Error returns the API error's message. -func (a *APIError) Error() string { - if a == nil || (a.err == nil && a.StatusCode == 0) { - return "" - } - if a.err == nil { - return fmt.Sprintf("%d", a.StatusCode) - } - if a.StatusCode == 0 { - return a.err.Error() - } - return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) -} diff --git a/seed/go-sdk/allof-inline/core/http.go b/seed/go-sdk/allof-inline/core/http.go deleted file mode 100644 index 92c435692940..000000000000 --- a/seed/go-sdk/allof-inline/core/http.go +++ /dev/null @@ -1,15 +0,0 @@ -package core - -import "net/http" - -// HTTPClient is an interface for a subset of the *http.Client. -type HTTPClient interface { - Do(*http.Request) (*http.Response, error) -} - -// Response is an HTTP response from an HTTP client. -type Response[T any] struct { - StatusCode int - Header http.Header - Body T -} diff --git a/seed/go-sdk/allof-inline/core/request_option.go b/seed/go-sdk/allof-inline/core/request_option.go deleted file mode 100644 index 2ae425289a2a..000000000000 --- a/seed/go-sdk/allof-inline/core/request_option.go +++ /dev/null @@ -1,119 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package core - -import ( - http "net/http" - url "net/url" -) - -// RequestOption adapts the behavior of the client or an individual request. -type RequestOption interface { - applyRequestOptions(*RequestOptions) -} - -// RequestOptions defines all of the possible request options. -// -// This type is primarily used by the generated code and is not meant -// to be used directly; use the option package instead. -type RequestOptions struct { - BaseURL string - HTTPClient HTTPClient - HTTPHeader http.Header - BodyProperties map[string]interface{} - QueryParameters url.Values - MaxAttempts uint - MaxBufSize int -} - -// NewRequestOptions returns a new *RequestOptions value. -// -// This function is primarily used by the generated code and is not meant -// to be used directly; use RequestOption instead. -func NewRequestOptions(opts ...RequestOption) *RequestOptions { - options := &RequestOptions{ - HTTPHeader: make(http.Header), - BodyProperties: make(map[string]interface{}), - QueryParameters: make(url.Values), - } - for _, opt := range opts { - opt.applyRequestOptions(options) - } - return options -} - -// ToHeader maps the configured request options into a http.Header used -// for the request(s). -func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } - -func (r *RequestOptions) cloneHeader() http.Header { - headers := r.HTTPHeader.Clone() - headers.Set("X-Fern-Language", "Go") - headers.Set("X-Fern-SDK-Name", "github.com/allof-inline/fern") - headers.Set("X-Fern-SDK-Version", "v0.0.1") - headers.Set("User-Agent", "github.com/allof-inline/fern/0.0.1") - return headers -} - -// BaseURLOption implements the RequestOption interface. -type BaseURLOption struct { - BaseURL string -} - -func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { - opts.BaseURL = b.BaseURL -} - -// HTTPClientOption implements the RequestOption interface. -type HTTPClientOption struct { - HTTPClient HTTPClient -} - -func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { - opts.HTTPClient = h.HTTPClient -} - -// HTTPHeaderOption implements the RequestOption interface. -type HTTPHeaderOption struct { - HTTPHeader http.Header -} - -func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { - opts.HTTPHeader = h.HTTPHeader -} - -// BodyPropertiesOption implements the RequestOption interface. -type BodyPropertiesOption struct { - BodyProperties map[string]interface{} -} - -func (b *BodyPropertiesOption) applyRequestOptions(opts *RequestOptions) { - opts.BodyProperties = b.BodyProperties -} - -// QueryParametersOption implements the RequestOption interface. -type QueryParametersOption struct { - QueryParameters url.Values -} - -func (q *QueryParametersOption) applyRequestOptions(opts *RequestOptions) { - opts.QueryParameters = q.QueryParameters -} - -// MaxAttemptsOption implements the RequestOption interface. -type MaxAttemptsOption struct { - MaxAttempts uint -} - -func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { - opts.MaxAttempts = m.MaxAttempts -} - -// MaxBufSizeOption implements the RequestOption interface. -type MaxBufSizeOption struct { - MaxBufSize int -} - -func (m *MaxBufSizeOption) applyRequestOptions(opts *RequestOptions) { - opts.MaxBufSize = m.MaxBufSize -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go deleted file mode 100644 index 62bb212116c0..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example0/snippet.go +++ /dev/null @@ -1,22 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof-inline/fern" - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.SearchRuleTypesRequest{} - client.SearchRuleTypes( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go deleted file mode 100644 index 29f0a4cc1a87..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example1/snippet.go +++ /dev/null @@ -1,26 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof-inline/fern" - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.SearchRuleTypesRequest{ - Query: fern.String( - "query", - ), - } - client.SearchRuleTypes( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go deleted file mode 100644 index ead43f2a6d2f..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example2/snippet.go +++ /dev/null @@ -1,25 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof-inline/fern" - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } - client.CreateRule( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go deleted file mode 100644 index ead43f2a6d2f..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example3/snippet.go +++ /dev/null @@ -1,25 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof-inline/fern" - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } - client.CreateRule( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go deleted file mode 100644 index db43c60a2916..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example4/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.ListUsers( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go deleted file mode 100644 index db43c60a2916..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example5/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.ListUsers( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go deleted file mode 100644 index 0f87de8dc18f..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example6/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetEntity( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go deleted file mode 100644 index 0f87de8dc18f..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example7/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetEntity( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go deleted file mode 100644 index 49494c3c0576..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example8/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetOrganization( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go b/seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go deleted file mode 100644 index 49494c3c0576..000000000000 --- a/seed/go-sdk/allof-inline/dynamic-snippets/example9/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof-inline/fern/client" - option "github.com/allof-inline/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetOrganization( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof-inline/environments.go b/seed/go-sdk/allof-inline/environments.go deleted file mode 100644 index 27941a670e97..000000000000 --- a/seed/go-sdk/allof-inline/environments.go +++ /dev/null @@ -1,13 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -// Environments defines all of the API environments. -// These values can be used with the WithBaseURL -// RequestOption to override the client's default environment, -// if any. -var Environments = struct { - Default string -}{ - Default: "https://api.example.com", -} diff --git a/seed/go-sdk/allof-inline/error_codes.go b/seed/go-sdk/allof-inline/error_codes.go deleted file mode 100644 index f8da5275cc1d..000000000000 --- a/seed/go-sdk/allof-inline/error_codes.go +++ /dev/null @@ -1,9 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - internal "github.com/allof-inline/fern/internal" -) - -var ErrorCodes internal.ErrorCodes = internal.ErrorCodes{} diff --git a/seed/go-sdk/allof-inline/file_param.go b/seed/go-sdk/allof-inline/file_param.go deleted file mode 100644 index 737abb97c837..000000000000 --- a/seed/go-sdk/allof-inline/file_param.go +++ /dev/null @@ -1,41 +0,0 @@ -package api - -import ( - "io" -) - -// FileParam is a file type suitable for multipart/form-data uploads. -type FileParam struct { - io.Reader - filename string - contentType string -} - -// FileParamOption adapts the behavior of the FileParam. No options are -// implemented yet, but this interface allows for future extensibility. -type FileParamOption interface { - apply() -} - -// NewFileParam returns a *FileParam type suitable for multipart/form-data uploads. All file -// upload endpoints accept a simple io.Reader, which is usually created by opening a file -// via os.Open. -// -// However, some endpoints require additional metadata about the file such as a specific -// Content-Type or custom filename. FileParam makes it easier to create the correct type -// signature for these endpoints. -func NewFileParam( - reader io.Reader, - filename string, - contentType string, - opts ...FileParamOption, -) *FileParam { - return &FileParam{ - Reader: reader, - filename: filename, - contentType: contentType, - } -} - -func (f *FileParam) Name() string { return f.filename } -func (f *FileParam) ContentType() string { return f.contentType } diff --git a/seed/go-sdk/allof-inline/go.mod b/seed/go-sdk/allof-inline/go.mod deleted file mode 100644 index d1ba4957a004..000000000000 --- a/seed/go-sdk/allof-inline/go.mod +++ /dev/null @@ -1,16 +0,0 @@ -module github.com/allof-inline/fern - -go 1.21 - -toolchain go1.23.8 - -require github.com/google/uuid v1.6.0 - -require github.com/stretchr/testify v1.8.4 - -require gopkg.in/yaml.v3 v3.0.1 // indirect - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect -) diff --git a/seed/go-sdk/allof-inline/go.sum b/seed/go-sdk/allof-inline/go.sum deleted file mode 100644 index fcca6d128057..000000000000 --- a/seed/go-sdk/allof-inline/go.sum +++ /dev/null @@ -1,12 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/allof-inline/internal/caller.go b/seed/go-sdk/allof-inline/internal/caller.go deleted file mode 100644 index e9b32b76af32..000000000000 --- a/seed/go-sdk/allof-inline/internal/caller.go +++ /dev/null @@ -1,311 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "reflect" - "strings" - - "github.com/allof-inline/fern/core" -) - -const ( - // contentType specifies the JSON Content-Type header value. - contentType = "application/json" - contentTypeHeader = "Content-Type" - contentTypeFormURLEncoded = "application/x-www-form-urlencoded" -) - -// Caller calls APIs and deserializes their response, if any. -type Caller struct { - client core.HTTPClient - retrier *Retrier -} - -// CallerParams represents the parameters used to constrcut a new *Caller. -type CallerParams struct { - Client core.HTTPClient - MaxAttempts uint -} - -// NewCaller returns a new *Caller backed by the given parameters. -func NewCaller(params *CallerParams) *Caller { - var httpClient core.HTTPClient = http.DefaultClient - if params.Client != nil { - httpClient = params.Client - } - var retryOptions []RetryOption - if params.MaxAttempts > 0 { - retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) - } - return &Caller{ - client: httpClient, - retrier: NewRetrier(retryOptions...), - } -} - -// CallParams represents the parameters used to issue an API call. -type CallParams struct { - URL string - Method string - MaxAttempts uint - Headers http.Header - BodyProperties map[string]interface{} - QueryParameters url.Values - Client core.HTTPClient - Request interface{} - Response interface{} - ResponseIsOptional bool - ErrorDecoder ErrorDecoder -} - -// CallResponse is a parsed HTTP response from an API call. -type CallResponse struct { - StatusCode int - Header http.Header -} - -// Call issues an API call according to the given call parameters. -func (c *Caller) Call(ctx context.Context, params *CallParams) (*CallResponse, error) { - url := buildURL(params.URL, params.QueryParameters) - req, err := newRequest( - ctx, - url, - params.Method, - params.Headers, - params.Request, - params.BodyProperties, - ) - if err != nil { - return nil, err - } - - // If the call has been cancelled, don't issue the request. - if err := ctx.Err(); err != nil { - return nil, err - } - - client := c.client - if params.Client != nil { - // Use the HTTP client scoped to the request. - client = params.Client - } - - var retryOptions []RetryOption - if params.MaxAttempts > 0 { - retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) - } - - resp, err := c.retrier.Run( - client.Do, - req, - params.ErrorDecoder, - retryOptions..., - ) - if err != nil { - return nil, err - } - - // Close the response body after we're done. - defer func() { _ = resp.Body.Close() }() - - // Check if the call was cancelled before we return the error - // associated with the call and/or unmarshal the response data. - if err := ctx.Err(); err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return nil, decodeError(resp, params.ErrorDecoder) - } - - // Mutate the response parameter in-place. - if params.Response != nil { - if writer, ok := params.Response.(io.Writer); ok { - _, err = io.Copy(writer, resp.Body) - } else { - err = json.NewDecoder(resp.Body).Decode(params.Response) - } - if err != nil { - if err == io.EOF { - if params.ResponseIsOptional { - // The response is optional, so we should ignore the - // io.EOF error - return &CallResponse{ - StatusCode: resp.StatusCode, - Header: resp.Header, - }, nil - } - return nil, fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) - } - return nil, err - } - } - - return &CallResponse{ - StatusCode: resp.StatusCode, - Header: resp.Header, - }, nil -} - -// buildURL constructs the final URL by appending the given query parameters (if any). -func buildURL( - url string, - queryParameters url.Values, -) string { - if len(queryParameters) == 0 { - return url - } - if strings.ContainsRune(url, '?') { - url += "&" - } else { - url += "?" - } - url += queryParameters.Encode() - return url -} - -// newRequest returns a new *http.Request with all of the fields -// required to issue the call. -func newRequest( - ctx context.Context, - url string, - method string, - endpointHeaders http.Header, - request interface{}, - bodyProperties map[string]interface{}, -) (*http.Request, error) { - // Determine the content type from headers, defaulting to JSON. - reqContentType := contentType - if endpointHeaders != nil { - if ct := endpointHeaders.Get(contentTypeHeader); ct != "" { - reqContentType = ct - } - } - requestBody, err := newRequestBody(request, bodyProperties, reqContentType) - if err != nil { - return nil, err - } - req, err := http.NewRequestWithContext(ctx, method, url, requestBody) - if err != nil { - return nil, err - } - req.Header.Set(contentTypeHeader, reqContentType) - for name, values := range endpointHeaders { - req.Header[name] = values - } - return req, nil -} - -// newRequestBody returns a new io.Reader that represents the HTTP request body. -func newRequestBody(request interface{}, bodyProperties map[string]interface{}, reqContentType string) (io.Reader, error) { - if isNil(request) { - if len(bodyProperties) == 0 { - return nil, nil - } - if reqContentType == contentTypeFormURLEncoded { - return newFormURLEncodedBody(bodyProperties), nil - } - requestBytes, err := json.Marshal(bodyProperties) - if err != nil { - return nil, err - } - return bytes.NewReader(requestBytes), nil - } - if body, ok := request.(io.Reader); ok { - return body, nil - } - // Handle form URL encoded content type. - if reqContentType == contentTypeFormURLEncoded { - return newFormURLEncodedRequestBody(request, bodyProperties) - } - requestBytes, err := MarshalJSONWithExtraProperties(request, bodyProperties) - if err != nil { - return nil, err - } - return bytes.NewReader(requestBytes), nil -} - -// newFormURLEncodedBody returns a new io.Reader that represents a form URL encoded body -// from the given body properties map. -func newFormURLEncodedBody(bodyProperties map[string]interface{}) io.Reader { - values := url.Values{} - for key, val := range bodyProperties { - values.Set(key, fmt.Sprintf("%v", val)) - } - return strings.NewReader(values.Encode()) -} - -// newFormURLEncodedRequestBody returns a new io.Reader that represents a form URL encoded body -// from the given request struct and body properties. -func newFormURLEncodedRequestBody(request interface{}, bodyProperties map[string]interface{}) (io.Reader, error) { - values := url.Values{} - // Marshal the request to JSON first to respect any custom MarshalJSON methods, - // then unmarshal into a map to extract the field values. - jsonBytes, err := json.Marshal(request) - if err != nil { - return nil, err - } - var jsonMap map[string]interface{} - if err := json.Unmarshal(jsonBytes, &jsonMap); err != nil { - return nil, err - } - // Convert the JSON map to form URL encoded values. - for key, val := range jsonMap { - if val == nil { - continue - } - values.Set(key, fmt.Sprintf("%v", val)) - } - // Add any extra body properties. - for key, val := range bodyProperties { - values.Set(key, fmt.Sprintf("%v", val)) - } - return strings.NewReader(values.Encode()), nil -} - -// decodeError decodes the error from the given HTTP response. Note that -// it's the caller's responsibility to close the response body. -func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { - if errorDecoder != nil { - // This endpoint has custom errors, so we'll - // attempt to unmarshal the error into a structured - // type based on the status code. - return errorDecoder(response.StatusCode, response.Header, response.Body) - } - // This endpoint doesn't have any custom error - // types, so we just read the body as-is, and - // put it into a normal error. - bytes, err := io.ReadAll(response.Body) - if err != nil && err != io.EOF { - return err - } - if err == io.EOF { - // The error didn't have a response body, - // so all we can do is return an error - // with the status code. - return core.NewAPIError(response.StatusCode, response.Header, nil) - } - return core.NewAPIError(response.StatusCode, response.Header, errors.New(string(bytes))) -} - -// isNil is used to determine if the request value is equal to nil (i.e. an interface -// value that holds a nil concrete value is itself non-nil). -func isNil(value interface{}) bool { - if value == nil { - return true - } - v := reflect.ValueOf(value) - switch v.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: - return v.IsNil() - default: - return false - } -} diff --git a/seed/go-sdk/allof-inline/internal/caller_test.go b/seed/go-sdk/allof-inline/internal/caller_test.go deleted file mode 100644 index efc29b145798..000000000000 --- a/seed/go-sdk/allof-inline/internal/caller_test.go +++ /dev/null @@ -1,705 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "strings" - "testing" - - "github.com/allof-inline/fern/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// InternalTestCase represents a single test case. -type InternalTestCase struct { - description string - - // Server-side assertions. - givePathSuffix string - giveMethod string - giveResponseIsOptional bool - giveHeader http.Header - giveErrorDecoder ErrorDecoder - giveRequest *InternalTestRequest - giveQueryParams url.Values - giveBodyProperties map[string]interface{} - - // Client-side assertions. - wantResponse *InternalTestResponse - wantError error -} - -// InternalTestRequest a simple request body. -type InternalTestRequest struct { - Id string `json:"id"` -} - -// InternalTestResponse a simple response body. -type InternalTestResponse struct { - Id string `json:"id"` - ExtraBodyProperties map[string]interface{} `json:"extraBodyProperties,omitempty"` - QueryParameters url.Values `json:"queryParameters,omitempty"` -} - -// InternalTestNotFoundError represents a 404. -type InternalTestNotFoundError struct { - *core.APIError - - Message string `json:"message"` -} - -func TestCall(t *testing.T) { - tests := []*InternalTestCase{ - { - description: "GET success", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - wantResponse: &InternalTestResponse{ - Id: "123", - }, - }, - { - description: "GET success with query", - givePathSuffix: "?limit=1", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - wantResponse: &InternalTestResponse{ - Id: "123", - QueryParameters: url.Values{ - "limit": []string{"1"}, - }, - }, - }, - { - description: "GET not found", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"fail"}, - }, - giveRequest: &InternalTestRequest{ - Id: strconv.Itoa(http.StatusNotFound), - }, - giveErrorDecoder: newTestErrorDecoder(t), - wantError: &InternalTestNotFoundError{ - APIError: core.NewAPIError( - http.StatusNotFound, - http.Header{}, - errors.New(`{"message":"ID \"404\" not found"}`), - ), - }, - }, - { - description: "POST empty body", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"fail"}, - }, - giveRequest: nil, - wantError: core.NewAPIError( - http.StatusBadRequest, - http.Header{}, - errors.New("invalid request"), - ), - }, - { - description: "POST optional response", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - giveResponseIsOptional: true, - }, - { - description: "POST API error", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"fail"}, - }, - giveRequest: &InternalTestRequest{ - Id: strconv.Itoa(http.StatusInternalServerError), - }, - wantError: core.NewAPIError( - http.StatusInternalServerError, - http.Header{}, - errors.New("failed to process request"), - ), - }, - { - description: "POST extra properties", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: new(InternalTestRequest), - giveBodyProperties: map[string]interface{}{ - "key": "value", - }, - wantResponse: &InternalTestResponse{ - ExtraBodyProperties: map[string]interface{}{ - "key": "value", - }, - }, - }, - { - description: "GET extra query parameters", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveQueryParams: url.Values{ - "extra": []string{"true"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - wantResponse: &InternalTestResponse{ - Id: "123", - QueryParameters: url.Values{ - "extra": []string{"true"}, - }, - }, - }, - { - description: "GET merge extra query parameters", - givePathSuffix: "?limit=1", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - giveQueryParams: url.Values{ - "extra": []string{"true"}, - }, - wantResponse: &InternalTestResponse{ - Id: "123", - QueryParameters: url.Values{ - "limit": []string{"1"}, - "extra": []string{"true"}, - }, - }, - }, - } - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - var ( - server = newTestServer(t, test) - client = server.Client() - ) - caller := NewCaller( - &CallerParams{ - Client: client, - }, - ) - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL + test.givePathSuffix, - Method: test.giveMethod, - Headers: test.giveHeader, - BodyProperties: test.giveBodyProperties, - QueryParameters: test.giveQueryParams, - Request: test.giveRequest, - Response: &response, - ResponseIsOptional: test.giveResponseIsOptional, - ErrorDecoder: test.giveErrorDecoder, - }, - ) - if test.wantError != nil { - assert.EqualError(t, err, test.wantError.Error()) - return - } - require.NoError(t, err) - assert.Equal(t, test.wantResponse, response) - }) - } -} - -func TestMergeHeaders(t *testing.T) { - t.Run("both empty", func(t *testing.T) { - merged := MergeHeaders(make(http.Header), make(http.Header)) - assert.Empty(t, merged) - }) - - t.Run("empty left", func(t *testing.T) { - left := make(http.Header) - - right := make(http.Header) - right.Set("X-API-Version", "0.0.1") - - merged := MergeHeaders(left, right) - assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) - }) - - t.Run("empty right", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Version", "0.0.1") - - right := make(http.Header) - - merged := MergeHeaders(left, right) - assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) - }) - - t.Run("single value override", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Version", "0.0.0") - - right := make(http.Header) - right.Set("X-API-Version", "0.0.1") - - merged := MergeHeaders(left, right) - assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) - }) - - t.Run("multiple value override", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Versions", "0.0.0") - - right := make(http.Header) - right.Add("X-API-Versions", "0.0.1") - right.Add("X-API-Versions", "0.0.2") - - merged := MergeHeaders(left, right) - assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) - }) - - t.Run("disjoint merge", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Tenancy", "test") - - right := make(http.Header) - right.Set("X-API-Version", "0.0.1") - - merged := MergeHeaders(left, right) - assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) - assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) - }) -} - -// newTestServer returns a new *httptest.Server configured with the -// given test parameters. -func newTestServer(t *testing.T, tc *InternalTestCase) *httptest.Server { - return httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, tc.giveMethod, r.Method) - assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) - for header, value := range tc.giveHeader { - assert.Equal(t, value, r.Header.Values(header)) - } - - request := new(InternalTestRequest) - - bytes, err := io.ReadAll(r.Body) - if tc.giveRequest == nil { - require.Empty(t, bytes) - w.WriteHeader(http.StatusBadRequest) - _, err = w.Write([]byte("invalid request")) - require.NoError(t, err) - return - } - require.NoError(t, err) - require.NoError(t, json.Unmarshal(bytes, request)) - - switch request.Id { - case strconv.Itoa(http.StatusNotFound): - notFoundError := &InternalTestNotFoundError{ - APIError: &core.APIError{ - StatusCode: http.StatusNotFound, - }, - Message: fmt.Sprintf("ID %q not found", request.Id), - } - bytes, err = json.Marshal(notFoundError) - require.NoError(t, err) - - w.WriteHeader(http.StatusNotFound) - _, err = w.Write(bytes) - require.NoError(t, err) - return - - case strconv.Itoa(http.StatusInternalServerError): - w.WriteHeader(http.StatusInternalServerError) - _, err = w.Write([]byte("failed to process request")) - require.NoError(t, err) - return - } - - if tc.giveResponseIsOptional { - w.WriteHeader(http.StatusOK) - return - } - - extraBodyProperties := make(map[string]interface{}) - require.NoError(t, json.Unmarshal(bytes, &extraBodyProperties)) - delete(extraBodyProperties, "id") - - response := &InternalTestResponse{ - Id: request.Id, - ExtraBodyProperties: extraBodyProperties, - QueryParameters: r.URL.Query(), - } - bytes, err = json.Marshal(response) - require.NoError(t, err) - - _, err = w.Write(bytes) - require.NoError(t, err) - }, - ), - ) -} - -func TestIsNil(t *testing.T) { - t.Run("nil interface", func(t *testing.T) { - assert.True(t, isNil(nil)) - }) - - t.Run("nil pointer", func(t *testing.T) { - var ptr *string - assert.True(t, isNil(ptr)) - }) - - t.Run("non-nil pointer", func(t *testing.T) { - s := "test" - assert.False(t, isNil(&s)) - }) - - t.Run("nil slice", func(t *testing.T) { - var slice []string - assert.True(t, isNil(slice)) - }) - - t.Run("non-nil slice", func(t *testing.T) { - slice := []string{} - assert.False(t, isNil(slice)) - }) - - t.Run("nil map", func(t *testing.T) { - var m map[string]string - assert.True(t, isNil(m)) - }) - - t.Run("non-nil map", func(t *testing.T) { - m := make(map[string]string) - assert.False(t, isNil(m)) - }) - - t.Run("string value", func(t *testing.T) { - assert.False(t, isNil("test")) - }) - - t.Run("empty string value", func(t *testing.T) { - assert.False(t, isNil("")) - }) - - t.Run("int value", func(t *testing.T) { - assert.False(t, isNil(42)) - }) - - t.Run("zero int value", func(t *testing.T) { - assert.False(t, isNil(0)) - }) - - t.Run("bool value", func(t *testing.T) { - assert.False(t, isNil(true)) - }) - - t.Run("false bool value", func(t *testing.T) { - assert.False(t, isNil(false)) - }) - - t.Run("struct value", func(t *testing.T) { - type testStruct struct { - Field string - } - assert.False(t, isNil(testStruct{Field: "test"})) - }) - - t.Run("empty struct value", func(t *testing.T) { - type testStruct struct { - Field string - } - assert.False(t, isNil(testStruct{})) - }) -} - -// newTestErrorDecoder returns an error decoder suitable for tests. -func newTestErrorDecoder(t *testing.T) func(int, http.Header, io.Reader) error { - return func(statusCode int, header http.Header, body io.Reader) error { - raw, err := io.ReadAll(body) - require.NoError(t, err) - - var ( - apiError = core.NewAPIError(statusCode, header, errors.New(string(raw))) - decoder = json.NewDecoder(bytes.NewReader(raw)) - ) - if statusCode == http.StatusNotFound { - value := new(InternalTestNotFoundError) - value.APIError = apiError - require.NoError(t, decoder.Decode(value)) - - return value - } - return apiError - } -} - -// FormURLEncodedTestRequest is a test struct for form URL encoding tests. -type FormURLEncodedTestRequest struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - GrantType string `json:"grant_type,omitempty"` - Scope *string `json:"scope,omitempty"` - NilPointer *string `json:"nil_pointer,omitempty"` -} - -func TestNewFormURLEncodedBody(t *testing.T) { - t.Run("simple key-value pairs", func(t *testing.T) { - bodyProperties := map[string]interface{}{ - "client_id": "test_client_id", - "client_secret": "test_client_secret", - "grant_type": "client_credentials", - } - reader := newFormURLEncodedBody(bodyProperties) - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Parse the body and verify values - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "client_credentials", values.Get("grant_type")) - - // Verify it's not JSON - bodyStr := string(body) - assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should not be JSON, got: %s", bodyStr) - }) - - t.Run("special characters requiring URL encoding", func(t *testing.T) { - bodyProperties := map[string]interface{}{ - "value_with_space": "hello world", - "value_with_ampersand": "a&b", - "value_with_equals": "a=b", - "value_with_plus": "a+b", - } - reader := newFormURLEncodedBody(bodyProperties) - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Parse the body and verify values are correctly decoded - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "hello world", values.Get("value_with_space")) - assert.Equal(t, "a&b", values.Get("value_with_ampersand")) - assert.Equal(t, "a=b", values.Get("value_with_equals")) - assert.Equal(t, "a+b", values.Get("value_with_plus")) - }) - - t.Run("empty map", func(t *testing.T) { - bodyProperties := map[string]interface{}{} - reader := newFormURLEncodedBody(bodyProperties) - body, err := io.ReadAll(reader) - require.NoError(t, err) - assert.Empty(t, string(body)) - }) -} - -func TestNewFormURLEncodedRequestBody(t *testing.T) { - t.Run("struct with json tags", func(t *testing.T) { - scope := "read write" - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - GrantType: "client_credentials", - Scope: &scope, - NilPointer: nil, - } - reader, err := newFormURLEncodedRequestBody(request, nil) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Parse the body and verify values - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "client_credentials", values.Get("grant_type")) - assert.Equal(t, "read write", values.Get("scope")) - // nil_pointer should not be present (nil pointer with omitempty) - assert.Empty(t, values.Get("nil_pointer")) - - // Verify it's not JSON - bodyStr := string(body) - assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should not be JSON, got: %s", bodyStr) - }) - - t.Run("struct with omitempty and zero values", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - GrantType: "", // empty string with omitempty should be omitted - Scope: nil, - NilPointer: nil, - } - reader, err := newFormURLEncodedRequestBody(request, nil) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - // grant_type should not be present (empty string with omitempty) - assert.Empty(t, values.Get("grant_type")) - assert.Empty(t, values.Get("scope")) - }) - - t.Run("struct with extra body properties", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - } - bodyProperties := map[string]interface{}{ - "extra_param": "extra_value", - } - reader, err := newFormURLEncodedRequestBody(request, bodyProperties) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "extra_value", values.Get("extra_param")) - }) - - t.Run("special characters in struct fields", func(t *testing.T) { - scope := "read&write=all+permissions" - request := &FormURLEncodedTestRequest{ - ClientID: "client with spaces", - ClientSecret: "secret&with=special+chars", - Scope: &scope, - } - reader, err := newFormURLEncodedRequestBody(request, nil) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "client with spaces", values.Get("client_id")) - assert.Equal(t, "secret&with=special+chars", values.Get("client_secret")) - assert.Equal(t, "read&write=all+permissions", values.Get("scope")) - }) -} - -func TestNewRequestBodyFormURLEncoded(t *testing.T) { - t.Run("selects form encoding when content-type is form-urlencoded", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - GrantType: "client_credentials", - } - reader, err := newRequestBody(request, nil, contentTypeFormURLEncoded) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Verify it's form-urlencoded, not JSON - bodyStr := string(body) - assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should not be JSON when Content-Type is form-urlencoded, got: %s", bodyStr) - - // Parse and verify values - values, err := url.ParseQuery(bodyStr) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "client_credentials", values.Get("grant_type")) - }) - - t.Run("selects JSON encoding when content-type is application/json", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - } - reader, err := newRequestBody(request, nil, contentType) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Verify it's JSON - bodyStr := string(body) - assert.True(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should be JSON when Content-Type is application/json, got: %s", bodyStr) - - // Parse and verify it's valid JSON - var parsed map[string]interface{} - err = json.Unmarshal(body, &parsed) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", parsed["client_id"]) - assert.Equal(t, "test_client_secret", parsed["client_secret"]) - }) - - t.Run("form encoding with body properties only (nil request)", func(t *testing.T) { - bodyProperties := map[string]interface{}{ - "client_id": "test_client_id", - "client_secret": "test_client_secret", - } - reader, err := newRequestBody(nil, bodyProperties, contentTypeFormURLEncoded) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - }) -} diff --git a/seed/go-sdk/allof-inline/internal/error_decoder.go b/seed/go-sdk/allof-inline/internal/error_decoder.go deleted file mode 100644 index a0358e5dd9ba..000000000000 --- a/seed/go-sdk/allof-inline/internal/error_decoder.go +++ /dev/null @@ -1,64 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - - "github.com/allof-inline/fern/core" -) - -// ErrorCodes maps HTTP status codes to error constructors. -type ErrorCodes map[int]func(*core.APIError) error - -// ErrorDecoder decodes *http.Response errors and returns a -// typed API error (e.g. *core.APIError). -type ErrorDecoder func(statusCode int, header http.Header, body io.Reader) error - -// NewErrorDecoder returns a new ErrorDecoder backed by the given error codes. -// errorCodesOverrides is optional and will be merged with the default error codes, -// with overrides taking precedence. -func NewErrorDecoder(errorCodes ErrorCodes, errorCodesOverrides ...ErrorCodes) ErrorDecoder { - // Merge default error codes with overrides - mergedErrorCodes := make(ErrorCodes) - - // Start with default error codes - for statusCode, errorFunc := range errorCodes { - mergedErrorCodes[statusCode] = errorFunc - } - - // Apply overrides if provided - if len(errorCodesOverrides) > 0 && errorCodesOverrides[0] != nil { - for statusCode, errorFunc := range errorCodesOverrides[0] { - mergedErrorCodes[statusCode] = errorFunc - } - } - - return func(statusCode int, header http.Header, body io.Reader) error { - raw, err := io.ReadAll(body) - if err != nil { - return fmt.Errorf("failed to read error from response body: %w", err) - } - apiError := core.NewAPIError( - statusCode, - header, - errors.New(string(raw)), - ) - newErrorFunc, ok := mergedErrorCodes[statusCode] - if !ok { - // This status code isn't recognized, so we return - // the API error as-is. - return apiError - } - customError := newErrorFunc(apiError) - if err := json.NewDecoder(bytes.NewReader(raw)).Decode(customError); err != nil { - // If we fail to decode the error, we return the - // API error as-is. - return apiError - } - return customError - } -} diff --git a/seed/go-sdk/allof-inline/internal/error_decoder_test.go b/seed/go-sdk/allof-inline/internal/error_decoder_test.go deleted file mode 100644 index 88cbaeb9cd17..000000000000 --- a/seed/go-sdk/allof-inline/internal/error_decoder_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package internal - -import ( - "bytes" - "errors" - "net/http" - "testing" - - "github.com/allof-inline/fern/core" - "github.com/stretchr/testify/assert" -) - -func TestErrorDecoder(t *testing.T) { - decoder := NewErrorDecoder( - ErrorCodes{ - http.StatusNotFound: func(apiError *core.APIError) error { - return &InternalTestNotFoundError{APIError: apiError} - }, - }) - - tests := []struct { - description string - giveStatusCode int - giveHeader http.Header - giveBody string - wantError error - }{ - { - description: "unrecognized status code", - giveStatusCode: http.StatusInternalServerError, - giveHeader: http.Header{}, - giveBody: "Internal Server Error", - wantError: core.NewAPIError(http.StatusInternalServerError, http.Header{}, errors.New("Internal Server Error")), - }, - { - description: "not found with valid JSON", - giveStatusCode: http.StatusNotFound, - giveHeader: http.Header{}, - giveBody: `{"message": "Resource not found"}`, - wantError: &InternalTestNotFoundError{ - APIError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New(`{"message": "Resource not found"}`)), - Message: "Resource not found", - }, - }, - { - description: "not found with invalid JSON", - giveStatusCode: http.StatusNotFound, - giveHeader: http.Header{}, - giveBody: `Resource not found`, - wantError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New("Resource not found")), - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - assert.Equal(t, tt.wantError, decoder(tt.giveStatusCode, tt.giveHeader, bytes.NewReader([]byte(tt.giveBody)))) - }) - } -} diff --git a/seed/go-sdk/allof-inline/internal/explicit_fields.go b/seed/go-sdk/allof-inline/internal/explicit_fields.go deleted file mode 100644 index 4bdf34fc2b7c..000000000000 --- a/seed/go-sdk/allof-inline/internal/explicit_fields.go +++ /dev/null @@ -1,116 +0,0 @@ -package internal - -import ( - "math/big" - "reflect" - "strings" -) - -// HandleExplicitFields processes a struct to remove `omitempty` from -// fields that have been explicitly set (as indicated by their corresponding bit in explicitFields). -// Note that `marshaler` should be an embedded struct to avoid infinite recursion. -// Returns an interface{} that can be passed to json.Marshal. -func HandleExplicitFields(marshaler interface{}, explicitFields *big.Int) interface{} { - val := reflect.ValueOf(marshaler) - typ := reflect.TypeOf(marshaler) - - // Handle pointer types - if val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil - } - val = val.Elem() - typ = typ.Elem() - } - - // Only handle struct types - if val.Kind() != reflect.Struct { - return marshaler - } - - // Handle embedded struct pattern - var sourceVal reflect.Value - var sourceType reflect.Type - - // Check if this is an embedded struct pattern - if typ.NumField() == 1 && typ.Field(0).Anonymous { - // This is likely an embedded struct, get the embedded value - embeddedField := val.Field(0) - sourceVal = embeddedField - sourceType = embeddedField.Type() - } else { - // Regular struct - sourceVal = val - sourceType = typ - } - - // If no explicit fields set, use standard marshaling - if explicitFields == nil || explicitFields.Sign() == 0 { - return marshaler - } - - // Create a new struct type with modified tags - fields := make([]reflect.StructField, 0, sourceType.NumField()) - - for i := 0; i < sourceType.NumField(); i++ { - field := sourceType.Field(i) - - // Skip unexported fields and the explicitFields field itself - if !field.IsExported() || field.Name == "explicitFields" { - continue - } - - // Check if this field has been explicitly set - fieldBit := big.NewInt(1) - fieldBit.Lsh(fieldBit, uint(i)) - if big.NewInt(0).And(explicitFields, fieldBit).Sign() != 0 { - // Remove omitempty from the json tag - tag := field.Tag.Get("json") - if tag != "" && tag != "-" { - // Parse the json tag, remove omitempty from options - parts := strings.Split(tag, ",") - if len(parts) > 1 { - var newParts []string - newParts = append(newParts, parts[0]) // Keep the field name - for _, part := range parts[1:] { - if strings.TrimSpace(part) != "omitempty" { - newParts = append(newParts, part) - } - } - tag = strings.Join(newParts, ",") - } - - // Reconstruct the struct tag - newTag := `json:"` + tag + `"` - if urlTag := field.Tag.Get("url"); urlTag != "" { - newTag += ` url:"` + urlTag + `"` - } - - field.Tag = reflect.StructTag(newTag) - } - } - - fields = append(fields, field) - } - - // Create new struct type with modified tags - newType := reflect.StructOf(fields) - newVal := reflect.New(newType).Elem() - - // Copy field values from original struct to new struct - fieldIndex := 0 - for i := 0; i < sourceType.NumField(); i++ { - originalField := sourceType.Field(i) - - // Skip unexported fields and the explicitFields field itself - if !originalField.IsExported() || originalField.Name == "explicitFields" { - continue - } - - originalValue := sourceVal.Field(i) - newVal.Field(fieldIndex).Set(originalValue) - fieldIndex++ - } - - return newVal.Interface() -} diff --git a/seed/go-sdk/allof-inline/internal/explicit_fields_test.go b/seed/go-sdk/allof-inline/internal/explicit_fields_test.go deleted file mode 100644 index f44beec447d6..000000000000 --- a/seed/go-sdk/allof-inline/internal/explicit_fields_test.go +++ /dev/null @@ -1,645 +0,0 @@ -package internal - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testExplicitFieldsStruct struct { - Name *string `json:"name,omitempty"` - Code *string `json:"code,omitempty"` - Count *int `json:"count,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - Tags []string `json:"tags,omitempty"` - unexported string `json:"-"` //nolint:unused - explicitFields *big.Int `json:"-"` -} - -var ( - testFieldName = big.NewInt(1 << 0) - testFieldCode = big.NewInt(1 << 1) - testFieldCount = big.NewInt(1 << 2) - testFieldEnabled = big.NewInt(1 << 3) - testFieldTags = big.NewInt(1 << 4) -) - -func (t *testExplicitFieldsStruct) require(field *big.Int) { - if t.explicitFields == nil { - t.explicitFields = big.NewInt(0) - } - t.explicitFields.Or(t.explicitFields, field) -} - -func (t *testExplicitFieldsStruct) SetName(name *string) { - t.Name = name - t.require(testFieldName) -} - -func (t *testExplicitFieldsStruct) SetCode(code *string) { - t.Code = code - t.require(testFieldCode) -} - -func (t *testExplicitFieldsStruct) SetCount(count *int) { - t.Count = count - t.require(testFieldCount) -} - -func (t *testExplicitFieldsStruct) SetEnabled(enabled *bool) { - t.Enabled = enabled - t.require(testFieldEnabled) -} - -func (t *testExplicitFieldsStruct) SetTags(tags []string) { - t.Tags = tags - t.require(testFieldTags) -} - -func (t *testExplicitFieldsStruct) MarshalJSON() ([]byte, error) { - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*t), - } - return json.Marshal(HandleExplicitFields(marshaler, t.explicitFields)) -} - -type testStructWithoutExplicitFields struct { - Name *string `json:"name,omitempty"` - Code *string `json:"code,omitempty"` -} - -func TestHandleExplicitFields(t *testing.T) { - tests := []struct { - desc string - giveInput interface{} - wantBytes []byte - wantError string - }{ - { - desc: "nil input", - giveInput: nil, - wantBytes: []byte(`null`), - }, - { - desc: "non-struct input", - giveInput: "string", - wantBytes: []byte(`"string"`), - }, - { - desc: "slice input", - giveInput: []string{"a", "b"}, - wantBytes: []byte(`["a","b"]`), - }, - { - desc: "map input", - giveInput: map[string]interface{}{"key": "value"}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "struct without explicitFields field", - giveInput: &testStructWithoutExplicitFields{ - Name: stringPtr("test"), - Code: nil, - }, - wantBytes: []byte(`{"name":"test"}`), - }, - { - desc: "struct with no explicit fields set", - giveInput: &testExplicitFieldsStruct{ - Name: stringPtr("test"), - Code: nil, - }, - wantBytes: []byte(`{"name":"test"}`), - }, - { - desc: "struct with explicit nil field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Name: stringPtr("test"), - } - s.SetCode(nil) - return s - }(), - wantBytes: []byte(`{"name":"test","code":null}`), - }, - { - desc: "struct with explicit non-nil field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{} - s.SetName(stringPtr("explicit")) - s.SetCode(stringPtr("also-explicit")) - return s - }(), - wantBytes: []byte(`{"name":"explicit","code":"also-explicit"}`), - }, - { - desc: "struct with mixed explicit and implicit fields", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Name: stringPtr("implicit"), - Count: intPtr(42), - } - s.SetCode(nil) // explicit nil - return s - }(), - wantBytes: []byte(`{"name":"implicit","code":null,"count":42}`), - }, - { - desc: "struct with multiple explicit nil fields", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Name: stringPtr("test"), - } - s.SetCode(nil) - s.SetCount(nil) - return s - }(), - wantBytes: []byte(`{"name":"test","code":null,"count":null}`), - }, - { - desc: "struct with slice field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Tags: []string{"tag1", "tag2"}, - } - s.SetTags(nil) // explicit nil slice - return s - }(), - wantBytes: []byte(`{"tags":null}`), - }, - { - desc: "struct with boolean field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{} - s.SetEnabled(boolPtr(false)) // explicit false - return s - }(), - wantBytes: []byte(`{"enabled":false}`), - }, - { - desc: "struct with all fields explicit", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{} - s.SetName(stringPtr("test")) - s.SetCode(nil) - s.SetCount(intPtr(0)) - s.SetEnabled(boolPtr(false)) - s.SetTags([]string{}) - return s - }(), - wantBytes: []byte(`{"name":"test","code":null,"count":0,"enabled":false,"tags":[]}`), - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - var explicitFields *big.Int - if s, ok := tt.giveInput.(*testExplicitFieldsStruct); ok { - explicitFields = s.explicitFields - } - bytes, err := json.Marshal(HandleExplicitFields(tt.giveInput, explicitFields)) - if tt.wantError != "" { - require.EqualError(t, err, tt.wantError) - assert.Nil(t, tt.wantBytes) - return - } - require.NoError(t, err) - assert.JSONEq(t, string(tt.wantBytes), string(bytes)) - - // Verify it's valid JSON - var value interface{} - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -func TestHandleExplicitFieldsCustomMarshaler(t *testing.T) { - t.Run("custom marshaler with explicit fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - s.SetName(nil) - s.SetCode(stringPtr("test-code")) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) - }) - - t.Run("custom marshaler with no explicit fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("implicit"), - Code: stringPtr("also-implicit"), - } - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) - }) -} - -func TestHandleExplicitFieldsPointerHandling(t *testing.T) { - t.Run("nil pointer", func(t *testing.T) { - var s *testExplicitFieldsStruct - bytes, err := json.Marshal(HandleExplicitFields(s, nil)) - require.NoError(t, err) - assert.Equal(t, []byte(`null`), bytes) - }) - - t.Run("pointer to struct", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - s.SetName(nil) - - bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) - require.NoError(t, err) - assert.JSONEq(t, `{"name":null}`, string(bytes)) - }) -} - -func TestHandleExplicitFieldsEmbeddedStruct(t *testing.T) { - t.Run("embedded struct with explicit fields", func(t *testing.T) { - // Create a struct similar to what MarshalJSON creates - s := &testExplicitFieldsStruct{} - s.SetName(nil) - s.SetCode(stringPtr("test-code")) - - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*s), - } - - bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) - require.NoError(t, err) - // Should include both explicit fields (name as null, code as "test-code") - assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) - }) - - t.Run("embedded struct with no explicit fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("implicit"), - Code: stringPtr("also-implicit"), - } - - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*s), - } - - bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) - require.NoError(t, err) - // Should only include non-nil fields (omitempty behavior) - assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) - }) - - t.Run("embedded struct with mixed fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Count: intPtr(42), // implicit field - } - s.SetName(nil) // explicit nil - s.SetCode(stringPtr("explicit")) // explicit value - - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*s), - } - - bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) - require.NoError(t, err) - // Should include explicit null, explicit value, and implicit value - assert.JSONEq(t, `{"name":null,"code":"explicit","count":42}`, string(bytes)) - }) -} - -func TestHandleExplicitFieldsTagHandling(t *testing.T) { - type testStructWithComplexTags struct { - Field1 *string `json:"field1,omitempty" url:"field1,omitempty"` - Field2 *string `json:"field2,omitempty,string" url:"field2"` - Field3 *string `json:"-"` - Field4 *string `json:"field4"` - explicitFields *big.Int `json:"-"` - } - - s := &testStructWithComplexTags{ - Field1: stringPtr("test1"), - Field4: stringPtr("test4"), - explicitFields: big.NewInt(1), // Only first field is explicit - } - - bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) - require.NoError(t, err) - - // Field1 should have omitempty removed, Field2 should keep omitempty, Field4 should be included - assert.JSONEq(t, `{"field1":"test1","field4":"test4"}`, string(bytes)) -} - -// Test types for nested struct explicit fields testing -type testNestedStruct struct { - NestedName *string `json:"nested_name,omitempty"` - NestedCode *string `json:"nested_code,omitempty"` - explicitFields *big.Int `json:"-"` -} - -type testParentStruct struct { - ParentName *string `json:"parent_name,omitempty"` - Nested *testNestedStruct `json:"nested,omitempty"` - explicitFields *big.Int `json:"-"` -} - -var ( - nestedFieldName = big.NewInt(1 << 0) - nestedFieldCode = big.NewInt(1 << 1) -) - -var ( - parentFieldName = big.NewInt(1 << 0) - parentFieldNested = big.NewInt(1 << 1) -) - -func (n *testNestedStruct) require(field *big.Int) { - if n.explicitFields == nil { - n.explicitFields = big.NewInt(0) - } - n.explicitFields.Or(n.explicitFields, field) -} - -func (n *testNestedStruct) SetNestedName(name *string) { - n.NestedName = name - n.require(nestedFieldName) -} - -func (n *testNestedStruct) SetNestedCode(code *string) { - n.NestedCode = code - n.require(nestedFieldCode) -} - -func (n *testNestedStruct) MarshalJSON() ([]byte, error) { - type embed testNestedStruct - var marshaler = struct { - embed - }{ - embed: embed(*n), - } - return json.Marshal(HandleExplicitFields(marshaler, n.explicitFields)) -} - -func (p *testParentStruct) require(field *big.Int) { - if p.explicitFields == nil { - p.explicitFields = big.NewInt(0) - } - p.explicitFields.Or(p.explicitFields, field) -} - -func (p *testParentStruct) SetParentName(name *string) { - p.ParentName = name - p.require(parentFieldName) -} - -func (p *testParentStruct) SetNested(nested *testNestedStruct) { - p.Nested = nested - p.require(parentFieldNested) -} - -func (p *testParentStruct) MarshalJSON() ([]byte, error) { - type embed testParentStruct - var marshaler = struct { - embed - }{ - embed: embed(*p), - } - return json.Marshal(HandleExplicitFields(marshaler, p.explicitFields)) -} - -func TestHandleExplicitFieldsNestedStruct(t *testing.T) { - tests := []struct { - desc string - setupFunc func() *testParentStruct - wantBytes []byte - }{ - { - desc: "nested struct with explicit nil in nested object", - setupFunc: func() *testParentStruct { - nested := &testNestedStruct{ - NestedName: stringPtr("implicit-nested"), - } - nested.SetNestedCode(nil) // explicit nil - - return &testParentStruct{ - ParentName: stringPtr("implicit-parent"), - Nested: nested, - } - }, - wantBytes: []byte(`{"parent_name":"implicit-parent","nested":{"nested_name":"implicit-nested","nested_code":null}}`), - }, - { - desc: "parent with explicit nil nested struct", - setupFunc: func() *testParentStruct { - parent := &testParentStruct{ - ParentName: stringPtr("implicit-parent"), - } - parent.SetNested(nil) // explicit nil nested struct - return parent - }, - wantBytes: []byte(`{"parent_name":"implicit-parent","nested":null}`), - }, - { - desc: "all explicit fields in nested structure", - setupFunc: func() *testParentStruct { - nested := &testNestedStruct{} - nested.SetNestedName(stringPtr("explicit-nested")) - nested.SetNestedCode(nil) // explicit nil - - parent := &testParentStruct{} - parent.SetParentName(nil) // explicit nil - parent.SetNested(nested) // explicit nested struct - - return parent - }, - wantBytes: []byte(`{"parent_name":null,"nested":{"nested_name":"explicit-nested","nested_code":null}}`), - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - parent := tt.setupFunc() - bytes, err := parent.MarshalJSON() - require.NoError(t, err) - assert.JSONEq(t, string(tt.wantBytes), string(bytes)) - - // Verify it's valid JSON - var value interface{} - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -// Test for setter method documentation and behavior -func TestSetterMethodsDocumentation(t *testing.T) { - t.Run("setter prevents omitempty for nil values", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Use setter to explicitly set nil - this should prevent omitempty - s.SetName(nil) - s.SetCode(nil) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Both fields should be included as null, not omitted - assert.JSONEq(t, `{"name":null,"code":null}`, string(bytes)) - }) - - t.Run("setter prevents omitempty for empty slice", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Use setter to explicitly set empty slice - s.SetTags([]string{}) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Empty slice should be included as [], not omitted - assert.JSONEq(t, `{"tags":[]}`, string(bytes)) - }) - - t.Run("setter prevents omitempty for zero values", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Use setter to explicitly set zero values - s.SetCount(intPtr(0)) - s.SetEnabled(boolPtr(false)) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Zero values should be included, not omitted - assert.JSONEq(t, `{"count":0,"enabled":false}`, string(bytes)) - }) - - t.Run("direct assignment is omitted when nil", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: nil, // Direct assignment, not using setter - Code: nil, // Direct assignment, not using setter - } - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Fields not set via setter should be omitted when nil - assert.JSONEq(t, `{}`, string(bytes)) - }) - - t.Run("mix of setter and direct assignment", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("direct"), // Direct assignment - Count: intPtr(42), // Direct assignment - } - s.SetCode(nil) // Setter with nil - s.SetEnabled(boolPtr(false)) // Setter with zero value - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Direct assignments included if non-nil, setter fields always included - assert.JSONEq(t, `{"name":"direct","code":null,"count":42,"enabled":false}`, string(bytes)) - }) -} - -// Test for complex scenarios with multiple setters -func TestComplexSetterScenarios(t *testing.T) { - t.Run("multiple setter calls on same field", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Call setter multiple times - last one should win - s.SetName(stringPtr("first")) - s.SetName(stringPtr("second")) - s.SetName(nil) // Final value is nil - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Should serialize the last set value (nil) - assert.JSONEq(t, `{"name":null}`, string(bytes)) - }) - - t.Run("setter after direct assignment", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("direct"), - } - - // Override with setter - s.SetName(nil) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Setter should mark field as explicit, so nil is serialized - assert.JSONEq(t, `{"name":null}`, string(bytes)) - }) - - t.Run("all fields set via setters", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - s.SetName(nil) - s.SetCode(stringPtr("")) // Empty string - s.SetCount(intPtr(0)) // Zero - s.SetEnabled(boolPtr(false)) // False - s.SetTags(nil) // Nil slice - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // All fields should be present even with nil/zero values - assert.JSONEq(t, `{"name":null,"code":"","count":0,"enabled":false,"tags":null}`, string(bytes)) - }) -} - -// Test for backwards compatibility -func TestBackwardsCompatibility(t *testing.T) { - t.Run("struct without setters behaves normally", func(t *testing.T) { - s := &testStructWithoutExplicitFields{ - Name: stringPtr("test"), - Code: nil, // This should be omitted - } - - bytes, err := json.Marshal(s) - require.NoError(t, err) - - // Without setters, omitempty works normally - assert.JSONEq(t, `{"name":"test"}`, string(bytes)) - }) - - t.Run("struct with explicit fields works with standard json.Marshal", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("test"), - } - s.SetCode(nil) - - // Using the custom MarshalJSON - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - assert.JSONEq(t, `{"name":"test","code":null}`, string(bytes)) - }) -} - -// Helper functions -func stringPtr(s string) *string { - return &s -} - -func intPtr(i int) *int { - return &i -} - -func boolPtr(b bool) *bool { - return &b -} diff --git a/seed/go-sdk/allof-inline/internal/extra_properties.go b/seed/go-sdk/allof-inline/internal/extra_properties.go deleted file mode 100644 index 540c3fd89eeb..000000000000 --- a/seed/go-sdk/allof-inline/internal/extra_properties.go +++ /dev/null @@ -1,141 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "strings" -) - -// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. -func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { - return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) -} - -// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. -func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { - bytes, err := json.Marshal(marshaler) - if err != nil { - return nil, err - } - if len(extraProperties) == 0 { - return bytes, nil - } - keys, err := getKeys(marshaler) - if err != nil { - return nil, err - } - for _, key := range keys { - if _, ok := extraProperties[key]; ok { - return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) - } - } - extraBytes, err := json.Marshal(extraProperties) - if err != nil { - return nil, err - } - if isEmptyJSON(bytes) { - if isEmptyJSON(extraBytes) { - return bytes, nil - } - return extraBytes, nil - } - result := bytes[:len(bytes)-1] - result = append(result, ',') - result = append(result, extraBytes[1:len(extraBytes)-1]...) - result = append(result, '}') - return result, nil -} - -// ExtractExtraProperties extracts any extra properties from the given value. -func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { - val := reflect.ValueOf(value) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil, fmt.Errorf("value must be non-nil to extract extra properties") - } - val = val.Elem() - } - if err := json.Unmarshal(bytes, &value); err != nil { - return nil, err - } - var extraProperties map[string]interface{} - if err := json.Unmarshal(bytes, &extraProperties); err != nil { - return nil, err - } - for i := 0; i < val.Type().NumField(); i++ { - key := jsonKey(val.Type().Field(i)) - if key == "" || key == "-" { - continue - } - delete(extraProperties, key) - } - for _, key := range exclude { - delete(extraProperties, key) - } - if len(extraProperties) == 0 { - return nil, nil - } - return extraProperties, nil -} - -// getKeys returns the keys associated with the given value. The value must be a -// a struct or a map with string keys. -func getKeys(value interface{}) ([]string, error) { - val := reflect.ValueOf(value) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - if !val.IsValid() { - return nil, nil - } - switch val.Kind() { - case reflect.Struct: - return getKeysForStructType(val.Type()), nil - case reflect.Map: - var keys []string - if val.Type().Key().Kind() != reflect.String { - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } - for _, key := range val.MapKeys() { - keys = append(keys, key.String()) - } - return keys, nil - default: - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } -} - -// getKeysForStructType returns all the keys associated with the given struct type, -// visiting embedded fields recursively. -func getKeysForStructType(structType reflect.Type) []string { - if structType.Kind() == reflect.Pointer { - structType = structType.Elem() - } - if structType.Kind() != reflect.Struct { - return nil - } - var keys []string - for i := 0; i < structType.NumField(); i++ { - field := structType.Field(i) - if field.Anonymous { - keys = append(keys, getKeysForStructType(field.Type)...) - continue - } - keys = append(keys, jsonKey(field)) - } - return keys -} - -// jsonKey returns the JSON key from the struct tag of the given field, -// excluding the omitempty flag (if any). -func jsonKey(field reflect.StructField) string { - return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") -} - -// isEmptyJSON returns true if the given data is empty, the empty JSON object, or -// an explicit null. -func isEmptyJSON(data []byte) bool { - return len(data) <= 2 || bytes.Equal(data, []byte("null")) -} diff --git a/seed/go-sdk/allof-inline/internal/extra_properties_test.go b/seed/go-sdk/allof-inline/internal/extra_properties_test.go deleted file mode 100644 index aa2510ee5121..000000000000 --- a/seed/go-sdk/allof-inline/internal/extra_properties_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package internal - -import ( - "encoding/json" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testMarshaler struct { - Name string `json:"name"` - BirthDate time.Time `json:"birthDate"` - CreatedAt time.Time `json:"created_at"` -} - -func (t *testMarshaler) MarshalJSON() ([]byte, error) { - type embed testMarshaler - var marshaler = struct { - embed - BirthDate string `json:"birthDate"` - CreatedAt string `json:"created_at"` - }{ - embed: embed(*t), - BirthDate: t.BirthDate.Format("2006-01-02"), - CreatedAt: t.CreatedAt.Format(time.RFC3339), - } - return MarshalJSONWithExtraProperty(marshaler, "type", "test") -} - -func TestMarshalJSONWithExtraProperties(t *testing.T) { - tests := []struct { - desc string - giveMarshaler interface{} - giveExtraProperties map[string]interface{} - wantBytes []byte - wantError string - }{ - { - desc: "invalid type", - giveMarshaler: []string{"invalid"}, - giveExtraProperties: map[string]interface{}{"key": "overwrite"}, - wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, - }, - { - desc: "invalid key type", - giveMarshaler: map[int]interface{}{42: "value"}, - giveExtraProperties: map[string]interface{}{"key": "overwrite"}, - wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, - }, - { - desc: "invalid map overwrite", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{"key": "overwrite"}, - wantError: `cannot add extra property "key" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, - wantError: `cannot add extra property "birthDate" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite embedded type", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]interface{}{"name": "bob"}, - wantError: `cannot add extra property "name" because it is already defined on the type`, - }, - { - desc: "nil", - giveMarshaler: nil, - giveExtraProperties: nil, - wantBytes: []byte(`null`), - }, - { - desc: "empty", - giveMarshaler: map[string]interface{}{}, - giveExtraProperties: map[string]interface{}{}, - wantBytes: []byte(`{}`), - }, - { - desc: "no extra properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "only extra properties", - giveMarshaler: map[string]interface{}{}, - giveExtraProperties: map[string]interface{}{"key": "value"}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "single extra property", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{"extra": "property"}, - wantBytes: []byte(`{"key":"value","extra":"property"}`), - }, - { - desc: "multiple extra properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, - wantBytes: []byte(`{"key":"value","one":1,"two":2}`), - }, - { - desc: "nested properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{ - "user": map[string]interface{}{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), - }, - { - desc: "multiple nested properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{ - "metadata": map[string]interface{}{ - "ip": "127.0.0.1", - }, - "user": map[string]interface{}{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), - }, - { - desc: "custom marshaler", - giveMarshaler: &testMarshaler{ - Name: "alice", - BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - }, - giveExtraProperties: map[string]interface{}{ - "extra": "property", - }, - wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) - if tt.wantError != "" { - require.EqualError(t, err, tt.wantError) - assert.Nil(t, tt.wantBytes) - return - } - require.NoError(t, err) - assert.Equal(t, tt.wantBytes, bytes) - - value := make(map[string]interface{}) - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -func TestExtractExtraProperties(t *testing.T) { - t.Run("none", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) - - t.Run("non-nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) - }) - - t.Run("nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value *user - _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - assert.EqualError(t, err, "value must be non-nil to extract extra properties") - }) - - t.Run("non-zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) - }) - - t.Run("zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value user - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) - }) - - t.Run("exclude", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) -} diff --git a/seed/go-sdk/allof-inline/internal/http.go b/seed/go-sdk/allof-inline/internal/http.go deleted file mode 100644 index 77863752bb58..000000000000 --- a/seed/go-sdk/allof-inline/internal/http.go +++ /dev/null @@ -1,71 +0,0 @@ -package internal - -import ( - "fmt" - "net/http" - "net/url" - "reflect" -) - -// HTTPClient is an interface for a subset of the *http.Client. -type HTTPClient interface { - Do(*http.Request) (*http.Response, error) -} - -// ResolveBaseURL resolves the base URL from the given arguments, -// preferring the first non-empty value. -func ResolveBaseURL(values ...string) string { - for _, value := range values { - if value != "" { - return value - } - } - return "" -} - -// EncodeURL encodes the given arguments into the URL, escaping -// values as needed. Pointer arguments are dereferenced before processing. -func EncodeURL(urlFormat string, args ...interface{}) string { - escapedArgs := make([]interface{}, 0, len(args)) - for _, arg := range args { - // Dereference the argument if it's a pointer - value := dereferenceArg(arg) - escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", value))) - } - return fmt.Sprintf(urlFormat, escapedArgs...) -} - -// dereferenceArg dereferences a pointer argument if necessary, returning the underlying value. -// If the argument is not a pointer or is nil, it returns the argument as-is. -func dereferenceArg(arg interface{}) interface{} { - if arg == nil { - return arg - } - - v := reflect.ValueOf(arg) - - // Keep dereferencing until we get to a non-pointer value or hit nil - for v.Kind() == reflect.Ptr { - if v.IsNil() { - return nil - } - v = v.Elem() - } - - return v.Interface() -} - -// MergeHeaders merges the given headers together, where the right -// takes precedence over the left. -func MergeHeaders(left, right http.Header) http.Header { - for key, values := range right { - if len(values) > 1 { - left[key] = values - continue - } - if value := right.Get(key); value != "" { - left.Set(key, value) - } - } - return left -} diff --git a/seed/go-sdk/allof-inline/internal/query.go b/seed/go-sdk/allof-inline/internal/query.go deleted file mode 100644 index 9b567f7a5563..000000000000 --- a/seed/go-sdk/allof-inline/internal/query.go +++ /dev/null @@ -1,358 +0,0 @@ -package internal - -import ( - "encoding/base64" - "fmt" - "net/url" - "reflect" - "strings" - "time" - - "github.com/google/uuid" -) - -// RFC3339Milli is a time format string for RFC 3339 with millisecond precision. -// Go's time.RFC3339 omits fractional seconds and time.RFC3339Nano trims trailing -// zeros, so neither produces the fixed ".000" millisecond suffix that many APIs expect. -const RFC3339Milli = "2006-01-02T15:04:05.000Z07:00" - -var ( - bytesType = reflect.TypeOf([]byte{}) - queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() - timeType = reflect.TypeOf(time.Time{}) - uuidType = reflect.TypeOf(uuid.UUID{}) -) - -// QueryEncoder is an interface implemented by any type that wishes to encode -// itself into URL values in a non-standard way. -type QueryEncoder interface { - EncodeQueryValues(key string, v *url.Values) error -} - -// prepareValue handles common validation and unwrapping logic for both functions -func prepareValue(v interface{}) (reflect.Value, url.Values, error) { - values := make(url.Values) - val := reflect.ValueOf(v) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return reflect.Value{}, values, nil - } - val = val.Elem() - } - - if v == nil { - return reflect.Value{}, values, nil - } - - if val.Kind() != reflect.Struct { - return reflect.Value{}, nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) - } - - err := reflectValue(values, val, "") - if err != nil { - return reflect.Value{}, nil, err - } - - return val, values, nil -} - -// QueryValues encodes url.Values from request objects. -// -// Note: This type is inspired by Google's query encoding library, but -// supports far less customization and is tailored to fit this SDK's use case. -// -// Ref: https://github.com/google/go-querystring -func QueryValues(v interface{}) (url.Values, error) { - _, values, err := prepareValue(v) - return values, err -} - -// QueryValuesWithDefaults encodes url.Values from request objects -// and default values, merging the defaults into the request. -// It's expected that the values of defaults are wire names. -func QueryValuesWithDefaults(v interface{}, defaults map[string]interface{}) (url.Values, error) { - val, values, err := prepareValue(v) - if err != nil { - return values, err - } - if !val.IsValid() { - return values, nil - } - - // apply defaults to zero-value fields directly on the original struct - valType := val.Type() - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - fieldType := valType.Field(i) - fieldName := fieldType.Name - - if fieldType.PkgPath != "" && !fieldType.Anonymous { - // Skip unexported fields. - continue - } - - // check if field is zero value and we have a default for it - if field.CanSet() && field.IsZero() { - tag := fieldType.Tag.Get("url") - if tag == "" || tag == "-" { - continue - } - wireName, _ := parseTag(tag) - if wireName == "" { - wireName = fieldName - } - if defaultVal, exists := defaults[wireName]; exists { - values.Set(wireName, valueString(reflect.ValueOf(defaultVal), tagOptions{}, reflect.StructField{})) - } - } - } - - return values, err -} - -// reflectValue populates the values parameter from the struct fields in val. -// Embedded structs are followed recursively (using the rules defined in the -// Values function documentation) breadth-first. -func reflectValue(values url.Values, val reflect.Value, scope string) error { - typ := val.Type() - for i := 0; i < typ.NumField(); i++ { - sf := typ.Field(i) - if sf.PkgPath != "" && !sf.Anonymous { - // Skip unexported fields. - continue - } - - sv := val.Field(i) - tag := sf.Tag.Get("url") - if tag == "" || tag == "-" { - continue - } - - name, opts := parseTag(tag) - if name == "" { - name = sf.Name - } - - if scope != "" { - name = scope + "[" + name + "]" - } - - if opts.Contains("omitempty") && isEmptyValue(sv) { - continue - } - - if sv.Type().Implements(queryEncoderType) { - // If sv is a nil pointer and the custom encoder is defined on a non-pointer - // method receiver, set sv to the zero value of the underlying type - if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { - sv = reflect.New(sv.Type().Elem()) - } - - m := sv.Interface().(QueryEncoder) - if err := m.EncodeQueryValues(name, &values); err != nil { - return err - } - continue - } - - // Recursively dereference pointers, but stop at nil pointers. - for sv.Kind() == reflect.Ptr { - if sv.IsNil() { - break - } - sv = sv.Elem() - } - - if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { - values.Add(name, valueString(sv, opts, sf)) - continue - } - - if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { - if sv.Len() == 0 { - // Skip if slice or array is empty. - continue - } - for i := 0; i < sv.Len(); i++ { - value := sv.Index(i) - if isStructPointer(value) && !value.IsNil() { - if err := reflectValue(values, value.Elem(), name); err != nil { - return err - } - } else { - values.Add(name, valueString(value, opts, sf)) - } - } - continue - } - - if sv.Kind() == reflect.Map { - if err := reflectMap(values, sv, name); err != nil { - return err - } - continue - } - - if sv.Kind() == reflect.Struct { - if err := reflectValue(values, sv, name); err != nil { - return err - } - continue - } - - values.Add(name, valueString(sv, opts, sf)) - } - - return nil -} - -// reflectMap handles map types specifically, generating query parameters in the format key[mapkey]=value -func reflectMap(values url.Values, val reflect.Value, scope string) error { - if val.IsNil() { - return nil - } - - iter := val.MapRange() - for iter.Next() { - k := iter.Key() - v := iter.Value() - - key := fmt.Sprint(k.Interface()) - paramName := scope + "[" + key + "]" - - for v.Kind() == reflect.Ptr { - if v.IsNil() { - break - } - v = v.Elem() - } - - for v.Kind() == reflect.Interface { - v = v.Elem() - } - - if v.Kind() == reflect.Map { - if err := reflectMap(values, v, paramName); err != nil { - return err - } - continue - } - - if v.Kind() == reflect.Struct { - if err := reflectValue(values, v, paramName); err != nil { - return err - } - continue - } - - if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { - if v.Len() == 0 { - continue - } - for i := 0; i < v.Len(); i++ { - value := v.Index(i) - if isStructPointer(value) && !value.IsNil() { - if err := reflectValue(values, value.Elem(), paramName); err != nil { - return err - } - } else { - values.Add(paramName, valueString(value, tagOptions{}, reflect.StructField{})) - } - } - continue - } - - values.Add(paramName, valueString(v, tagOptions{}, reflect.StructField{})) - } - - return nil -} - -// valueString returns the string representation of a value. -func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { - for v.Kind() == reflect.Ptr { - if v.IsNil() { - return "" - } - v = v.Elem() - } - - if v.Type() == timeType { - t := v.Interface().(time.Time) - if format := sf.Tag.Get("format"); format == "date" { - return t.Format("2006-01-02") - } - return t.Format(RFC3339Milli) - } - - if v.Type() == uuidType { - u := v.Interface().(uuid.UUID) - return u.String() - } - - if v.Type() == bytesType { - b := v.Interface().([]byte) - return base64.StdEncoding.EncodeToString(b) - } - - return fmt.Sprint(v.Interface()) -} - -// isEmptyValue checks if a value should be considered empty for the purposes -// of omitting fields with the "omitempty" option. -func isEmptyValue(v reflect.Value) bool { - type zeroable interface { - IsZero() bool - } - - if !v.IsZero() { - if z, ok := v.Interface().(zeroable); ok { - return z.IsZero() - } - } - - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: - return false - } - - return false -} - -// isStructPointer returns true if the given reflect.Value is a pointer to a struct. -func isStructPointer(v reflect.Value) bool { - return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct -} - -// tagOptions is the string following a comma in a struct field's "url" tag, or -// the empty string. It does not include the leading comma. -type tagOptions []string - -// parseTag splits a struct field's url tag into its name and comma-separated -// options. -func parseTag(tag string) (string, tagOptions) { - s := strings.Split(tag, ",") - return s[0], s[1:] -} - -// Contains checks whether the tagOptions contains the specified option. -func (o tagOptions) Contains(option string) bool { - for _, s := range o { - if s == option { - return true - } - } - return false -} diff --git a/seed/go-sdk/allof-inline/internal/query_test.go b/seed/go-sdk/allof-inline/internal/query_test.go deleted file mode 100644 index 5b463e297350..000000000000 --- a/seed/go-sdk/allof-inline/internal/query_test.go +++ /dev/null @@ -1,395 +0,0 @@ -package internal - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestQueryValues(t *testing.T) { - t.Run("empty optional", func(t *testing.T) { - type nested struct { - Value *string `json:"value,omitempty" url:"value,omitempty"` - } - type example struct { - Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` - } - - values, err := QueryValues(&example{}) - require.NoError(t, err) - assert.Empty(t, values) - }) - - t.Run("empty required", func(t *testing.T) { - type nested struct { - Value *string `json:"value,omitempty" url:"value,omitempty"` - } - type example struct { - Required string `json:"required" url:"required"` - Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` - } - - values, err := QueryValues(&example{}) - require.NoError(t, err) - assert.Equal(t, "required=", values.Encode()) - }) - - t.Run("allow multiple", func(t *testing.T) { - type example struct { - Values []string `json:"values" url:"values"` - } - - values, err := QueryValues( - &example{ - Values: []string{"foo", "bar", "baz"}, - }, - ) - require.NoError(t, err) - assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) - }) - - t.Run("nested object", func(t *testing.T) { - type nested struct { - Value *string `json:"value,omitempty" url:"value,omitempty"` - } - type example struct { - Required string `json:"required" url:"required"` - Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` - } - - nestedValue := "nestedValue" - values, err := QueryValues( - &example{ - Required: "requiredValue", - Nested: &nested{ - Value: &nestedValue, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) - }) - - t.Run("url unspecified", func(t *testing.T) { - type example struct { - Required string `json:"required" url:"required"` - NotFound string `json:"notFound"` - } - - values, err := QueryValues( - &example{ - Required: "requiredValue", - NotFound: "notFound", - }, - ) - require.NoError(t, err) - assert.Equal(t, "required=requiredValue", values.Encode()) - }) - - t.Run("url ignored", func(t *testing.T) { - type example struct { - Required string `json:"required" url:"required"` - NotFound string `json:"notFound" url:"-"` - } - - values, err := QueryValues( - &example{ - Required: "requiredValue", - NotFound: "notFound", - }, - ) - require.NoError(t, err) - assert.Equal(t, "required=requiredValue", values.Encode()) - }) - - t.Run("datetime", func(t *testing.T) { - type example struct { - DateTime time.Time `json:"dateTime" url:"dateTime"` - } - - values, err := QueryValues( - &example{ - DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), - }, - ) - require.NoError(t, err) - assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56.000Z", values.Encode()) - }) - - t.Run("date", func(t *testing.T) { - type example struct { - Date time.Time `json:"date" url:"date" format:"date"` - } - - values, err := QueryValues( - &example{ - Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), - }, - ) - require.NoError(t, err) - assert.Equal(t, "date=1994-03-16", values.Encode()) - }) - - t.Run("optional time", func(t *testing.T) { - type example struct { - Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` - } - - values, err := QueryValues( - &example{}, - ) - require.NoError(t, err) - assert.Empty(t, values.Encode()) - }) - - t.Run("omitempty with non-pointer zero value", func(t *testing.T) { - type enum string - - type example struct { - Enum enum `json:"enum,omitempty" url:"enum,omitempty"` - } - - values, err := QueryValues( - &example{}, - ) - require.NoError(t, err) - assert.Empty(t, values.Encode()) - }) - - t.Run("object array", func(t *testing.T) { - type object struct { - Key string `json:"key" url:"key"` - Value string `json:"value" url:"value"` - } - type example struct { - Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` - } - - values, err := QueryValues( - &example{ - Objects: []*object{ - { - Key: "hello", - Value: "world", - }, - { - Key: "foo", - Value: "bar", - }, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) - }) - - t.Run("map", func(t *testing.T) { - type request struct { - Metadata map[string]interface{} `json:"metadata" url:"metadata"` - } - values, err := QueryValues( - &request{ - Metadata: map[string]interface{}{ - "foo": "bar", - "baz": "qux", - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "metadata%5Bbaz%5D=qux&metadata%5Bfoo%5D=bar", values.Encode()) - }) - - t.Run("nested map", func(t *testing.T) { - type request struct { - Metadata map[string]interface{} `json:"metadata" url:"metadata"` - } - values, err := QueryValues( - &request{ - Metadata: map[string]interface{}{ - "inner": map[string]interface{}{ - "foo": "bar", - }, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "metadata%5Binner%5D%5Bfoo%5D=bar", values.Encode()) - }) - - t.Run("nested map array", func(t *testing.T) { - type request struct { - Metadata map[string]interface{} `json:"metadata" url:"metadata"` - } - values, err := QueryValues( - &request{ - Metadata: map[string]interface{}{ - "inner": []string{ - "one", - "two", - "three", - }, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "metadata%5Binner%5D=one&metadata%5Binner%5D=two&metadata%5Binner%5D=three", values.Encode()) - }) -} - -func TestQueryValuesWithDefaults(t *testing.T) { - t.Run("apply defaults to zero values", func(t *testing.T) { - type example struct { - Name string `json:"name" url:"name"` - Age int `json:"age" url:"age"` - Enabled bool `json:"enabled" url:"enabled"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "enabled": true, - } - - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) - }) - - t.Run("preserve non-zero values over defaults", func(t *testing.T) { - type example struct { - Name string `json:"name" url:"name"` - Age int `json:"age" url:"age"` - Enabled bool `json:"enabled" url:"enabled"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "enabled": true, - } - - values, err := QueryValuesWithDefaults(&example{ - Name: "actual-name", - Age: 30, - // Enabled remains false (zero value), should get default - }, defaults) - require.NoError(t, err) - assert.Equal(t, "age=30&enabled=true&name=actual-name", values.Encode()) - }) - - t.Run("ignore defaults for fields not in struct", func(t *testing.T) { - type example struct { - Name string `json:"name" url:"name"` - Age int `json:"age" url:"age"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "nonexistent": "should-be-ignored", - } - - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "age=25&name=default-name", values.Encode()) - }) - - t.Run("type conversion for compatible defaults", func(t *testing.T) { - type example struct { - Count int64 `json:"count" url:"count"` - Rate float64 `json:"rate" url:"rate"` - Message string `json:"message" url:"message"` - } - - defaults := map[string]interface{}{ - "count": int(100), // int -> int64 conversion - "rate": float32(2.5), // float32 -> float64 conversion - "message": "hello", // string -> string (no conversion needed) - } - - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "count=100&message=hello&rate=2.5", values.Encode()) - }) - - t.Run("mixed with pointer fields and omitempty", func(t *testing.T) { - type example struct { - Required string `json:"required" url:"required"` - Optional *string `json:"optional,omitempty" url:"optional,omitempty"` - Count int `json:"count,omitempty" url:"count,omitempty"` - } - - defaultOptional := "default-optional" - defaults := map[string]interface{}{ - "required": "default-required", - "optional": &defaultOptional, // pointer type - "count": 42, - } - - values, err := QueryValuesWithDefaults(&example{ - Required: "custom-required", // should override default - // Optional is nil, should get default - // Count is 0, should get default - }, defaults) - require.NoError(t, err) - assert.Equal(t, "count=42&optional=default-optional&required=custom-required", values.Encode()) - }) - - t.Run("override non-zero defaults with explicit zero values", func(t *testing.T) { - type example struct { - Name *string `json:"name" url:"name"` - Age *int `json:"age" url:"age"` - Enabled *bool `json:"enabled" url:"enabled"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "enabled": true, - } - - // first, test that a properly empty request is overridden: - { - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) - } - - // second, test that a request that contains zeros is not overridden: - var ( - name = "" - age = 0 - enabled = false - ) - values, err := QueryValuesWithDefaults(&example{ - Name: &name, // explicit empty string should override default - Age: &age, // explicit zero should override default - Enabled: &enabled, // explicit false should override default - }, defaults) - require.NoError(t, err) - assert.Equal(t, "age=0&enabled=false&name=", values.Encode()) - }) - - t.Run("nil input returns empty values", func(t *testing.T) { - defaults := map[string]any{ - "name": "default-name", - "age": 25, - } - - // Test with nil - values, err := QueryValuesWithDefaults(nil, defaults) - require.NoError(t, err) - assert.Empty(t, values) - - // Test with nil pointer - type example struct { - Name string `json:"name" url:"name"` - } - var nilPtr *example - values, err = QueryValuesWithDefaults(nilPtr, defaults) - require.NoError(t, err) - assert.Empty(t, values) - }) -} diff --git a/seed/go-sdk/allof-inline/internal/retrier.go b/seed/go-sdk/allof-inline/internal/retrier.go deleted file mode 100644 index 02fd1fb7d3f1..000000000000 --- a/seed/go-sdk/allof-inline/internal/retrier.go +++ /dev/null @@ -1,239 +0,0 @@ -package internal - -import ( - "crypto/rand" - "math/big" - "net/http" - "strconv" - "time" -) - -const ( - defaultRetryAttempts = 2 - minRetryDelay = 1000 * time.Millisecond - maxRetryDelay = 60000 * time.Millisecond -) - -// RetryOption adapts the behavior the *Retrier. -type RetryOption func(*retryOptions) - -// RetryFunc is a retryable HTTP function call (i.e. *http.Client.Do). -type RetryFunc func(*http.Request) (*http.Response, error) - -// WithMaxAttempts configures the maximum number of attempts -// of the *Retrier. -func WithMaxAttempts(attempts uint) RetryOption { - return func(opts *retryOptions) { - opts.attempts = attempts - } -} - -// Retrier retries failed requests a configurable number of times with an -// exponential back-off between each retry. -type Retrier struct { - attempts uint -} - -// NewRetrier constructs a new *Retrier with the given options, if any. -func NewRetrier(opts ...RetryOption) *Retrier { - options := new(retryOptions) - for _, opt := range opts { - opt(options) - } - attempts := uint(defaultRetryAttempts) - if options.attempts > 0 { - attempts = options.attempts - } - return &Retrier{ - attempts: attempts, - } -} - -// Run issues the request and, upon failure, retries the request if possible. -// -// The request will be retried as long as the request is deemed retryable and the -// number of retry attempts has not grown larger than the configured retry limit. -func (r *Retrier) Run( - fn RetryFunc, - request *http.Request, - errorDecoder ErrorDecoder, - opts ...RetryOption, -) (*http.Response, error) { - options := new(retryOptions) - for _, opt := range opts { - opt(options) - } - maxRetryAttempts := r.attempts - if options.attempts > 0 { - maxRetryAttempts = options.attempts - } - var ( - retryAttempt uint - previousError error - ) - return r.run( - fn, - request, - errorDecoder, - maxRetryAttempts, - retryAttempt, - previousError, - ) -} - -func (r *Retrier) run( - fn RetryFunc, - request *http.Request, - errorDecoder ErrorDecoder, - maxRetryAttempts uint, - retryAttempt uint, - previousError error, -) (*http.Response, error) { - if retryAttempt >= maxRetryAttempts { - return nil, previousError - } - - // If the call has been cancelled, don't issue the request. - if err := request.Context().Err(); err != nil { - return nil, err - } - - // Reset the request body for retries since the body may have already been read. - if retryAttempt > 0 && request.GetBody != nil { - requestBody, err := request.GetBody() - if err != nil { - return nil, err - } - request.Body = requestBody - } - - response, err := fn(request) - if err != nil { - return nil, err - } - - if r.shouldRetry(response) { - defer func() { _ = response.Body.Close() }() - - delay, err := r.retryDelay(response, retryAttempt) - if err != nil { - return nil, err - } - - time.Sleep(delay) - - return r.run( - fn, - request, - errorDecoder, - maxRetryAttempts, - retryAttempt+1, - decodeError(response, errorDecoder), - ) - } - - return response, nil -} - -// shouldRetry returns true if the request should be retried based on the given -// response status code. -func (r *Retrier) shouldRetry(response *http.Response) bool { - return response.StatusCode == http.StatusTooManyRequests || - response.StatusCode == http.StatusRequestTimeout || - response.StatusCode >= http.StatusInternalServerError -} - -// retryDelay calculates the delay time based on response headers, -// falling back to exponential backoff if no headers are present. -func (r *Retrier) retryDelay(response *http.Response, retryAttempt uint) (time.Duration, error) { - // Check for Retry-After header first (RFC 7231), applying no jitter - if retryAfter := response.Header.Get("Retry-After"); retryAfter != "" { - // Parse as number of seconds... - if seconds, err := strconv.Atoi(retryAfter); err == nil { - delay := time.Duration(seconds) * time.Second - if delay > 0 { - if delay > maxRetryDelay { - delay = maxRetryDelay - } - return delay, nil - } - } - - // ...or as an HTTP date; both are valid - if retryTime, err := time.Parse(time.RFC1123, retryAfter); err == nil { - delay := time.Until(retryTime) - if delay > 0 { - if delay > maxRetryDelay { - delay = maxRetryDelay - } - return delay, nil - } - } - } - - // Then check for industry-standard X-RateLimit-Reset header, applying positive jitter - if rateLimitReset := response.Header.Get("X-RateLimit-Reset"); rateLimitReset != "" { - if resetTimestamp, err := strconv.ParseInt(rateLimitReset, 10, 64); err == nil { - // Assume Unix timestamp in seconds - resetTime := time.Unix(resetTimestamp, 0) - delay := time.Until(resetTime) - if delay > 0 { - if delay > maxRetryDelay { - delay = maxRetryDelay - } - return r.addPositiveJitter(delay) - } - } - } - - // Fall back to exponential backoff - return r.exponentialBackoff(retryAttempt) -} - -// exponentialBackoff calculates the delay time based on the retry attempt -// and applies symmetric jitter (±10% around the delay). -func (r *Retrier) exponentialBackoff(retryAttempt uint) (time.Duration, error) { - if retryAttempt > 63 { // 2^63+ would overflow uint64 - retryAttempt = 63 - } - - delay := minRetryDelay << retryAttempt - if delay > maxRetryDelay { - delay = maxRetryDelay - } - - return r.addSymmetricJitter(delay) -} - -// addJitterWithRange applies jitter to the given delay. -// minPercent and maxPercent define the jitter range (e.g., 100, 120 for +0% to +20%). -func (r *Retrier) addJitterWithRange(delay time.Duration, minPercent, maxPercent int) (time.Duration, error) { - jitterRange := big.NewInt(int64(delay * time.Duration(maxPercent-minPercent) / 100)) - jitter, err := rand.Int(rand.Reader, jitterRange) - if err != nil { - return 0, err - } - - jitteredDelay := delay + time.Duration(jitter.Int64()) + delay*time.Duration(minPercent-100)/100 - if jitteredDelay < minRetryDelay { - jitteredDelay = minRetryDelay - } - if jitteredDelay > maxRetryDelay { - jitteredDelay = maxRetryDelay - } - return jitteredDelay, nil -} - -// addPositiveJitter applies positive jitter to the given delay (100%-120% range). -func (r *Retrier) addPositiveJitter(delay time.Duration) (time.Duration, error) { - return r.addJitterWithRange(delay, 100, 120) -} - -// addSymmetricJitter applies symmetric jitter to the given delay (90%-110% range). -func (r *Retrier) addSymmetricJitter(delay time.Duration) (time.Duration, error) { - return r.addJitterWithRange(delay, 90, 110) -} - -type retryOptions struct { - attempts uint -} diff --git a/seed/go-sdk/allof-inline/internal/retrier_test.go b/seed/go-sdk/allof-inline/internal/retrier_test.go deleted file mode 100644 index 8c4692cdf509..000000000000 --- a/seed/go-sdk/allof-inline/internal/retrier_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package internal - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/allof-inline/fern/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type RetryTestCase struct { - description string - - giveAttempts uint - giveStatusCodes []int - giveResponse *InternalTestResponse - - wantResponse *InternalTestResponse - wantError *core.APIError -} - -func TestRetrier(t *testing.T) { - tests := []*RetryTestCase{ - { - description: "retry request succeeds after multiple failures", - giveAttempts: 3, - giveStatusCodes: []int{ - http.StatusServiceUnavailable, - http.StatusServiceUnavailable, - http.StatusOK, - }, - giveResponse: &InternalTestResponse{ - Id: "1", - }, - wantResponse: &InternalTestResponse{ - Id: "1", - }, - }, - { - description: "retry request fails if MaxAttempts is exceeded", - giveAttempts: 3, - giveStatusCodes: []int{ - http.StatusRequestTimeout, - http.StatusRequestTimeout, - http.StatusRequestTimeout, - http.StatusOK, - }, - wantError: &core.APIError{ - StatusCode: http.StatusRequestTimeout, - }, - }, - { - description: "retry durations increase exponentially and stay within the min and max delay values", - giveAttempts: 4, - giveStatusCodes: []int{ - http.StatusServiceUnavailable, - http.StatusServiceUnavailable, - http.StatusServiceUnavailable, - http.StatusOK, - }, - }, - { - description: "retry does not occur on status code 404", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusNotFound, http.StatusOK}, - wantError: &core.APIError{ - StatusCode: http.StatusNotFound, - }, - }, - { - description: "retries occur on status code 429", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusTooManyRequests, http.StatusOK}, - }, - { - description: "retries occur on status code 408", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusRequestTimeout, http.StatusOK}, - }, - { - description: "retries occur on status code 500", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusInternalServerError, http.StatusOK}, - }, - } - - for _, tc := range tests { - t.Run(tc.description, func(t *testing.T) { - var ( - test = tc - server = newTestRetryServer(t, test) - client = server.Client() - ) - - t.Parallel() - - caller := NewCaller( - &CallerParams{ - Client: client, - }, - ) - - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL, - Method: http.MethodGet, - Request: &InternalTestRequest{}, - Response: &response, - MaxAttempts: test.giveAttempts, - ResponseIsOptional: true, - }, - ) - - if test.wantError != nil { - require.IsType(t, err, &core.APIError{}) - expectedErrorCode := test.wantError.StatusCode - actualErrorCode := err.(*core.APIError).StatusCode - assert.Equal(t, expectedErrorCode, actualErrorCode) - return - } - - require.NoError(t, err) - assert.Equal(t, test.wantResponse, response) - }) - } -} - -// newTestRetryServer returns a new *httptest.Server configured with the -// given test parameters, suitable for testing retries. -func newTestRetryServer(t *testing.T, tc *RetryTestCase) *httptest.Server { - var index int - timestamps := make([]time.Time, 0, len(tc.giveStatusCodes)) - - return httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - timestamps = append(timestamps, time.Now()) - if index > 0 && index < len(expectedRetryDurations) { - // Ensure that the duration between retries increases exponentially, - // and that it is within the minimum and maximum retry delay values. - actualDuration := timestamps[index].Sub(timestamps[index-1]) - expectedDurationMin := expectedRetryDurations[index-1] * 50 / 100 - expectedDurationMax := expectedRetryDurations[index-1] * 150 / 100 - assert.True( - t, - actualDuration >= expectedDurationMin && actualDuration <= expectedDurationMax, - "expected duration to be in range [%v, %v], got %v", - expectedDurationMin, - expectedDurationMax, - actualDuration, - ) - assert.LessOrEqual( - t, - actualDuration, - maxRetryDelay, - "expected duration to be less than the maxRetryDelay (%v), got %v", - maxRetryDelay, - actualDuration, - ) - assert.GreaterOrEqual( - t, - actualDuration, - minRetryDelay, - "expected duration to be greater than the minRetryDelay (%v), got %v", - minRetryDelay, - actualDuration, - ) - } - - request := new(InternalTestRequest) - bytes, err := io.ReadAll(r.Body) - require.NoError(t, err) - require.NoError(t, json.Unmarshal(bytes, request)) - require.LessOrEqual(t, index, len(tc.giveStatusCodes)) - - statusCode := tc.giveStatusCodes[index] - - w.WriteHeader(statusCode) - - if tc.giveResponse != nil && statusCode == http.StatusOK { - bytes, err = json.Marshal(tc.giveResponse) - require.NoError(t, err) - _, err = w.Write(bytes) - require.NoError(t, err) - } - - index++ - }, - ), - ) -} - -// expectedRetryDurations holds an array of calculated retry durations, -// where the index of the array should correspond to the retry attempt. -// -// Values are calculated based off of `minRetryDelay * 2^i`. -var expectedRetryDurations = []time.Duration{ - 1000 * time.Millisecond, // 500ms * 2^1 = 1000ms - 2000 * time.Millisecond, // 500ms * 2^2 = 2000ms - 4000 * time.Millisecond, // 500ms * 2^3 = 4000ms - 8000 * time.Millisecond, // 500ms * 2^4 = 8000ms -} - -func TestRetryWithRequestBody(t *testing.T) { - // This test verifies that POST requests with a body are properly retried. - // The request body should be re-sent on each retry attempt. - expectedBody := `{"id":"test-id"}` - var requestBodies []string - var requestCount int - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestCount++ - bodyBytes, err := io.ReadAll(r.Body) - require.NoError(t, err) - requestBodies = append(requestBodies, string(bodyBytes)) - - if requestCount == 1 { - // First request - return retryable error - w.WriteHeader(http.StatusServiceUnavailable) - return - } - // Second request - return success - w.WriteHeader(http.StatusOK) - response := &InternalTestResponse{Id: "success"} - bytes, _ := json.Marshal(response) - _, _ = w.Write(bytes) - })) - defer server.Close() - - caller := NewCaller(&CallerParams{ - Client: server.Client(), - }) - - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL, - Method: http.MethodPost, - Request: &InternalTestRequest{Id: "test-id"}, - Response: &response, - MaxAttempts: 2, - ResponseIsOptional: true, - }, - ) - - require.NoError(t, err) - require.Equal(t, 2, requestCount, "Expected exactly 2 requests") - require.Len(t, requestBodies, 2, "Expected 2 request bodies to be captured") - - // Both requests should have the same non-empty body - assert.Equal(t, expectedBody, requestBodies[0], "First request body should match expected") - assert.Equal(t, expectedBody, requestBodies[1], "Second request body should match expected (retry should re-send body)") -} - -func TestRetryDelayTiming(t *testing.T) { - tests := []struct { - name string - headerName string - headerValueFunc func() string - expectedMinMs int64 - expectedMaxMs int64 - }{ - { - name: "retry-after with seconds value", - headerName: "retry-after", - headerValueFunc: func() string { - return "1" - }, - expectedMinMs: 500, - expectedMaxMs: 1500, - }, - { - name: "retry-after with HTTP date", - headerName: "retry-after", - headerValueFunc: func() string { - return time.Now().Add(3 * time.Second).Format(time.RFC1123) - }, - expectedMinMs: 1500, - expectedMaxMs: 4500, - }, - { - name: "x-ratelimit-reset with future timestamp", - headerName: "x-ratelimit-reset", - headerValueFunc: func() string { - return fmt.Sprintf("%d", time.Now().Add(3*time.Second).Unix()) - }, - expectedMinMs: 1500, - expectedMaxMs: 4500, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - var timestamps []time.Time - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - timestamps = append(timestamps, time.Now()) - if len(timestamps) == 1 { - // First request - return retryable error with header - w.Header().Set(tt.headerName, tt.headerValueFunc()) - w.WriteHeader(http.StatusTooManyRequests) - } else { - // Second request - return success - w.WriteHeader(http.StatusOK) - response := &InternalTestResponse{Id: "success"} - bytes, _ := json.Marshal(response) - _, _ = w.Write(bytes) - } - })) - defer server.Close() - - caller := NewCaller(&CallerParams{ - Client: server.Client(), - }) - - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL, - Method: http.MethodGet, - Request: &InternalTestRequest{}, - Response: &response, - MaxAttempts: 2, - ResponseIsOptional: true, - }, - ) - - require.NoError(t, err) - require.Len(t, timestamps, 2, "Expected exactly 2 requests") - - actualDelayMs := timestamps[1].Sub(timestamps[0]).Milliseconds() - - assert.GreaterOrEqual(t, actualDelayMs, tt.expectedMinMs, - "Actual delay %dms should be >= expected min %dms", actualDelayMs, tt.expectedMinMs) - assert.LessOrEqual(t, actualDelayMs, tt.expectedMaxMs, - "Actual delay %dms should be <= expected max %dms", actualDelayMs, tt.expectedMaxMs) - }) - } -} diff --git a/seed/go-sdk/allof-inline/internal/stringer.go b/seed/go-sdk/allof-inline/internal/stringer.go deleted file mode 100644 index 312801851e0e..000000000000 --- a/seed/go-sdk/allof-inline/internal/stringer.go +++ /dev/null @@ -1,13 +0,0 @@ -package internal - -import "encoding/json" - -// StringifyJSON returns a pretty JSON string representation of -// the given value. -func StringifyJSON(value interface{}) (string, error) { - bytes, err := json.MarshalIndent(value, "", " ") - if err != nil { - return "", err - } - return string(bytes), nil -} diff --git a/seed/go-sdk/allof-inline/internal/time.go b/seed/go-sdk/allof-inline/internal/time.go deleted file mode 100644 index 57f901a35ed8..000000000000 --- a/seed/go-sdk/allof-inline/internal/time.go +++ /dev/null @@ -1,165 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "time" -) - -const dateFormat = "2006-01-02" - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date (e.g. 2006-01-02). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type Date struct { - t *time.Time -} - -// NewDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewDate(t time.Time) *Date { - return &Date{t: &t} -} - -// NewOptionalDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDate(t *time.Time) *Date { - if t == nil { - return nil - } - return &Date{t: t} -} - -// Time returns the Date's underlying time, if any. If the -// date is nil, the zero value is returned. -func (d *Date) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the Date's underlying time.Time, if any. -func (d *Date) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *Date) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(dateFormat)) -} - -func (d *Date) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - parsedTime, err := time.Parse(dateFormat, raw) - if err != nil { - return err - } - - *d = Date{t: &parsedTime} - return nil -} - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type DateTime struct { - t *time.Time -} - -// NewDateTime returns a new *DateTime. -func NewDateTime(t time.Time) *DateTime { - return &DateTime{t: &t} -} - -// NewOptionalDateTime returns a new *DateTime. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDateTime(t *time.Time) *DateTime { - if t == nil { - return nil - } - return &DateTime{t: t} -} - -// Time returns the DateTime's underlying time, if any. If the -// date-time is nil, the zero value is returned. -func (d *DateTime) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. -func (d *DateTime) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *DateTime) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(time.RFC3339)) -} - -func (d *DateTime) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - // If the value is not a string, check if it is a number (unix epoch seconds). - var epoch int64 - if numErr := json.Unmarshal(data, &epoch); numErr == nil { - t := time.Unix(epoch, 0).UTC() - *d = DateTime{t: &t} - return nil - } - return err - } - - // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). - parsedTime, err := time.Parse(time.RFC3339Nano, raw) - if err == nil { - *d = DateTime{t: &parsedTime} - return nil - } - rfc3339NanoErr := err - - // Fall back to ISO 8601 without timezone (assume UTC). - parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - iso8601Err := err - - // Fall back to date-only format. - parsedTime, err = time.Parse("2006-01-02", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - dateOnlyErr := err - - return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) -} diff --git a/seed/go-sdk/allof-inline/option/request_option.go b/seed/go-sdk/allof-inline/option/request_option.go deleted file mode 100644 index f91507faf8c9..000000000000 --- a/seed/go-sdk/allof-inline/option/request_option.go +++ /dev/null @@ -1,73 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package option - -import ( - core "github.com/allof-inline/fern/core" - http "net/http" - url "net/url" -) - -// RequestOption adapts the behavior of an individual request. -type RequestOption = core.RequestOption - -// WithBaseURL sets the base URL, overriding the default -// environment, if any. -func WithBaseURL(baseURL string) *core.BaseURLOption { - return &core.BaseURLOption{ - BaseURL: baseURL, - } -} - -// WithHTTPClient uses the given HTTPClient to issue the request. -func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { - return &core.HTTPClientOption{ - HTTPClient: httpClient, - } -} - -// WithHTTPHeader adds the given http.Header to the request. -func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { - return &core.HTTPHeaderOption{ - // Clone the headers so they can't be modified after the option call. - HTTPHeader: httpHeader.Clone(), - } -} - -// WithBodyProperties adds the given body properties to the request. -func WithBodyProperties(bodyProperties map[string]interface{}) *core.BodyPropertiesOption { - copiedBodyProperties := make(map[string]interface{}, len(bodyProperties)) - for key, value := range bodyProperties { - copiedBodyProperties[key] = value - } - return &core.BodyPropertiesOption{ - BodyProperties: copiedBodyProperties, - } -} - -// WithQueryParameters adds the given query parameters to the request. -func WithQueryParameters(queryParameters url.Values) *core.QueryParametersOption { - copiedQueryParameters := make(url.Values, len(queryParameters)) - for key, values := range queryParameters { - copiedQueryParameters[key] = values - } - return &core.QueryParametersOption{ - QueryParameters: copiedQueryParameters, - } -} - -// WithMaxAttempts configures the maximum number of retry attempts. -func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { - return &core.MaxAttemptsOption{ - MaxAttempts: attempts, - } -} - -// WithMaxStreamBufSize configures the maximum buffer size for streaming responses. -// This controls the maximum size of a single message (in bytes) that the stream -// can process. By default, this is set to 1MB. -func WithMaxStreamBufSize(size int) *core.MaxBufSizeOption { - return &core.MaxBufSizeOption{ - MaxBufSize: size, - } -} diff --git a/seed/go-sdk/allof-inline/pointer.go b/seed/go-sdk/allof-inline/pointer.go deleted file mode 100644 index f9a8177e734f..000000000000 --- a/seed/go-sdk/allof-inline/pointer.go +++ /dev/null @@ -1,137 +0,0 @@ -package api - -import ( - "time" - - "github.com/google/uuid" -) - -// Bool returns a pointer to the given bool value. -func Bool(b bool) *bool { - return &b -} - -// Byte returns a pointer to the given byte value. -func Byte(b byte) *byte { - return &b -} - -// Bytes returns a pointer to the given []byte value. -func Bytes(b []byte) *[]byte { - return &b -} - -// Complex64 returns a pointer to the given complex64 value. -func Complex64(c complex64) *complex64 { - return &c -} - -// Complex128 returns a pointer to the given complex128 value. -func Complex128(c complex128) *complex128 { - return &c -} - -// Float32 returns a pointer to the given float32 value. -func Float32(f float32) *float32 { - return &f -} - -// Float64 returns a pointer to the given float64 value. -func Float64(f float64) *float64 { - return &f -} - -// Int returns a pointer to the given int value. -func Int(i int) *int { - return &i -} - -// Int8 returns a pointer to the given int8 value. -func Int8(i int8) *int8 { - return &i -} - -// Int16 returns a pointer to the given int16 value. -func Int16(i int16) *int16 { - return &i -} - -// Int32 returns a pointer to the given int32 value. -func Int32(i int32) *int32 { - return &i -} - -// Int64 returns a pointer to the given int64 value. -func Int64(i int64) *int64 { - return &i -} - -// Rune returns a pointer to the given rune value. -func Rune(r rune) *rune { - return &r -} - -// String returns a pointer to the given string value. -func String(s string) *string { - return &s -} - -// Uint returns a pointer to the given uint value. -func Uint(u uint) *uint { - return &u -} - -// Uint8 returns a pointer to the given uint8 value. -func Uint8(u uint8) *uint8 { - return &u -} - -// Uint16 returns a pointer to the given uint16 value. -func Uint16(u uint16) *uint16 { - return &u -} - -// Uint32 returns a pointer to the given uint32 value. -func Uint32(u uint32) *uint32 { - return &u -} - -// Uint64 returns a pointer to the given uint64 value. -func Uint64(u uint64) *uint64 { - return &u -} - -// Uintptr returns a pointer to the given uintptr value. -func Uintptr(u uintptr) *uintptr { - return &u -} - -// UUID returns a pointer to the given uuid.UUID value. -func UUID(u uuid.UUID) *uuid.UUID { - return &u -} - -// Time returns a pointer to the given time.Time value. -func Time(t time.Time) *time.Time { - return &t -} - -// MustParseDate attempts to parse the given string as a -// date time.Time, and panics upon failure. -func MustParseDate(date string) time.Time { - t, err := time.Parse("2006-01-02", date) - if err != nil { - panic(err) - } - return t -} - -// MustParseDateTime attempts to parse the given string as a -// datetime time.Time, and panics upon failure. -func MustParseDateTime(datetime string) time.Time { - t, err := time.Parse(time.RFC3339, datetime) - if err != nil { - panic(err) - } - return t -} diff --git a/seed/go-sdk/allof-inline/pointer_test.go b/seed/go-sdk/allof-inline/pointer_test.go deleted file mode 100644 index 06d62d2410b2..000000000000 --- a/seed/go-sdk/allof-inline/pointer_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package api - -import ( - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestBool(t *testing.T) { - value := true - ptr := Bool(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestByte(t *testing.T) { - value := byte(42) - ptr := Byte(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestComplex64(t *testing.T) { - value := complex64(1 + 2i) - ptr := Complex64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestComplex128(t *testing.T) { - value := complex128(1 + 2i) - ptr := Complex128(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestFloat32(t *testing.T) { - value := float32(3.14) - ptr := Float32(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestFloat64(t *testing.T) { - value := 3.14159 - ptr := Float64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt(t *testing.T) { - value := 42 - ptr := Int(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt8(t *testing.T) { - value := int8(42) - ptr := Int8(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt16(t *testing.T) { - value := int16(42) - ptr := Int16(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt32(t *testing.T) { - value := int32(42) - ptr := Int32(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt64(t *testing.T) { - value := int64(42) - ptr := Int64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestRune(t *testing.T) { - value := 'A' - ptr := Rune(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestString(t *testing.T) { - value := "hello" - ptr := String(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint(t *testing.T) { - value := uint(42) - ptr := Uint(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint8(t *testing.T) { - value := uint8(42) - ptr := Uint8(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint16(t *testing.T) { - value := uint16(42) - ptr := Uint16(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint32(t *testing.T) { - value := uint32(42) - ptr := Uint32(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint64(t *testing.T) { - value := uint64(42) - ptr := Uint64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUintptr(t *testing.T) { - value := uintptr(42) - ptr := Uintptr(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUUID(t *testing.T) { - value := uuid.New() - ptr := UUID(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestTime(t *testing.T) { - value := time.Now() - ptr := Time(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestMustParseDate(t *testing.T) { - t.Run("valid date", func(t *testing.T) { - result := MustParseDate("2024-01-15") - expected, _ := time.Parse("2006-01-02", "2024-01-15") - assert.Equal(t, expected, result) - }) - - t.Run("invalid date panics", func(t *testing.T) { - assert.Panics(t, func() { - MustParseDate("invalid-date") - }) - }) -} - -func TestMustParseDateTime(t *testing.T) { - t.Run("valid datetime", func(t *testing.T) { - result := MustParseDateTime("2024-01-15T10:30:00Z") - expected, _ := time.Parse(time.RFC3339, "2024-01-15T10:30:00Z") - assert.Equal(t, expected, result) - }) - - t.Run("invalid datetime panics", func(t *testing.T) { - assert.Panics(t, func() { - MustParseDateTime("invalid-datetime") - }) - }) -} - -func TestPointerHelpersWithZeroValues(t *testing.T) { - t.Run("zero bool", func(t *testing.T) { - ptr := Bool(false) - assert.NotNil(t, ptr) - assert.Equal(t, false, *ptr) - }) - - t.Run("zero int", func(t *testing.T) { - ptr := Int(0) - assert.NotNil(t, ptr) - assert.Equal(t, 0, *ptr) - }) - - t.Run("empty string", func(t *testing.T) { - ptr := String("") - assert.NotNil(t, ptr) - assert.Equal(t, "", *ptr) - }) - - t.Run("zero time", func(t *testing.T) { - zeroTime := time.Time{} - ptr := Time(zeroTime) - assert.NotNil(t, ptr) - assert.Equal(t, zeroTime, *ptr) - }) -} diff --git a/seed/go-sdk/allof-inline/reference.md b/seed/go-sdk/allof-inline/reference.md deleted file mode 100644 index 86fef51f321b..000000000000 --- a/seed/go-sdk/allof-inline/reference.md +++ /dev/null @@ -1,186 +0,0 @@ -# Reference -
client.SearchRuleTypes() -> *fern.RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -request := &fern.SearchRuleTypesRequest{} -client.SearchRuleTypes( - context.TODO(), - request, - ) -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `*string` - -
-
-
-
- - -
-
-
- -
client.CreateRule(request) -> *fern.RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } -client.CreateRule( - context.TODO(), - request, - ) -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `string` - -
-
- -
-
- -**executionContext:** `*fern.RuleExecutionContext` - -
-
-
-
- - -
-
-
- -
client.ListUsers() -> *fern.UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -client.ListUsers( - context.TODO(), - ) -} -``` -
-
-
-
- - -
-
-
- -
client.GetEntity() -> *fern.CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -client.GetEntity( - context.TODO(), - ) -} -``` -
-
-
-
- - -
-
-
- -
client.GetOrganization() -> *fern.Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -client.GetOrganization( - context.TODO(), - ) -} -``` -
-
-
-
- - -
-
-
- diff --git a/seed/go-sdk/allof-inline/snippet.json b/seed/go-sdk/allof-inline/snippet.json deleted file mode 100644 index 1b623ba80b32..000000000000 --- a/seed/go-sdk/allof-inline/snippet.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "endpoints": [ - { - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetEntity(\n\tcontext.TODO(),\n)\n" - } - }, - { - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetOrganization(\n\tcontext.TODO(),\n)\n" - } - }, - { - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof-inline/fern\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.SearchRuleTypes(\n\tcontext.TODO(),\n\t\u0026fern.SearchRuleTypesRequest{},\n)\n" - } - }, - { - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof-inline/fern\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.CreateRule(\n\tcontext.TODO(),\n\t\u0026fern.RuleCreateRequest{\n\t\tName: \"name\",\n\t\tExecutionContext: fern.RuleExecutionContextProd,\n\t},\n)\n" - } - }, - { - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof-inline/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.ListUsers(\n\tcontext.TODO(),\n)\n" - } - } - ] -} \ No newline at end of file diff --git a/seed/go-sdk/allof-inline/types.go b/seed/go-sdk/allof-inline/types.go deleted file mode 100644 index 096cbdbff438..000000000000 --- a/seed/go-sdk/allof-inline/types.go +++ /dev/null @@ -1,2091 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - json "encoding/json" - fmt "fmt" - internal "github.com/allof-inline/fern/internal" - big "math/big" - time "time" -) - -var ( - ruleCreateRequestFieldName = big.NewInt(1 << 0) - ruleCreateRequestFieldExecutionContext = big.NewInt(1 << 1) -) - -type RuleCreateRequest struct { - Name string `json:"name" url:"-"` - ExecutionContext RuleExecutionContext `json:"executionContext" url:"-"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` -} - -func (r *RuleCreateRequest) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleCreateRequest) SetName(name string) { - r.Name = name - r.require(ruleCreateRequestFieldName) -} - -// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleCreateRequest) SetExecutionContext(executionContext RuleExecutionContext) { - r.ExecutionContext = executionContext - r.require(ruleCreateRequestFieldExecutionContext) -} - -func (r *RuleCreateRequest) UnmarshalJSON(data []byte) error { - type unmarshaler RuleCreateRequest - var body unmarshaler - if err := json.Unmarshal(data, &body); err != nil { - return err - } - *r = RuleCreateRequest(body) - return nil -} - -func (r *RuleCreateRequest) MarshalJSON() ([]byte, error) { - type embed RuleCreateRequest - var marshaler = struct { - embed - }{ - embed: embed(*r), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -var ( - searchRuleTypesRequestFieldQuery = big.NewInt(1 << 0) -) - -type SearchRuleTypesRequest struct { - Query *string `json:"-" url:"query,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` -} - -func (s *SearchRuleTypesRequest) require(field *big.Int) { - if s.explicitFields == nil { - s.explicitFields = big.NewInt(0) - } - s.explicitFields.Or(s.explicitFields, field) -} - -// SetQuery sets the Query field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (s *SearchRuleTypesRequest) SetQuery(query *string) { - s.Query = query - s.require(searchRuleTypesRequestFieldQuery) -} - -// Common audit metadata. -var ( - auditInfoFieldCreatedBy = big.NewInt(1 << 0) - auditInfoFieldCreatedDateTime = big.NewInt(1 << 1) - auditInfoFieldModifiedBy = big.NewInt(1 << 2) - auditInfoFieldModifiedDateTime = big.NewInt(1 << 3) -) - -type AuditInfo struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (a *AuditInfo) GetCreatedBy() *string { - if a == nil { - return nil - } - return a.CreatedBy -} - -func (a *AuditInfo) GetCreatedDateTime() *time.Time { - if a == nil { - return nil - } - return a.CreatedDateTime -} - -func (a *AuditInfo) GetModifiedBy() *string { - if a == nil { - return nil - } - return a.ModifiedBy -} - -func (a *AuditInfo) GetModifiedDateTime() *time.Time { - if a == nil { - return nil - } - return a.ModifiedDateTime -} - -func (a *AuditInfo) GetExtraProperties() map[string]interface{} { - if a == nil { - return nil - } - return a.extraProperties -} - -func (a *AuditInfo) require(field *big.Int) { - if a.explicitFields == nil { - a.explicitFields = big.NewInt(0) - } - a.explicitFields.Or(a.explicitFields, field) -} - -// SetCreatedBy sets the CreatedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetCreatedBy(createdBy *string) { - a.CreatedBy = createdBy - a.require(auditInfoFieldCreatedBy) -} - -// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetCreatedDateTime(createdDateTime *time.Time) { - a.CreatedDateTime = createdDateTime - a.require(auditInfoFieldCreatedDateTime) -} - -// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetModifiedBy(modifiedBy *string) { - a.ModifiedBy = modifiedBy - a.require(auditInfoFieldModifiedBy) -} - -// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetModifiedDateTime(modifiedDateTime *time.Time) { - a.ModifiedDateTime = modifiedDateTime - a.require(auditInfoFieldModifiedDateTime) -} - -func (a *AuditInfo) UnmarshalJSON(data []byte) error { - type embed AuditInfo - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*a), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *a = AuditInfo(unmarshaler.embed) - a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *a) - if err != nil { - return err - } - a.extraProperties = extraProperties - a.rawJSON = json.RawMessage(data) - return nil -} - -func (a *AuditInfo) MarshalJSON() ([]byte, error) { - type embed AuditInfo - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*a), - CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, a.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (a *AuditInfo) String() string { - if a == nil { - return "" - } - if len(a.rawJSON) > 0 { - if value, err := internal.StringifyJSON(a.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(a); err == nil { - return value - } - return fmt.Sprintf("%#v", a) -} - -var ( - baseOrgFieldID = big.NewInt(1 << 0) - baseOrgFieldMetadata = big.NewInt(1 << 1) -) - -type BaseOrg struct { - ID string `json:"id" url:"id"` - Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (b *BaseOrg) GetID() string { - if b == nil { - return "" - } - return b.ID -} - -func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { - if b == nil { - return nil - } - return b.Metadata -} - -func (b *BaseOrg) GetExtraProperties() map[string]interface{} { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrg) require(field *big.Int) { - if b.explicitFields == nil { - b.explicitFields = big.NewInt(0) - } - b.explicitFields.Or(b.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrg) SetID(id string) { - b.ID = id - b.require(baseOrgFieldID) -} - -// SetMetadata sets the Metadata field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrg) SetMetadata(metadata *BaseOrgMetadata) { - b.Metadata = metadata - b.require(baseOrgFieldMetadata) -} - -func (b *BaseOrg) UnmarshalJSON(data []byte) error { - type unmarshaler BaseOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrg) MarshalJSON() ([]byte, error) { - type embed BaseOrg - var marshaler = struct { - embed - }{ - embed: embed(*b), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (b *BaseOrg) String() string { - if b == nil { - return "" - } - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -var ( - baseOrgMetadataFieldRegion = big.NewInt(1 << 0) - baseOrgMetadataFieldTier = big.NewInt(1 << 1) -) - -type BaseOrgMetadata struct { - // Deployment region from BaseOrg. - Region string `json:"region" url:"region"` - // Subscription tier. - Tier *string `json:"tier,omitempty" url:"tier,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (b *BaseOrgMetadata) GetRegion() string { - if b == nil { - return "" - } - return b.Region -} - -func (b *BaseOrgMetadata) GetTier() *string { - if b == nil { - return nil - } - return b.Tier -} - -func (b *BaseOrgMetadata) GetExtraProperties() map[string]interface{} { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrgMetadata) require(field *big.Int) { - if b.explicitFields == nil { - b.explicitFields = big.NewInt(0) - } - b.explicitFields.Or(b.explicitFields, field) -} - -// SetRegion sets the Region field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrgMetadata) SetRegion(region string) { - b.Region = region - b.require(baseOrgMetadataFieldRegion) -} - -// SetTier sets the Tier field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrgMetadata) SetTier(tier *string) { - b.Tier = tier - b.require(baseOrgMetadataFieldTier) -} - -func (b *BaseOrgMetadata) UnmarshalJSON(data []byte) error { - type unmarshaler BaseOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrgMetadata) MarshalJSON() ([]byte, error) { - type embed BaseOrgMetadata - var marshaler = struct { - embed - }{ - embed: embed(*b), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (b *BaseOrgMetadata) String() string { - if b == nil { - return "" - } - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -var ( - combinedEntityFieldID = big.NewInt(1 << 0) - combinedEntityFieldName = big.NewInt(1 << 1) - combinedEntityFieldSummary = big.NewInt(1 << 2) - combinedEntityFieldStatus = big.NewInt(1 << 3) -) - -type CombinedEntity struct { - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Describable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - Status CombinedEntityStatus `json:"status" url:"status"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (c *CombinedEntity) GetID() string { - if c == nil { - return "" - } - return c.ID -} - -func (c *CombinedEntity) GetName() *string { - if c == nil { - return nil - } - return c.Name -} - -func (c *CombinedEntity) GetSummary() *string { - if c == nil { - return nil - } - return c.Summary -} - -func (c *CombinedEntity) GetStatus() CombinedEntityStatus { - if c == nil { - return "" - } - return c.Status -} - -func (c *CombinedEntity) GetExtraProperties() map[string]interface{} { - if c == nil { - return nil - } - return c.extraProperties -} - -func (c *CombinedEntity) require(field *big.Int) { - if c.explicitFields == nil { - c.explicitFields = big.NewInt(0) - } - c.explicitFields.Or(c.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetID(id string) { - c.ID = id - c.require(combinedEntityFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetName(name *string) { - c.Name = name - c.require(combinedEntityFieldName) -} - -// SetSummary sets the Summary field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetSummary(summary *string) { - c.Summary = summary - c.require(combinedEntityFieldSummary) -} - -// SetStatus sets the Status field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetStatus(status CombinedEntityStatus) { - c.Status = status - c.require(combinedEntityFieldStatus) -} - -func (c *CombinedEntity) UnmarshalJSON(data []byte) error { - type unmarshaler CombinedEntity - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *c = CombinedEntity(value) - extraProperties, err := internal.ExtractExtraProperties(data, *c) - if err != nil { - return err - } - c.extraProperties = extraProperties - c.rawJSON = json.RawMessage(data) - return nil -} - -func (c *CombinedEntity) MarshalJSON() ([]byte, error) { - type embed CombinedEntity - var marshaler = struct { - embed - }{ - embed: embed(*c), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, c.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (c *CombinedEntity) String() string { - if c == nil { - return "" - } - if len(c.rawJSON) > 0 { - if value, err := internal.StringifyJSON(c.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(c); err == nil { - return value - } - return fmt.Sprintf("%#v", c) -} - -type CombinedEntityStatus string - -const ( - CombinedEntityStatusActive CombinedEntityStatus = "active" - CombinedEntityStatusArchived CombinedEntityStatus = "archived" -) - -func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { - switch s { - case "active": - return CombinedEntityStatusActive, nil - case "archived": - return CombinedEntityStatusArchived, nil - } - var t CombinedEntityStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { - return &c -} - -var ( - describableFieldName = big.NewInt(1 << 0) - describableFieldSummary = big.NewInt(1 << 1) -) - -type Describable struct { - // Display name from Describable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (d *Describable) GetName() *string { - if d == nil { - return nil - } - return d.Name -} - -func (d *Describable) GetSummary() *string { - if d == nil { - return nil - } - return d.Summary -} - -func (d *Describable) GetExtraProperties() map[string]interface{} { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *Describable) require(field *big.Int) { - if d.explicitFields == nil { - d.explicitFields = big.NewInt(0) - } - d.explicitFields.Or(d.explicitFields, field) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *Describable) SetName(name *string) { - d.Name = name - d.require(describableFieldName) -} - -// SetSummary sets the Summary field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *Describable) SetSummary(summary *string) { - d.Summary = summary - d.require(describableFieldSummary) -} - -func (d *Describable) UnmarshalJSON(data []byte) error { - type unmarshaler Describable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = Describable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *Describable) MarshalJSON() ([]byte, error) { - type embed Describable - var marshaler = struct { - embed - }{ - embed: embed(*d), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (d *Describable) String() string { - if d == nil { - return "" - } - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -var ( - detailedOrgFieldMetadata = big.NewInt(1 << 0) -) - -type DetailedOrg struct { - Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { - if d == nil { - return nil - } - return d.Metadata -} - -func (d *DetailedOrg) GetExtraProperties() map[string]interface{} { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrg) require(field *big.Int) { - if d.explicitFields == nil { - d.explicitFields = big.NewInt(0) - } - d.explicitFields.Or(d.explicitFields, field) -} - -// SetMetadata sets the Metadata field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *DetailedOrg) SetMetadata(metadata *DetailedOrgMetadata) { - d.Metadata = metadata - d.require(detailedOrgFieldMetadata) -} - -func (d *DetailedOrg) UnmarshalJSON(data []byte) error { - type unmarshaler DetailedOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrg) MarshalJSON() ([]byte, error) { - type embed DetailedOrg - var marshaler = struct { - embed - }{ - embed: embed(*d), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (d *DetailedOrg) String() string { - if d == nil { - return "" - } - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -var ( - detailedOrgMetadataFieldRegion = big.NewInt(1 << 0) - detailedOrgMetadataFieldDomain = big.NewInt(1 << 1) -) - -type DetailedOrgMetadata struct { - // Deployment region from DetailedOrg. - Region string `json:"region" url:"region"` - // Custom domain name. - Domain *string `json:"domain,omitempty" url:"domain,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (d *DetailedOrgMetadata) GetRegion() string { - if d == nil { - return "" - } - return d.Region -} - -func (d *DetailedOrgMetadata) GetDomain() *string { - if d == nil { - return nil - } - return d.Domain -} - -func (d *DetailedOrgMetadata) GetExtraProperties() map[string]interface{} { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrgMetadata) require(field *big.Int) { - if d.explicitFields == nil { - d.explicitFields = big.NewInt(0) - } - d.explicitFields.Or(d.explicitFields, field) -} - -// SetRegion sets the Region field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *DetailedOrgMetadata) SetRegion(region string) { - d.Region = region - d.require(detailedOrgMetadataFieldRegion) -} - -// SetDomain sets the Domain field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *DetailedOrgMetadata) SetDomain(domain *string) { - d.Domain = domain - d.require(detailedOrgMetadataFieldDomain) -} - -func (d *DetailedOrgMetadata) UnmarshalJSON(data []byte) error { - type unmarshaler DetailedOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrgMetadata) MarshalJSON() ([]byte, error) { - type embed DetailedOrgMetadata - var marshaler = struct { - embed - }{ - embed: embed(*d), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (d *DetailedOrgMetadata) String() string { - if d == nil { - return "" - } - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -var ( - identifiableFieldID = big.NewInt(1 << 0) - identifiableFieldName = big.NewInt(1 << 1) -) - -type Identifiable struct { - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Identifiable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (i *Identifiable) GetID() string { - if i == nil { - return "" - } - return i.ID -} - -func (i *Identifiable) GetName() *string { - if i == nil { - return nil - } - return i.Name -} - -func (i *Identifiable) GetExtraProperties() map[string]interface{} { - if i == nil { - return nil - } - return i.extraProperties -} - -func (i *Identifiable) require(field *big.Int) { - if i.explicitFields == nil { - i.explicitFields = big.NewInt(0) - } - i.explicitFields.Or(i.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (i *Identifiable) SetID(id string) { - i.ID = id - i.require(identifiableFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (i *Identifiable) SetName(name *string) { - i.Name = name - i.require(identifiableFieldName) -} - -func (i *Identifiable) UnmarshalJSON(data []byte) error { - type unmarshaler Identifiable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *i = Identifiable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *i) - if err != nil { - return err - } - i.extraProperties = extraProperties - i.rawJSON = json.RawMessage(data) - return nil -} - -func (i *Identifiable) MarshalJSON() ([]byte, error) { - type embed Identifiable - var marshaler = struct { - embed - }{ - embed: embed(*i), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, i.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (i *Identifiable) String() string { - if i == nil { - return "" - } - if len(i.rawJSON) > 0 { - if value, err := internal.StringifyJSON(i.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(i); err == nil { - return value - } - return fmt.Sprintf("%#v", i) -} - -var ( - organizationFieldID = big.NewInt(1 << 0) - organizationFieldMetadata = big.NewInt(1 << 1) - organizationFieldName = big.NewInt(1 << 2) -) - -type Organization struct { - ID string `json:"id" url:"id"` - Metadata *OrganizationMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - Name string `json:"name" url:"name"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (o *Organization) GetID() string { - if o == nil { - return "" - } - return o.ID -} - -func (o *Organization) GetMetadata() *OrganizationMetadata { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *Organization) GetName() string { - if o == nil { - return "" - } - return o.Name -} - -func (o *Organization) GetExtraProperties() map[string]interface{} { - if o == nil { - return nil - } - return o.extraProperties -} - -func (o *Organization) require(field *big.Int) { - if o.explicitFields == nil { - o.explicitFields = big.NewInt(0) - } - o.explicitFields.Or(o.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *Organization) SetID(id string) { - o.ID = id - o.require(organizationFieldID) -} - -// SetMetadata sets the Metadata field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *Organization) SetMetadata(metadata *OrganizationMetadata) { - o.Metadata = metadata - o.require(organizationFieldMetadata) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *Organization) SetName(name string) { - o.Name = name - o.require(organizationFieldName) -} - -func (o *Organization) UnmarshalJSON(data []byte) error { - type unmarshaler Organization - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *o = Organization(value) - extraProperties, err := internal.ExtractExtraProperties(data, *o) - if err != nil { - return err - } - o.extraProperties = extraProperties - o.rawJSON = json.RawMessage(data) - return nil -} - -func (o *Organization) MarshalJSON() ([]byte, error) { - type embed Organization - var marshaler = struct { - embed - }{ - embed: embed(*o), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, o.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (o *Organization) String() string { - if o == nil { - return "" - } - if len(o.rawJSON) > 0 { - if value, err := internal.StringifyJSON(o.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(o); err == nil { - return value - } - return fmt.Sprintf("%#v", o) -} - -var ( - organizationMetadataFieldRegion = big.NewInt(1 << 0) - organizationMetadataFieldDomain = big.NewInt(1 << 1) -) - -type OrganizationMetadata struct { - // Deployment region from DetailedOrg. - Region string `json:"region" url:"region"` - // Custom domain name. - Domain *string `json:"domain,omitempty" url:"domain,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (o *OrganizationMetadata) GetRegion() string { - if o == nil { - return "" - } - return o.Region -} - -func (o *OrganizationMetadata) GetDomain() *string { - if o == nil { - return nil - } - return o.Domain -} - -func (o *OrganizationMetadata) GetExtraProperties() map[string]interface{} { - if o == nil { - return nil - } - return o.extraProperties -} - -func (o *OrganizationMetadata) require(field *big.Int) { - if o.explicitFields == nil { - o.explicitFields = big.NewInt(0) - } - o.explicitFields.Or(o.explicitFields, field) -} - -// SetRegion sets the Region field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *OrganizationMetadata) SetRegion(region string) { - o.Region = region - o.require(organizationMetadataFieldRegion) -} - -// SetDomain sets the Domain field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *OrganizationMetadata) SetDomain(domain *string) { - o.Domain = domain - o.require(organizationMetadataFieldDomain) -} - -func (o *OrganizationMetadata) UnmarshalJSON(data []byte) error { - type unmarshaler OrganizationMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *o = OrganizationMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *o) - if err != nil { - return err - } - o.extraProperties = extraProperties - o.rawJSON = json.RawMessage(data) - return nil -} - -func (o *OrganizationMetadata) MarshalJSON() ([]byte, error) { - type embed OrganizationMetadata - var marshaler = struct { - embed - }{ - embed: embed(*o), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, o.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (o *OrganizationMetadata) String() string { - if o == nil { - return "" - } - if len(o.rawJSON) > 0 { - if value, err := internal.StringifyJSON(o.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(o); err == nil { - return value - } - return fmt.Sprintf("%#v", o) -} - -var ( - paginatedResultFieldPaging = big.NewInt(1 << 0) - paginatedResultFieldResults = big.NewInt(1 << 1) -) - -type PaginatedResult struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []any `json:"results" url:"results"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (p *PaginatedResult) GetPaging() *PagingCursors { - if p == nil { - return nil - } - return p.Paging -} - -func (p *PaginatedResult) GetResults() []any { - if p == nil { - return nil - } - return p.Results -} - -func (p *PaginatedResult) GetExtraProperties() map[string]interface{} { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PaginatedResult) require(field *big.Int) { - if p.explicitFields == nil { - p.explicitFields = big.NewInt(0) - } - p.explicitFields.Or(p.explicitFields, field) -} - -// SetPaging sets the Paging field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PaginatedResult) SetPaging(paging *PagingCursors) { - p.Paging = paging - p.require(paginatedResultFieldPaging) -} - -// SetResults sets the Results field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PaginatedResult) SetResults(results []any) { - p.Results = results - p.require(paginatedResultFieldResults) -} - -func (p *PaginatedResult) UnmarshalJSON(data []byte) error { - type unmarshaler PaginatedResult - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PaginatedResult(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PaginatedResult) MarshalJSON() ([]byte, error) { - type embed PaginatedResult - var marshaler = struct { - embed - }{ - embed: embed(*p), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (p *PaginatedResult) String() string { - if p == nil { - return "" - } - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -var ( - pagingCursorsFieldNext = big.NewInt(1 << 0) - pagingCursorsFieldPrevious = big.NewInt(1 << 1) -) - -type PagingCursors struct { - // Cursor for the next page of results. - Next string `json:"next" url:"next"` - // Cursor for the previous page of results. - Previous *string `json:"previous,omitempty" url:"previous,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (p *PagingCursors) GetNext() string { - if p == nil { - return "" - } - return p.Next -} - -func (p *PagingCursors) GetPrevious() *string { - if p == nil { - return nil - } - return p.Previous -} - -func (p *PagingCursors) GetExtraProperties() map[string]interface{} { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PagingCursors) require(field *big.Int) { - if p.explicitFields == nil { - p.explicitFields = big.NewInt(0) - } - p.explicitFields.Or(p.explicitFields, field) -} - -// SetNext sets the Next field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PagingCursors) SetNext(next string) { - p.Next = next - p.require(pagingCursorsFieldNext) -} - -// SetPrevious sets the Previous field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PagingCursors) SetPrevious(previous *string) { - p.Previous = previous - p.require(pagingCursorsFieldPrevious) -} - -func (p *PagingCursors) UnmarshalJSON(data []byte) error { - type unmarshaler PagingCursors - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PagingCursors(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PagingCursors) MarshalJSON() ([]byte, error) { - type embed PagingCursors - var marshaler = struct { - embed - }{ - embed: embed(*p), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (p *PagingCursors) String() string { - if p == nil { - return "" - } - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -// Execution environment for a rule. -type RuleExecutionContext string - -const ( - RuleExecutionContextProd RuleExecutionContext = "prod" - RuleExecutionContextStaging RuleExecutionContext = "staging" - RuleExecutionContextDev RuleExecutionContext = "dev" -) - -func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { - switch s { - case "prod": - return RuleExecutionContextProd, nil - case "staging": - return RuleExecutionContextStaging, nil - case "dev": - return RuleExecutionContextDev, nil - } - var t RuleExecutionContext - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleExecutionContext) Ptr() *RuleExecutionContext { - return &r -} - -var ( - ruleResponseFieldCreatedBy = big.NewInt(1 << 0) - ruleResponseFieldCreatedDateTime = big.NewInt(1 << 1) - ruleResponseFieldModifiedBy = big.NewInt(1 << 2) - ruleResponseFieldModifiedDateTime = big.NewInt(1 << 3) - ruleResponseFieldID = big.NewInt(1 << 4) - ruleResponseFieldName = big.NewInt(1 << 5) - ruleResponseFieldStatus = big.NewInt(1 << 6) - ruleResponseFieldExecutionContext = big.NewInt(1 << 7) -) - -type RuleResponse struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Status RuleResponseStatus `json:"status" url:"status"` - ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (r *RuleResponse) GetCreatedBy() *string { - if r == nil { - return nil - } - return r.CreatedBy -} - -func (r *RuleResponse) GetCreatedDateTime() *time.Time { - if r == nil { - return nil - } - return r.CreatedDateTime -} - -func (r *RuleResponse) GetModifiedBy() *string { - if r == nil { - return nil - } - return r.ModifiedBy -} - -func (r *RuleResponse) GetModifiedDateTime() *time.Time { - if r == nil { - return nil - } - return r.ModifiedDateTime -} - -func (r *RuleResponse) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleResponse) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleResponse) GetStatus() RuleResponseStatus { - if r == nil { - return "" - } - return r.Status -} - -func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { - if r == nil { - return nil - } - return r.ExecutionContext -} - -func (r *RuleResponse) GetExtraProperties() map[string]interface{} { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleResponse) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetCreatedBy sets the CreatedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetCreatedBy(createdBy *string) { - r.CreatedBy = createdBy - r.require(ruleResponseFieldCreatedBy) -} - -// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetCreatedDateTime(createdDateTime *time.Time) { - r.CreatedDateTime = createdDateTime - r.require(ruleResponseFieldCreatedDateTime) -} - -// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetModifiedBy(modifiedBy *string) { - r.ModifiedBy = modifiedBy - r.require(ruleResponseFieldModifiedBy) -} - -// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetModifiedDateTime(modifiedDateTime *time.Time) { - r.ModifiedDateTime = modifiedDateTime - r.require(ruleResponseFieldModifiedDateTime) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetID(id string) { - r.ID = id - r.require(ruleResponseFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetName(name string) { - r.Name = name - r.require(ruleResponseFieldName) -} - -// SetStatus sets the Status field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetStatus(status RuleResponseStatus) { - r.Status = status - r.require(ruleResponseFieldStatus) -} - -// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetExecutionContext(executionContext *RuleExecutionContext) { - r.ExecutionContext = executionContext - r.require(ruleResponseFieldExecutionContext) -} - -func (r *RuleResponse) UnmarshalJSON(data []byte) error { - type embed RuleResponse - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*r), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *r = RuleResponse(unmarshaler.embed) - r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleResponse) MarshalJSON() ([]byte, error) { - type embed RuleResponse - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*r), - CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (r *RuleResponse) String() string { - if r == nil { - return "" - } - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type RuleResponseStatus string - -const ( - RuleResponseStatusActive RuleResponseStatus = "active" - RuleResponseStatusInactive RuleResponseStatus = "inactive" - RuleResponseStatusDraft RuleResponseStatus = "draft" -) - -func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { - switch s { - case "active": - return RuleResponseStatusActive, nil - case "inactive": - return RuleResponseStatusInactive, nil - case "draft": - return RuleResponseStatusDraft, nil - } - var t RuleResponseStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleResponseStatus) Ptr() *RuleResponseStatus { - return &r -} - -var ( - ruleTypeFieldID = big.NewInt(1 << 0) - ruleTypeFieldName = big.NewInt(1 << 1) - ruleTypeFieldDescription = big.NewInt(1 << 2) -) - -type RuleType struct { - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Description *string `json:"description,omitempty" url:"description,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (r *RuleType) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleType) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleType) GetDescription() *string { - if r == nil { - return nil - } - return r.Description -} - -func (r *RuleType) GetExtraProperties() map[string]interface{} { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleType) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleType) SetID(id string) { - r.ID = id - r.require(ruleTypeFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleType) SetName(name string) { - r.Name = name - r.require(ruleTypeFieldName) -} - -// SetDescription sets the Description field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleType) SetDescription(description *string) { - r.Description = description - r.require(ruleTypeFieldDescription) -} - -func (r *RuleType) UnmarshalJSON(data []byte) error { - type unmarshaler RuleType - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleType(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleType) MarshalJSON() ([]byte, error) { - type embed RuleType - var marshaler = struct { - embed - }{ - embed: embed(*r), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (r *RuleType) String() string { - if r == nil { - return "" - } - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -var ( - ruleTypeSearchResponseFieldPaging = big.NewInt(1 << 0) - ruleTypeSearchResponseFieldResults = big.NewInt(1 << 1) -) - -type RuleTypeSearchResponse struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { - if r == nil { - return nil - } - return r.Paging -} - -func (r *RuleTypeSearchResponse) GetResults() []*RuleType { - if r == nil { - return nil - } - return r.Results -} - -func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]interface{} { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleTypeSearchResponse) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetPaging sets the Paging field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleTypeSearchResponse) SetPaging(paging *PagingCursors) { - r.Paging = paging - r.require(ruleTypeSearchResponseFieldPaging) -} - -// SetResults sets the Results field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleTypeSearchResponse) SetResults(results []*RuleType) { - r.Results = results - r.require(ruleTypeSearchResponseFieldResults) -} - -func (r *RuleTypeSearchResponse) UnmarshalJSON(data []byte) error { - type unmarshaler RuleTypeSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleTypeSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleTypeSearchResponse) MarshalJSON() ([]byte, error) { - type embed RuleTypeSearchResponse - var marshaler = struct { - embed - }{ - embed: embed(*r), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (r *RuleTypeSearchResponse) String() string { - if r == nil { - return "" - } - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -var ( - userFieldID = big.NewInt(1 << 0) - userFieldEmail = big.NewInt(1 << 1) -) - -type User struct { - ID string `json:"id" url:"id"` - Email string `json:"email" url:"email"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (u *User) GetID() string { - if u == nil { - return "" - } - return u.ID -} - -func (u *User) GetEmail() string { - if u == nil { - return "" - } - return u.Email -} - -func (u *User) GetExtraProperties() map[string]interface{} { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *User) require(field *big.Int) { - if u.explicitFields == nil { - u.explicitFields = big.NewInt(0) - } - u.explicitFields.Or(u.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *User) SetID(id string) { - u.ID = id - u.require(userFieldID) -} - -// SetEmail sets the Email field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *User) SetEmail(email string) { - u.Email = email - u.require(userFieldEmail) -} - -func (u *User) UnmarshalJSON(data []byte) error { - type unmarshaler User - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = User(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *User) MarshalJSON() ([]byte, error) { - type embed User - var marshaler = struct { - embed - }{ - embed: embed(*u), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (u *User) String() string { - if u == nil { - return "" - } - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} - -var ( - userSearchResponseFieldPaging = big.NewInt(1 << 0) - userSearchResponseFieldResults = big.NewInt(1 << 1) -) - -type UserSearchResponse struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []*User `json:"results,omitempty" url:"results,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (u *UserSearchResponse) GetPaging() *PagingCursors { - if u == nil { - return nil - } - return u.Paging -} - -func (u *UserSearchResponse) GetResults() []*User { - if u == nil { - return nil - } - return u.Results -} - -func (u *UserSearchResponse) GetExtraProperties() map[string]interface{} { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *UserSearchResponse) require(field *big.Int) { - if u.explicitFields == nil { - u.explicitFields = big.NewInt(0) - } - u.explicitFields.Or(u.explicitFields, field) -} - -// SetPaging sets the Paging field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *UserSearchResponse) SetPaging(paging *PagingCursors) { - u.Paging = paging - u.require(userSearchResponseFieldPaging) -} - -// SetResults sets the Results field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *UserSearchResponse) SetResults(results []*User) { - u.Results = results - u.require(userSearchResponseFieldResults) -} - -func (u *UserSearchResponse) UnmarshalJSON(data []byte) error { - type unmarshaler UserSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = UserSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *UserSearchResponse) MarshalJSON() ([]byte, error) { - type embed UserSearchResponse - var marshaler = struct { - embed - }{ - embed: embed(*u), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (u *UserSearchResponse) String() string { - if u == nil { - return "" - } - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} diff --git a/seed/go-sdk/allof-inline/types_test.go b/seed/go-sdk/allof-inline/types_test.go deleted file mode 100644 index 7f39409652b8..000000000000 --- a/seed/go-sdk/allof-inline/types_test.go +++ /dev/null @@ -1,4688 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - json "encoding/json" - assert "github.com/stretchr/testify/assert" - require "github.com/stretchr/testify/require" - testing "testing" - time "time" -) - -func TestSettersRuleCreateRequest(t *testing.T) { - t.Run("SetName", func(t *testing.T) { - obj := &RuleCreateRequest{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetExecutionContext", func(t *testing.T) { - obj := &RuleCreateRequest{} - var fernTestValueExecutionContext RuleExecutionContext - obj.SetExecutionContext(fernTestValueExecutionContext) - assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestSettersMarkExplicitRuleCreateRequest(t *testing.T) { - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleCreateRequest{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleCreateRequest{} - var fernTestValueExecutionContext RuleExecutionContext - - // Act - obj.SetExecutionContext(fernTestValueExecutionContext) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersSearchRuleTypesRequest(t *testing.T) { - t.Run("SetQuery", func(t *testing.T) { - obj := &SearchRuleTypesRequest{} - var fernTestValueQuery *string - obj.SetQuery(fernTestValueQuery) - assert.Equal(t, fernTestValueQuery, obj.Query) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestSettersMarkExplicitSearchRuleTypesRequest(t *testing.T) { - t.Run("SetQuery_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &SearchRuleTypesRequest{} - var fernTestValueQuery *string - - // Act - obj.SetQuery(fernTestValueQuery) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersAuditInfo(t *testing.T) { - t.Run("SetCreatedBy", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueCreatedBy *string - obj.SetCreatedBy(fernTestValueCreatedBy) - assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetCreatedDateTime", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueCreatedDateTime *time.Time - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedBy", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueModifiedBy *string - obj.SetModifiedBy(fernTestValueModifiedBy) - assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedDateTime", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueModifiedDateTime *time.Time - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersAuditInfo(t *testing.T) { - t.Run("GetCreatedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *string - obj.CreatedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") - }) - - t.Run("GetCreatedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.CreatedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedBy() // Should return zero value - }) - - t.Run("GetCreatedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *time.Time - obj.CreatedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") - }) - - t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.CreatedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedDateTime() // Should return zero value - }) - - t.Run("GetModifiedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *string - obj.ModifiedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") - }) - - t.Run("GetModifiedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.ModifiedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedBy() // Should return zero value - }) - - t.Run("GetModifiedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *time.Time - obj.ModifiedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") - }) - - t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.ModifiedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedDateTime() // Should return zero value - }) - -} - -func TestSettersMarkExplicitAuditInfo(t *testing.T) { - t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueCreatedBy *string - - // Act - obj.SetCreatedBy(fernTestValueCreatedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueCreatedDateTime *time.Time - - // Act - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueModifiedBy *string - - // Act - obj.SetModifiedBy(fernTestValueModifiedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueModifiedDateTime *time.Time - - // Act - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersBaseOrg(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &BaseOrg{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetMetadata", func(t *testing.T) { - obj := &BaseOrg{} - var fernTestValueMetadata *BaseOrgMetadata - obj.SetMetadata(fernTestValueMetadata) - assert.Equal(t, fernTestValueMetadata, obj.Metadata) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersBaseOrg(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetMetadata", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var expected *BaseOrgMetadata - obj.Metadata = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") - }) - - t.Run("GetMetadata_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - obj.Metadata = nil - - // Act & Assert - assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") - }) - - t.Run("GetMetadata_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetMetadata() // Should return zero value - }) - -} - -func TestSettersMarkExplicitBaseOrg(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var fernTestValueMetadata *BaseOrgMetadata - - // Act - obj.SetMetadata(fernTestValueMetadata) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersBaseOrgMetadata(t *testing.T) { - t.Run("SetRegion", func(t *testing.T) { - obj := &BaseOrgMetadata{} - var fernTestValueRegion string - obj.SetRegion(fernTestValueRegion) - assert.Equal(t, fernTestValueRegion, obj.Region) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetTier", func(t *testing.T) { - obj := &BaseOrgMetadata{} - var fernTestValueTier *string - obj.SetTier(fernTestValueTier) - assert.Equal(t, fernTestValueTier, obj.Tier) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersBaseOrgMetadata(t *testing.T) { - t.Run("GetRegion", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var expected string - obj.Region = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") - }) - - t.Run("GetRegion_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetRegion() // Should return zero value - }) - - t.Run("GetTier", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var expected *string - obj.Tier = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetTier(), "getter should return the property value") - }) - - t.Run("GetTier_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - obj.Tier = nil - - // Act & Assert - assert.Nil(t, obj.GetTier(), "getter should return nil when property is nil") - }) - - t.Run("GetTier_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetTier() // Should return zero value - }) - -} - -func TestSettersMarkExplicitBaseOrgMetadata(t *testing.T) { - t.Run("SetRegion_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var fernTestValueRegion string - - // Act - obj.SetRegion(fernTestValueRegion) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetTier_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var fernTestValueTier *string - - // Act - obj.SetTier(fernTestValueTier) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersCombinedEntity(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueName *string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetSummary", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueSummary *string - obj.SetSummary(fernTestValueSummary) - assert.Equal(t, fernTestValueSummary, obj.Summary) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetStatus", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueStatus CombinedEntityStatus - obj.SetStatus(fernTestValueStatus) - assert.Equal(t, fernTestValueStatus, obj.Status) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersCombinedEntity(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected *string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - obj.Name = nil - - // Act & Assert - assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetSummary", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected *string - obj.Summary = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") - }) - - t.Run("GetSummary_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - obj.Summary = nil - - // Act & Assert - assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") - }) - - t.Run("GetSummary_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetSummary() // Should return zero value - }) - - t.Run("GetStatus", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected CombinedEntityStatus - obj.Status = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") - }) - - t.Run("GetStatus_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetStatus() // Should return zero value - }) - -} - -func TestSettersMarkExplicitCombinedEntity(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueName *string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetSummary_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueSummary *string - - // Act - obj.SetSummary(fernTestValueSummary) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetStatus_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueStatus CombinedEntityStatus - - // Act - obj.SetStatus(fernTestValueStatus) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersDescribable(t *testing.T) { - t.Run("SetName", func(t *testing.T) { - obj := &Describable{} - var fernTestValueName *string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetSummary", func(t *testing.T) { - obj := &Describable{} - var fernTestValueSummary *string - obj.SetSummary(fernTestValueSummary) - assert.Equal(t, fernTestValueSummary, obj.Summary) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersDescribable(t *testing.T) { - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var expected *string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - obj.Name = nil - - // Act & Assert - assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetSummary", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var expected *string - obj.Summary = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") - }) - - t.Run("GetSummary_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - obj.Summary = nil - - // Act & Assert - assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") - }) - - t.Run("GetSummary_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetSummary() // Should return zero value - }) - -} - -func TestSettersMarkExplicitDescribable(t *testing.T) { - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var fernTestValueName *string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetSummary_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var fernTestValueSummary *string - - // Act - obj.SetSummary(fernTestValueSummary) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersDetailedOrg(t *testing.T) { - t.Run("SetMetadata", func(t *testing.T) { - obj := &DetailedOrg{} - var fernTestValueMetadata *DetailedOrgMetadata - obj.SetMetadata(fernTestValueMetadata) - assert.Equal(t, fernTestValueMetadata, obj.Metadata) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersDetailedOrg(t *testing.T) { - t.Run("GetMetadata", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - var expected *DetailedOrgMetadata - obj.Metadata = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") - }) - - t.Run("GetMetadata_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - obj.Metadata = nil - - // Act & Assert - assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") - }) - - t.Run("GetMetadata_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrg - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetMetadata() // Should return zero value - }) - -} - -func TestSettersMarkExplicitDetailedOrg(t *testing.T) { - t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - var fernTestValueMetadata *DetailedOrgMetadata - - // Act - obj.SetMetadata(fernTestValueMetadata) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersDetailedOrgMetadata(t *testing.T) { - t.Run("SetRegion", func(t *testing.T) { - obj := &DetailedOrgMetadata{} - var fernTestValueRegion string - obj.SetRegion(fernTestValueRegion) - assert.Equal(t, fernTestValueRegion, obj.Region) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetDomain", func(t *testing.T) { - obj := &DetailedOrgMetadata{} - var fernTestValueDomain *string - obj.SetDomain(fernTestValueDomain) - assert.Equal(t, fernTestValueDomain, obj.Domain) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersDetailedOrgMetadata(t *testing.T) { - t.Run("GetRegion", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var expected string - obj.Region = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") - }) - - t.Run("GetRegion_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetRegion() // Should return zero value - }) - - t.Run("GetDomain", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var expected *string - obj.Domain = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetDomain(), "getter should return the property value") - }) - - t.Run("GetDomain_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - obj.Domain = nil - - // Act & Assert - assert.Nil(t, obj.GetDomain(), "getter should return nil when property is nil") - }) - - t.Run("GetDomain_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetDomain() // Should return zero value - }) - -} - -func TestSettersMarkExplicitDetailedOrgMetadata(t *testing.T) { - t.Run("SetRegion_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var fernTestValueRegion string - - // Act - obj.SetRegion(fernTestValueRegion) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetDomain_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var fernTestValueDomain *string - - // Act - obj.SetDomain(fernTestValueDomain) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersIdentifiable(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &Identifiable{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &Identifiable{} - var fernTestValueName *string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersIdentifiable(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var expected *string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - obj.Name = nil - - // Act & Assert - assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - -} - -func TestSettersMarkExplicitIdentifiable(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var fernTestValueName *string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersOrganization(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &Organization{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetMetadata", func(t *testing.T) { - obj := &Organization{} - var fernTestValueMetadata *OrganizationMetadata - obj.SetMetadata(fernTestValueMetadata) - assert.Equal(t, fernTestValueMetadata, obj.Metadata) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &Organization{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersOrganization(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetMetadata", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var expected *OrganizationMetadata - obj.Metadata = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") - }) - - t.Run("GetMetadata_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - obj.Metadata = nil - - // Act & Assert - assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") - }) - - t.Run("GetMetadata_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetMetadata() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var expected string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - -} - -func TestSettersMarkExplicitOrganization(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var fernTestValueMetadata *OrganizationMetadata - - // Act - obj.SetMetadata(fernTestValueMetadata) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersOrganizationMetadata(t *testing.T) { - t.Run("SetRegion", func(t *testing.T) { - obj := &OrganizationMetadata{} - var fernTestValueRegion string - obj.SetRegion(fernTestValueRegion) - assert.Equal(t, fernTestValueRegion, obj.Region) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetDomain", func(t *testing.T) { - obj := &OrganizationMetadata{} - var fernTestValueDomain *string - obj.SetDomain(fernTestValueDomain) - assert.Equal(t, fernTestValueDomain, obj.Domain) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersOrganizationMetadata(t *testing.T) { - t.Run("GetRegion", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &OrganizationMetadata{} - var expected string - obj.Region = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") - }) - - t.Run("GetRegion_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *OrganizationMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetRegion() // Should return zero value - }) - - t.Run("GetDomain", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &OrganizationMetadata{} - var expected *string - obj.Domain = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetDomain(), "getter should return the property value") - }) - - t.Run("GetDomain_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &OrganizationMetadata{} - obj.Domain = nil - - // Act & Assert - assert.Nil(t, obj.GetDomain(), "getter should return nil when property is nil") - }) - - t.Run("GetDomain_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *OrganizationMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetDomain() // Should return zero value - }) - -} - -func TestSettersMarkExplicitOrganizationMetadata(t *testing.T) { - t.Run("SetRegion_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &OrganizationMetadata{} - var fernTestValueRegion string - - // Act - obj.SetRegion(fernTestValueRegion) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetDomain_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &OrganizationMetadata{} - var fernTestValueDomain *string - - // Act - obj.SetDomain(fernTestValueDomain) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersPaginatedResult(t *testing.T) { - t.Run("SetPaging", func(t *testing.T) { - obj := &PaginatedResult{} - var fernTestValuePaging *PagingCursors - obj.SetPaging(fernTestValuePaging) - assert.Equal(t, fernTestValuePaging, obj.Paging) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetResults", func(t *testing.T) { - obj := &PaginatedResult{} - var fernTestValueResults []any - obj.SetResults(fernTestValueResults) - assert.Equal(t, fernTestValueResults, obj.Results) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersPaginatedResult(t *testing.T) { - t.Run("GetPaging", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var expected *PagingCursors - obj.Paging = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") - }) - - t.Run("GetPaging_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - obj.Paging = nil - - // Act & Assert - assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") - }) - - t.Run("GetPaging_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPaging() // Should return zero value - }) - - t.Run("GetResults", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var expected []any - obj.Results = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") - }) - - t.Run("GetResults_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - obj.Results = nil - - // Act & Assert - assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") - }) - - t.Run("GetResults_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetResults() // Should return zero value - }) - -} - -func TestSettersMarkExplicitPaginatedResult(t *testing.T) { - t.Run("SetPaging_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var fernTestValuePaging *PagingCursors - - // Act - obj.SetPaging(fernTestValuePaging) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetResults_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var fernTestValueResults []any - - // Act - obj.SetResults(fernTestValueResults) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersPagingCursors(t *testing.T) { - t.Run("SetNext", func(t *testing.T) { - obj := &PagingCursors{} - var fernTestValueNext string - obj.SetNext(fernTestValueNext) - assert.Equal(t, fernTestValueNext, obj.Next) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetPrevious", func(t *testing.T) { - obj := &PagingCursors{} - var fernTestValuePrevious *string - obj.SetPrevious(fernTestValuePrevious) - assert.Equal(t, fernTestValuePrevious, obj.Previous) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersPagingCursors(t *testing.T) { - t.Run("GetNext", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var expected string - obj.Next = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetNext(), "getter should return the property value") - }) - - t.Run("GetNext_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetNext() // Should return zero value - }) - - t.Run("GetPrevious", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var expected *string - obj.Previous = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPrevious(), "getter should return the property value") - }) - - t.Run("GetPrevious_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - obj.Previous = nil - - // Act & Assert - assert.Nil(t, obj.GetPrevious(), "getter should return nil when property is nil") - }) - - t.Run("GetPrevious_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPrevious() // Should return zero value - }) - -} - -func TestSettersMarkExplicitPagingCursors(t *testing.T) { - t.Run("SetNext_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var fernTestValueNext string - - // Act - obj.SetNext(fernTestValueNext) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetPrevious_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var fernTestValuePrevious *string - - // Act - obj.SetPrevious(fernTestValuePrevious) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersRuleResponse(t *testing.T) { - t.Run("SetCreatedBy", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueCreatedBy *string - obj.SetCreatedBy(fernTestValueCreatedBy) - assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetCreatedDateTime", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueCreatedDateTime *time.Time - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedBy", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueModifiedBy *string - obj.SetModifiedBy(fernTestValueModifiedBy) - assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedDateTime", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueModifiedDateTime *time.Time - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetID", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetStatus", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueStatus RuleResponseStatus - obj.SetStatus(fernTestValueStatus) - assert.Equal(t, fernTestValueStatus, obj.Status) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetExecutionContext", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueExecutionContext *RuleExecutionContext - obj.SetExecutionContext(fernTestValueExecutionContext) - assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersRuleResponse(t *testing.T) { - t.Run("GetCreatedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *string - obj.CreatedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") - }) - - t.Run("GetCreatedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.CreatedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedBy() // Should return zero value - }) - - t.Run("GetCreatedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *time.Time - obj.CreatedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") - }) - - t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.CreatedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedDateTime() // Should return zero value - }) - - t.Run("GetModifiedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *string - obj.ModifiedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") - }) - - t.Run("GetModifiedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.ModifiedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedBy() // Should return zero value - }) - - t.Run("GetModifiedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *time.Time - obj.ModifiedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") - }) - - t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.ModifiedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedDateTime() // Should return zero value - }) - - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetStatus", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected RuleResponseStatus - obj.Status = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") - }) - - t.Run("GetStatus_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetStatus() // Should return zero value - }) - - t.Run("GetExecutionContext", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *RuleExecutionContext - obj.ExecutionContext = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetExecutionContext(), "getter should return the property value") - }) - - t.Run("GetExecutionContext_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.ExecutionContext = nil - - // Act & Assert - assert.Nil(t, obj.GetExecutionContext(), "getter should return nil when property is nil") - }) - - t.Run("GetExecutionContext_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetExecutionContext() // Should return zero value - }) - -} - -func TestSettersMarkExplicitRuleResponse(t *testing.T) { - t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueCreatedBy *string - - // Act - obj.SetCreatedBy(fernTestValueCreatedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueCreatedDateTime *time.Time - - // Act - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueModifiedBy *string - - // Act - obj.SetModifiedBy(fernTestValueModifiedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueModifiedDateTime *time.Time - - // Act - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetStatus_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueStatus RuleResponseStatus - - // Act - obj.SetStatus(fernTestValueStatus) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueExecutionContext *RuleExecutionContext - - // Act - obj.SetExecutionContext(fernTestValueExecutionContext) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersRuleType(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &RuleType{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &RuleType{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetDescription", func(t *testing.T) { - obj := &RuleType{} - var fernTestValueDescription *string - obj.SetDescription(fernTestValueDescription) - assert.Equal(t, fernTestValueDescription, obj.Description) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersRuleType(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var expected string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetDescription", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var expected *string - obj.Description = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetDescription(), "getter should return the property value") - }) - - t.Run("GetDescription_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - obj.Description = nil - - // Act & Assert - assert.Nil(t, obj.GetDescription(), "getter should return nil when property is nil") - }) - - t.Run("GetDescription_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetDescription() // Should return zero value - }) - -} - -func TestSettersMarkExplicitRuleType(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetDescription_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var fernTestValueDescription *string - - // Act - obj.SetDescription(fernTestValueDescription) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersRuleTypeSearchResponse(t *testing.T) { - t.Run("SetPaging", func(t *testing.T) { - obj := &RuleTypeSearchResponse{} - var fernTestValuePaging *PagingCursors - obj.SetPaging(fernTestValuePaging) - assert.Equal(t, fernTestValuePaging, obj.Paging) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetResults", func(t *testing.T) { - obj := &RuleTypeSearchResponse{} - var fernTestValueResults []*RuleType - obj.SetResults(fernTestValueResults) - assert.Equal(t, fernTestValueResults, obj.Results) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersRuleTypeSearchResponse(t *testing.T) { - t.Run("GetPaging", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var expected *PagingCursors - obj.Paging = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") - }) - - t.Run("GetPaging_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - obj.Paging = nil - - // Act & Assert - assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") - }) - - t.Run("GetPaging_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPaging() // Should return zero value - }) - - t.Run("GetResults", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var expected []*RuleType - obj.Results = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") - }) - - t.Run("GetResults_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - obj.Results = nil - - // Act & Assert - assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") - }) - - t.Run("GetResults_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetResults() // Should return zero value - }) - -} - -func TestSettersMarkExplicitRuleTypeSearchResponse(t *testing.T) { - t.Run("SetPaging_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var fernTestValuePaging *PagingCursors - - // Act - obj.SetPaging(fernTestValuePaging) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetResults_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var fernTestValueResults []*RuleType - - // Act - obj.SetResults(fernTestValueResults) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersUser(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &User{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetEmail", func(t *testing.T) { - obj := &User{} - var fernTestValueEmail string - obj.SetEmail(fernTestValueEmail) - assert.Equal(t, fernTestValueEmail, obj.Email) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersUser(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetEmail", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var expected string - obj.Email = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetEmail(), "getter should return the property value") - }) - - t.Run("GetEmail_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetEmail() // Should return zero value - }) - -} - -func TestSettersMarkExplicitUser(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetEmail_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var fernTestValueEmail string - - // Act - obj.SetEmail(fernTestValueEmail) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersUserSearchResponse(t *testing.T) { - t.Run("SetPaging", func(t *testing.T) { - obj := &UserSearchResponse{} - var fernTestValuePaging *PagingCursors - obj.SetPaging(fernTestValuePaging) - assert.Equal(t, fernTestValuePaging, obj.Paging) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetResults", func(t *testing.T) { - obj := &UserSearchResponse{} - var fernTestValueResults []*User - obj.SetResults(fernTestValueResults) - assert.Equal(t, fernTestValueResults, obj.Results) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersUserSearchResponse(t *testing.T) { - t.Run("GetPaging", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var expected *PagingCursors - obj.Paging = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") - }) - - t.Run("GetPaging_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - obj.Paging = nil - - // Act & Assert - assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") - }) - - t.Run("GetPaging_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPaging() // Should return zero value - }) - - t.Run("GetResults", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var expected []*User - obj.Results = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") - }) - - t.Run("GetResults_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - obj.Results = nil - - // Act & Assert - assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") - }) - - t.Run("GetResults_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetResults() // Should return zero value - }) - -} - -func TestSettersMarkExplicitUserSearchResponse(t *testing.T) { - t.Run("SetPaging_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var fernTestValuePaging *PagingCursors - - // Act - obj.SetPaging(fernTestValuePaging) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetResults_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var fernTestValueResults []*User - - // Act - obj.SetResults(fernTestValueResults) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestJSONMarshalingAuditInfo(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled AuditInfo - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj AuditInfo - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj AuditInfo - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingBaseOrg(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled BaseOrg - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj BaseOrg - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj BaseOrg - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingBaseOrgMetadata(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled BaseOrgMetadata - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj BaseOrgMetadata - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj BaseOrgMetadata - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingCombinedEntity(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled CombinedEntity - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj CombinedEntity - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj CombinedEntity - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingDescribable(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled Describable - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj Describable - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj Describable - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingDetailedOrg(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled DetailedOrg - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj DetailedOrg - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj DetailedOrg - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingDetailedOrgMetadata(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled DetailedOrgMetadata - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj DetailedOrgMetadata - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj DetailedOrgMetadata - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingIdentifiable(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled Identifiable - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj Identifiable - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj Identifiable - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingOrganization(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled Organization - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj Organization - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj Organization - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingOrganizationMetadata(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &OrganizationMetadata{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled OrganizationMetadata - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj OrganizationMetadata - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj OrganizationMetadata - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingPaginatedResult(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled PaginatedResult - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj PaginatedResult - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj PaginatedResult - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingPagingCursors(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled PagingCursors - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj PagingCursors - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj PagingCursors - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingRuleResponse(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled RuleResponse - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj RuleResponse - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj RuleResponse - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingRuleType(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled RuleType - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj RuleType - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj RuleType - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingRuleTypeSearchResponse(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled RuleTypeSearchResponse - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj RuleTypeSearchResponse - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj RuleTypeSearchResponse - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingUser(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled User - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj User - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj User - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingUserSearchResponse(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled UserSearchResponse - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj UserSearchResponse - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj UserSearchResponse - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestStringAuditInfo(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &AuditInfo{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringBaseOrg(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &BaseOrg{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringBaseOrgMetadata(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &BaseOrgMetadata{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringCombinedEntity(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &CombinedEntity{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringDescribable(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &Describable{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringDetailedOrg(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrg{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrg - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringDetailedOrgMetadata(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrgMetadata{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringIdentifiable(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &Identifiable{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringOrganization(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &Organization{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringOrganizationMetadata(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &OrganizationMetadata{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *OrganizationMetadata - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringPaginatedResult(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &PaginatedResult{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringPagingCursors(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &PagingCursors{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringRuleResponse(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &RuleResponse{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringRuleType(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &RuleType{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringRuleTypeSearchResponse(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &RuleTypeSearchResponse{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringUser(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &User{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringUserSearchResponse(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &UserSearchResponse{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestEnumCombinedEntityStatus(t *testing.T) { - t.Run("NewFromString_active", func(t *testing.T) { - t.Parallel() - val, err := NewCombinedEntityStatusFromString("active") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, CombinedEntityStatus("active"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_archived", func(t *testing.T) { - t.Parallel() - val, err := NewCombinedEntityStatusFromString("archived") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, CombinedEntityStatus("archived"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_Invalid", func(t *testing.T) { - _, err := NewCombinedEntityStatusFromString("invalid_value_that_does_not_exist") - assert.Error(t, err) - }) - - t.Run("Ptr", func(t *testing.T) { - val, err := NewCombinedEntityStatusFromString("active") - assert.NoError(t, err) - ptr := val.Ptr() - assert.NotNil(t, ptr) - assert.Equal(t, val, *ptr) - }) -} - -func TestEnumRuleExecutionContext(t *testing.T) { - t.Run("NewFromString_prod", func(t *testing.T) { - t.Parallel() - val, err := NewRuleExecutionContextFromString("prod") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleExecutionContext("prod"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_staging", func(t *testing.T) { - t.Parallel() - val, err := NewRuleExecutionContextFromString("staging") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleExecutionContext("staging"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_dev", func(t *testing.T) { - t.Parallel() - val, err := NewRuleExecutionContextFromString("dev") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleExecutionContext("dev"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_Invalid", func(t *testing.T) { - _, err := NewRuleExecutionContextFromString("invalid_value_that_does_not_exist") - assert.Error(t, err) - }) - - t.Run("Ptr", func(t *testing.T) { - val, err := NewRuleExecutionContextFromString("prod") - assert.NoError(t, err) - ptr := val.Ptr() - assert.NotNil(t, ptr) - assert.Equal(t, val, *ptr) - }) -} - -func TestEnumRuleResponseStatus(t *testing.T) { - t.Run("NewFromString_active", func(t *testing.T) { - t.Parallel() - val, err := NewRuleResponseStatusFromString("active") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleResponseStatus("active"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_inactive", func(t *testing.T) { - t.Parallel() - val, err := NewRuleResponseStatusFromString("inactive") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleResponseStatus("inactive"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_draft", func(t *testing.T) { - t.Parallel() - val, err := NewRuleResponseStatusFromString("draft") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleResponseStatus("draft"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_Invalid", func(t *testing.T) { - _, err := NewRuleResponseStatusFromString("invalid_value_that_does_not_exist") - assert.Error(t, err) - }) - - t.Run("Ptr", func(t *testing.T) { - val, err := NewRuleResponseStatusFromString("active") - assert.NoError(t, err) - ptr := val.Ptr() - assert.NotNil(t, ptr) - assert.Equal(t, val, *ptr) - }) -} - -func TestExtraPropertiesAuditInfo(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &AuditInfo{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesBaseOrg(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &BaseOrg{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesBaseOrgMetadata(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &BaseOrgMetadata{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesCombinedEntity(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &CombinedEntity{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesDescribable(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &Describable{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesDetailedOrg(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrg{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrg - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesDetailedOrgMetadata(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrgMetadata{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesIdentifiable(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &Identifiable{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesOrganization(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &Organization{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesOrganizationMetadata(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &OrganizationMetadata{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *OrganizationMetadata - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesPaginatedResult(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &PaginatedResult{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesPagingCursors(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &PagingCursors{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesRuleResponse(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &RuleResponse{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesRuleType(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &RuleType{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesRuleTypeSearchResponse(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &RuleTypeSearchResponse{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesUser(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &User{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesUserSearchResponse(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &UserSearchResponse{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} diff --git a/seed/go-sdk/allof/.fern/metadata.json b/seed/go-sdk/allof/.fern/metadata.json deleted file mode 100644 index 711224a2aaf7..000000000000 --- a/seed/go-sdk/allof/.fern/metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-go-sdk", - "generatorVersion": "local", - "generatorConfig": { - "enableWireTests": false - }, - "originGitCommit": "DUMMY", - "sdkVersion": "v0.0.1" -} \ No newline at end of file diff --git a/seed/go-sdk/allof/.github/workflows/ci.yml b/seed/go-sdk/allof/.github/workflows/ci.yml deleted file mode 100644 index 1097e6a18acc..000000000000 --- a/seed/go-sdk/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up go - uses: actions/setup-go@v4 - - - name: Compile - run: go build ./... - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up go - uses: actions/setup-go@v4 - - - name: Lint - uses: golangci/golangci-lint-action@v9 - with: - version: v2.10.1 - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up go - uses: actions/setup-go@v4 - - - name: Setup wiremock server - run: | - PROJECT_NAME="wiremock-$(basename $(dirname $(pwd)) | tr -d '.')" - echo "PROJECT_NAME=$PROJECT_NAME" >> $GITHUB_ENV - if [ -f wiremock/docker-compose.test.yml ]; then - docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down - docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml up -d - WIREMOCK_PORT=$(docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml port wiremock 8080 | cut -d: -f2) - echo "WIREMOCK_URL=http://localhost:$WIREMOCK_PORT" >> $GITHUB_ENV - fi - - - name: Test - run: go test ./... - - - name: Teardown wiremock server - if: always() - run: | - if [ -f wiremock/docker-compose.test.yml ]; then - docker compose -p "$PROJECT_NAME" -f wiremock/docker-compose.test.yml down - fi diff --git a/seed/go-sdk/allof/README.md b/seed/go-sdk/allof/README.md deleted file mode 100644 index ee34317a346e..000000000000 --- a/seed/go-sdk/allof/README.md +++ /dev/null @@ -1,195 +0,0 @@ -# Seed Go Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FGo) - -The Seed Go library provides convenient access to the Seed APIs from Go. - -## Table of Contents - -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Request Options](#request-options) -- [Advanced](#advanced) - - [Response Headers](#response-headers) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Explicit Null](#explicit-null) -- [Contributing](#contributing) - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```go -package example - -import ( - context "context" - - fern "github.com/allof/fern" - client "github.com/allof/fern/client" -) - -func do() { - client := client.NewClient() - request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } - client.CreateRule( - context.TODO(), - request, - ) -} -``` - -## Environments - -You can choose between different environments by using the `option.WithBaseURL` option. You can configure any arbitrary base -URL, which is particularly useful in test environments. - -```go -client := client.NewClient( - option.WithBaseURL(api.Environments.Default), -) -``` - -## Errors - -Structured error types are returned from API calls that return non-success status codes. These errors are compatible -with the `errors.Is` and `errors.As` APIs, so you can access the error like so: - -```go -response, err := client.CreateRule(...) -if err != nil { - var apiError *core.APIError - if errors.As(err, apiError) { - // Do something with the API error ... - } - return err -} -``` - -## Request Options - -A variety of request options are included to adapt the behavior of the library, which includes configuring -authorization tokens, or providing your own instrumented `*http.Client`. - -These request options can either be -specified on the client so that they're applied on every request, or for an individual request, like so: - -> Providing your own `*http.Client` is recommended. Otherwise, the `http.DefaultClient` will be used, -> and your client will wait indefinitely for a response (unless the per-request, context-based timeout -> is used). - -```go -// Specify default options applied on every request. -client := client.NewClient( - option.WithToken(""), - option.WithHTTPClient( - &http.Client{ - Timeout: 5 * time.Second, - }, - ), -) - -// Specify options for an individual request. -response, err := client.CreateRule( - ..., - option.WithToken(""), -) -``` - -## Advanced - -### Response Headers - -You can access the raw HTTP response data by using the `WithRawResponse` field on the client. This is useful -when you need to examine the response headers received from the API call. (When the endpoint is paginated, -the raw HTTP response data will be included automatically in the Page response object.) - -```go -response, err := client.WithRawResponse.CreateRule(...) -if err != nil { - return err -} -fmt.Printf("Got response headers: %v", response.Header) -fmt.Printf("Got status code: %d", response.StatusCode) -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -If the `Retry-After` header is present in the response, the SDK will prioritize respecting its value exactly -over the default exponential backoff. - -Use the `option.WithMaxAttempts` option to configure this behavior for the entire client or an individual request: - -```go -client := client.NewClient( - option.WithMaxAttempts(1), -) - -response, err := client.CreateRule( - ..., - option.WithMaxAttempts(1), -) -``` - -### Timeouts - -Setting a timeout for each individual request is as simple as using the standard context library. Setting a one second timeout for an individual API call looks like the following: - -```go -ctx, cancel := context.WithTimeout(ctx, time.Second) -defer cancel() - -response, err := client.CreateRule(ctx, ...) -``` - -### Explicit Null - -If you want to send the explicit `null` JSON value through an optional parameter, you can use the setters\ -that come with every object. Calling a setter method for a property will flip a bit in the `explicitFields` -bitfield for that setter's object; during serialization, any property with a flipped bit will have its -omittable status stripped, so zero or `nil` values will be sent explicitly rather than omitted altogether: - -```go -type ExampleRequest struct { - // An optional string parameter. - Name *string `json:"name,omitempty" url:"-"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` -} - -request := &ExampleRequest{} -request.SetName(nil) - -response, err := client.CreateRule(ctx, request, ...) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/go-sdk/allof/client/client.go b/seed/go-sdk/allof/client/client.go deleted file mode 100644 index 9d6bf76337ab..000000000000 --- a/seed/go-sdk/allof/client/client.go +++ /dev/null @@ -1,109 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package client - -import ( - context "context" - - fern "github.com/allof/fern" - core "github.com/allof/fern/core" - internal "github.com/allof/fern/internal" - option "github.com/allof/fern/option" -) - -type Client struct { - WithRawResponse *RawClient - - options *core.RequestOptions - baseURL string - caller *internal.Caller -} - -func NewClient(opts ...option.RequestOption) *Client { - options := core.NewRequestOptions(opts...) - return &Client{ - WithRawResponse: NewRawClient(options), - options: options, - baseURL: options.BaseURL, - caller: internal.NewCaller( - &internal.CallerParams{ - Client: options.HTTPClient, - MaxAttempts: options.MaxAttempts, - }, - ), - } -} - -func (c *Client) SearchRuleTypes( - ctx context.Context, - request *fern.SearchRuleTypesRequest, - opts ...option.RequestOption, -) (*fern.RuleTypeSearchResponse, error) { - response, err := c.WithRawResponse.SearchRuleTypes( - ctx, - request, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) CreateRule( - ctx context.Context, - request *fern.RuleCreateRequest, - opts ...option.RequestOption, -) (*fern.RuleResponse, error) { - response, err := c.WithRawResponse.CreateRule( - ctx, - request, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) ListUsers( - ctx context.Context, - opts ...option.RequestOption, -) (*fern.UserSearchResponse, error) { - response, err := c.WithRawResponse.ListUsers( - ctx, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) GetEntity( - ctx context.Context, - opts ...option.RequestOption, -) (*fern.CombinedEntity, error) { - response, err := c.WithRawResponse.GetEntity( - ctx, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} - -func (c *Client) GetOrganization( - ctx context.Context, - opts ...option.RequestOption, -) (*fern.Organization, error) { - response, err := c.WithRawResponse.GetOrganization( - ctx, - opts..., - ) - if err != nil { - return nil, err - } - return response.Body, nil -} diff --git a/seed/go-sdk/allof/client/client_test.go b/seed/go-sdk/allof/client/client_test.go deleted file mode 100644 index 191355fede9a..000000000000 --- a/seed/go-sdk/allof/client/client_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package client - -import ( - option "github.com/allof/fern/option" - assert "github.com/stretchr/testify/assert" - http "net/http" - testing "testing" - time "time" -) - -func TestNewClient(t *testing.T) { - t.Run("default", func(t *testing.T) { - c := NewClient() - assert.Empty(t, c.baseURL) - }) - - t.Run("base url", func(t *testing.T) { - c := NewClient( - option.WithBaseURL("test.co"), - ) - assert.Equal(t, "test.co", c.baseURL) - }) - - t.Run("http client", func(t *testing.T) { - httpClient := &http.Client{ - Timeout: 5 * time.Second, - } - c := NewClient( - option.WithHTTPClient(httpClient), - ) - assert.Empty(t, c.baseURL) - }) - - t.Run("http header", func(t *testing.T) { - header := make(http.Header) - header.Set("X-API-Tenancy", "test") - c := NewClient( - option.WithHTTPHeader(header), - ) - assert.Empty(t, c.baseURL) - assert.Equal(t, "test", c.options.HTTPHeader.Get("X-API-Tenancy")) - }) -} diff --git a/seed/go-sdk/allof/client/raw_client.go b/seed/go-sdk/allof/client/raw_client.go deleted file mode 100644 index c340ae08f80d..000000000000 --- a/seed/go-sdk/allof/client/raw_client.go +++ /dev/null @@ -1,238 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package client - -import ( - context "context" - http "net/http" - - fern "github.com/allof/fern" - core "github.com/allof/fern/core" - internal "github.com/allof/fern/internal" - option "github.com/allof/fern/option" -) - -type RawClient struct { - baseURL string - caller *internal.Caller - options *core.RequestOptions -} - -func NewRawClient(options *core.RequestOptions) *RawClient { - return &RawClient{ - options: options, - baseURL: options.BaseURL, - caller: internal.NewCaller( - &internal.CallerParams{ - Client: options.HTTPClient, - MaxAttempts: options.MaxAttempts, - }, - ), - } -} - -func (r *RawClient) SearchRuleTypes( - ctx context.Context, - request *fern.SearchRuleTypesRequest, - opts ...option.RequestOption, -) (*core.Response[*fern.RuleTypeSearchResponse], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/rule-types" - queryParams, err := internal.QueryValues(request) - if err != nil { - return nil, err - } - if len(queryParams) > 0 { - endpointURL += "?" + queryParams.Encode() - } - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.RuleTypeSearchResponse - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.RuleTypeSearchResponse]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) CreateRule( - ctx context.Context, - request *fern.RuleCreateRequest, - opts ...option.RequestOption, -) (*core.Response[*fern.RuleResponse], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/rules" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - headers.Add("Content-Type", "application/json") - var response *fern.RuleResponse - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Request: request, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.RuleResponse]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) ListUsers( - ctx context.Context, - opts ...option.RequestOption, -) (*core.Response[*fern.UserSearchResponse], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/users" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.UserSearchResponse - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.UserSearchResponse]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) GetEntity( - ctx context.Context, - opts ...option.RequestOption, -) (*core.Response[*fern.CombinedEntity], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/entities" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.CombinedEntity - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.CombinedEntity]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} - -func (r *RawClient) GetOrganization( - ctx context.Context, - opts ...option.RequestOption, -) (*core.Response[*fern.Organization], error) { - options := core.NewRequestOptions(opts...) - baseURL := internal.ResolveBaseURL( - options.BaseURL, - r.baseURL, - "https://api.example.com", - ) - endpointURL := baseURL + "/organizations" - headers := internal.MergeHeaders( - r.options.ToHeader(), - options.ToHeader(), - ) - var response *fern.Organization - raw, err := r.caller.Call( - ctx, - &internal.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: headers, - MaxAttempts: options.MaxAttempts, - BodyProperties: options.BodyProperties, - QueryParameters: options.QueryParameters, - Client: options.HTTPClient, - Response: &response, - }, - ) - if err != nil { - return nil, err - } - return &core.Response[*fern.Organization]{ - StatusCode: raw.StatusCode, - Header: raw.Header, - Body: response, - }, nil -} diff --git a/seed/go-sdk/allof/core/api_error.go b/seed/go-sdk/allof/core/api_error.go deleted file mode 100644 index 6168388541b4..000000000000 --- a/seed/go-sdk/allof/core/api_error.go +++ /dev/null @@ -1,47 +0,0 @@ -package core - -import ( - "fmt" - "net/http" -) - -// APIError is a lightweight wrapper around the standard error -// interface that preserves the status code from the RPC, if any. -type APIError struct { - err error - - StatusCode int `json:"-"` - Header http.Header `json:"-"` -} - -// NewAPIError constructs a new API error. -func NewAPIError(statusCode int, header http.Header, err error) *APIError { - return &APIError{ - err: err, - Header: header, - StatusCode: statusCode, - } -} - -// Unwrap returns the underlying error. This also makes the error compatible -// with errors.As and errors.Is. -func (a *APIError) Unwrap() error { - if a == nil { - return nil - } - return a.err -} - -// Error returns the API error's message. -func (a *APIError) Error() string { - if a == nil || (a.err == nil && a.StatusCode == 0) { - return "" - } - if a.err == nil { - return fmt.Sprintf("%d", a.StatusCode) - } - if a.StatusCode == 0 { - return a.err.Error() - } - return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) -} diff --git a/seed/go-sdk/allof/core/http.go b/seed/go-sdk/allof/core/http.go deleted file mode 100644 index 92c435692940..000000000000 --- a/seed/go-sdk/allof/core/http.go +++ /dev/null @@ -1,15 +0,0 @@ -package core - -import "net/http" - -// HTTPClient is an interface for a subset of the *http.Client. -type HTTPClient interface { - Do(*http.Request) (*http.Response, error) -} - -// Response is an HTTP response from an HTTP client. -type Response[T any] struct { - StatusCode int - Header http.Header - Body T -} diff --git a/seed/go-sdk/allof/core/request_option.go b/seed/go-sdk/allof/core/request_option.go deleted file mode 100644 index d675ba244eb8..000000000000 --- a/seed/go-sdk/allof/core/request_option.go +++ /dev/null @@ -1,119 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package core - -import ( - http "net/http" - url "net/url" -) - -// RequestOption adapts the behavior of the client or an individual request. -type RequestOption interface { - applyRequestOptions(*RequestOptions) -} - -// RequestOptions defines all of the possible request options. -// -// This type is primarily used by the generated code and is not meant -// to be used directly; use the option package instead. -type RequestOptions struct { - BaseURL string - HTTPClient HTTPClient - HTTPHeader http.Header - BodyProperties map[string]interface{} - QueryParameters url.Values - MaxAttempts uint - MaxBufSize int -} - -// NewRequestOptions returns a new *RequestOptions value. -// -// This function is primarily used by the generated code and is not meant -// to be used directly; use RequestOption instead. -func NewRequestOptions(opts ...RequestOption) *RequestOptions { - options := &RequestOptions{ - HTTPHeader: make(http.Header), - BodyProperties: make(map[string]interface{}), - QueryParameters: make(url.Values), - } - for _, opt := range opts { - opt.applyRequestOptions(options) - } - return options -} - -// ToHeader maps the configured request options into a http.Header used -// for the request(s). -func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } - -func (r *RequestOptions) cloneHeader() http.Header { - headers := r.HTTPHeader.Clone() - headers.Set("X-Fern-Language", "Go") - headers.Set("X-Fern-SDK-Name", "github.com/allof/fern") - headers.Set("X-Fern-SDK-Version", "v0.0.1") - headers.Set("User-Agent", "github.com/allof/fern/0.0.1") - return headers -} - -// BaseURLOption implements the RequestOption interface. -type BaseURLOption struct { - BaseURL string -} - -func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { - opts.BaseURL = b.BaseURL -} - -// HTTPClientOption implements the RequestOption interface. -type HTTPClientOption struct { - HTTPClient HTTPClient -} - -func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { - opts.HTTPClient = h.HTTPClient -} - -// HTTPHeaderOption implements the RequestOption interface. -type HTTPHeaderOption struct { - HTTPHeader http.Header -} - -func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { - opts.HTTPHeader = h.HTTPHeader -} - -// BodyPropertiesOption implements the RequestOption interface. -type BodyPropertiesOption struct { - BodyProperties map[string]interface{} -} - -func (b *BodyPropertiesOption) applyRequestOptions(opts *RequestOptions) { - opts.BodyProperties = b.BodyProperties -} - -// QueryParametersOption implements the RequestOption interface. -type QueryParametersOption struct { - QueryParameters url.Values -} - -func (q *QueryParametersOption) applyRequestOptions(opts *RequestOptions) { - opts.QueryParameters = q.QueryParameters -} - -// MaxAttemptsOption implements the RequestOption interface. -type MaxAttemptsOption struct { - MaxAttempts uint -} - -func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { - opts.MaxAttempts = m.MaxAttempts -} - -// MaxBufSizeOption implements the RequestOption interface. -type MaxBufSizeOption struct { - MaxBufSize int -} - -func (m *MaxBufSizeOption) applyRequestOptions(opts *RequestOptions) { - opts.MaxBufSize = m.MaxBufSize -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example0/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example0/snippet.go deleted file mode 100644 index 72903cf9a04f..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example0/snippet.go +++ /dev/null @@ -1,22 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof/fern" - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.SearchRuleTypesRequest{} - client.SearchRuleTypes( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example1/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example1/snippet.go deleted file mode 100644 index c255973cd015..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example1/snippet.go +++ /dev/null @@ -1,26 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof/fern" - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.SearchRuleTypesRequest{ - Query: fern.String( - "query", - ), - } - client.SearchRuleTypes( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example2/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example2/snippet.go deleted file mode 100644 index a9823743c596..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example2/snippet.go +++ /dev/null @@ -1,25 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof/fern" - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } - client.CreateRule( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example3/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example3/snippet.go deleted file mode 100644 index a9823743c596..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example3/snippet.go +++ /dev/null @@ -1,25 +0,0 @@ -package example - -import ( - context "context" - - fern "github.com/allof/fern" - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } - client.CreateRule( - context.TODO(), - request, - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example4/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example4/snippet.go deleted file mode 100644 index 8d3e4ead5a0e..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example4/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.ListUsers( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example5/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example5/snippet.go deleted file mode 100644 index 8d3e4ead5a0e..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example5/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.ListUsers( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example6/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example6/snippet.go deleted file mode 100644 index e7666f5aaf1d..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example6/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetEntity( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example7/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example7/snippet.go deleted file mode 100644 index e7666f5aaf1d..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example7/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetEntity( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example8/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example8/snippet.go deleted file mode 100644 index 61385fbcb9bf..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example8/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetOrganization( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof/dynamic-snippets/example9/snippet.go b/seed/go-sdk/allof/dynamic-snippets/example9/snippet.go deleted file mode 100644 index 61385fbcb9bf..000000000000 --- a/seed/go-sdk/allof/dynamic-snippets/example9/snippet.go +++ /dev/null @@ -1,19 +0,0 @@ -package example - -import ( - context "context" - - client "github.com/allof/fern/client" - option "github.com/allof/fern/option" -) - -func do() { - client := client.NewClient( - option.WithBaseURL( - "https://api.fern.com", - ), - ) - client.GetOrganization( - context.TODO(), - ) -} diff --git a/seed/go-sdk/allof/environments.go b/seed/go-sdk/allof/environments.go deleted file mode 100644 index 27941a670e97..000000000000 --- a/seed/go-sdk/allof/environments.go +++ /dev/null @@ -1,13 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -// Environments defines all of the API environments. -// These values can be used with the WithBaseURL -// RequestOption to override the client's default environment, -// if any. -var Environments = struct { - Default string -}{ - Default: "https://api.example.com", -} diff --git a/seed/go-sdk/allof/error_codes.go b/seed/go-sdk/allof/error_codes.go deleted file mode 100644 index d1094c40fab1..000000000000 --- a/seed/go-sdk/allof/error_codes.go +++ /dev/null @@ -1,9 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - internal "github.com/allof/fern/internal" -) - -var ErrorCodes internal.ErrorCodes = internal.ErrorCodes{} diff --git a/seed/go-sdk/allof/file_param.go b/seed/go-sdk/allof/file_param.go deleted file mode 100644 index 737abb97c837..000000000000 --- a/seed/go-sdk/allof/file_param.go +++ /dev/null @@ -1,41 +0,0 @@ -package api - -import ( - "io" -) - -// FileParam is a file type suitable for multipart/form-data uploads. -type FileParam struct { - io.Reader - filename string - contentType string -} - -// FileParamOption adapts the behavior of the FileParam. No options are -// implemented yet, but this interface allows for future extensibility. -type FileParamOption interface { - apply() -} - -// NewFileParam returns a *FileParam type suitable for multipart/form-data uploads. All file -// upload endpoints accept a simple io.Reader, which is usually created by opening a file -// via os.Open. -// -// However, some endpoints require additional metadata about the file such as a specific -// Content-Type or custom filename. FileParam makes it easier to create the correct type -// signature for these endpoints. -func NewFileParam( - reader io.Reader, - filename string, - contentType string, - opts ...FileParamOption, -) *FileParam { - return &FileParam{ - Reader: reader, - filename: filename, - contentType: contentType, - } -} - -func (f *FileParam) Name() string { return f.filename } -func (f *FileParam) ContentType() string { return f.contentType } diff --git a/seed/go-sdk/allof/go.mod b/seed/go-sdk/allof/go.mod deleted file mode 100644 index 99668dd6c416..000000000000 --- a/seed/go-sdk/allof/go.mod +++ /dev/null @@ -1,16 +0,0 @@ -module github.com/allof/fern - -go 1.21 - -toolchain go1.23.8 - -require github.com/google/uuid v1.6.0 - -require github.com/stretchr/testify v1.8.4 - -require gopkg.in/yaml.v3 v3.0.1 // indirect - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect -) diff --git a/seed/go-sdk/allof/go.sum b/seed/go-sdk/allof/go.sum deleted file mode 100644 index fcca6d128057..000000000000 --- a/seed/go-sdk/allof/go.sum +++ /dev/null @@ -1,12 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/allof/internal/caller.go b/seed/go-sdk/allof/internal/caller.go deleted file mode 100644 index 29d91e237cc6..000000000000 --- a/seed/go-sdk/allof/internal/caller.go +++ /dev/null @@ -1,311 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "reflect" - "strings" - - "github.com/allof/fern/core" -) - -const ( - // contentType specifies the JSON Content-Type header value. - contentType = "application/json" - contentTypeHeader = "Content-Type" - contentTypeFormURLEncoded = "application/x-www-form-urlencoded" -) - -// Caller calls APIs and deserializes their response, if any. -type Caller struct { - client core.HTTPClient - retrier *Retrier -} - -// CallerParams represents the parameters used to constrcut a new *Caller. -type CallerParams struct { - Client core.HTTPClient - MaxAttempts uint -} - -// NewCaller returns a new *Caller backed by the given parameters. -func NewCaller(params *CallerParams) *Caller { - var httpClient core.HTTPClient = http.DefaultClient - if params.Client != nil { - httpClient = params.Client - } - var retryOptions []RetryOption - if params.MaxAttempts > 0 { - retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) - } - return &Caller{ - client: httpClient, - retrier: NewRetrier(retryOptions...), - } -} - -// CallParams represents the parameters used to issue an API call. -type CallParams struct { - URL string - Method string - MaxAttempts uint - Headers http.Header - BodyProperties map[string]interface{} - QueryParameters url.Values - Client core.HTTPClient - Request interface{} - Response interface{} - ResponseIsOptional bool - ErrorDecoder ErrorDecoder -} - -// CallResponse is a parsed HTTP response from an API call. -type CallResponse struct { - StatusCode int - Header http.Header -} - -// Call issues an API call according to the given call parameters. -func (c *Caller) Call(ctx context.Context, params *CallParams) (*CallResponse, error) { - url := buildURL(params.URL, params.QueryParameters) - req, err := newRequest( - ctx, - url, - params.Method, - params.Headers, - params.Request, - params.BodyProperties, - ) - if err != nil { - return nil, err - } - - // If the call has been cancelled, don't issue the request. - if err := ctx.Err(); err != nil { - return nil, err - } - - client := c.client - if params.Client != nil { - // Use the HTTP client scoped to the request. - client = params.Client - } - - var retryOptions []RetryOption - if params.MaxAttempts > 0 { - retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) - } - - resp, err := c.retrier.Run( - client.Do, - req, - params.ErrorDecoder, - retryOptions..., - ) - if err != nil { - return nil, err - } - - // Close the response body after we're done. - defer func() { _ = resp.Body.Close() }() - - // Check if the call was cancelled before we return the error - // associated with the call and/or unmarshal the response data. - if err := ctx.Err(); err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return nil, decodeError(resp, params.ErrorDecoder) - } - - // Mutate the response parameter in-place. - if params.Response != nil { - if writer, ok := params.Response.(io.Writer); ok { - _, err = io.Copy(writer, resp.Body) - } else { - err = json.NewDecoder(resp.Body).Decode(params.Response) - } - if err != nil { - if err == io.EOF { - if params.ResponseIsOptional { - // The response is optional, so we should ignore the - // io.EOF error - return &CallResponse{ - StatusCode: resp.StatusCode, - Header: resp.Header, - }, nil - } - return nil, fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) - } - return nil, err - } - } - - return &CallResponse{ - StatusCode: resp.StatusCode, - Header: resp.Header, - }, nil -} - -// buildURL constructs the final URL by appending the given query parameters (if any). -func buildURL( - url string, - queryParameters url.Values, -) string { - if len(queryParameters) == 0 { - return url - } - if strings.ContainsRune(url, '?') { - url += "&" - } else { - url += "?" - } - url += queryParameters.Encode() - return url -} - -// newRequest returns a new *http.Request with all of the fields -// required to issue the call. -func newRequest( - ctx context.Context, - url string, - method string, - endpointHeaders http.Header, - request interface{}, - bodyProperties map[string]interface{}, -) (*http.Request, error) { - // Determine the content type from headers, defaulting to JSON. - reqContentType := contentType - if endpointHeaders != nil { - if ct := endpointHeaders.Get(contentTypeHeader); ct != "" { - reqContentType = ct - } - } - requestBody, err := newRequestBody(request, bodyProperties, reqContentType) - if err != nil { - return nil, err - } - req, err := http.NewRequestWithContext(ctx, method, url, requestBody) - if err != nil { - return nil, err - } - req.Header.Set(contentTypeHeader, reqContentType) - for name, values := range endpointHeaders { - req.Header[name] = values - } - return req, nil -} - -// newRequestBody returns a new io.Reader that represents the HTTP request body. -func newRequestBody(request interface{}, bodyProperties map[string]interface{}, reqContentType string) (io.Reader, error) { - if isNil(request) { - if len(bodyProperties) == 0 { - return nil, nil - } - if reqContentType == contentTypeFormURLEncoded { - return newFormURLEncodedBody(bodyProperties), nil - } - requestBytes, err := json.Marshal(bodyProperties) - if err != nil { - return nil, err - } - return bytes.NewReader(requestBytes), nil - } - if body, ok := request.(io.Reader); ok { - return body, nil - } - // Handle form URL encoded content type. - if reqContentType == contentTypeFormURLEncoded { - return newFormURLEncodedRequestBody(request, bodyProperties) - } - requestBytes, err := MarshalJSONWithExtraProperties(request, bodyProperties) - if err != nil { - return nil, err - } - return bytes.NewReader(requestBytes), nil -} - -// newFormURLEncodedBody returns a new io.Reader that represents a form URL encoded body -// from the given body properties map. -func newFormURLEncodedBody(bodyProperties map[string]interface{}) io.Reader { - values := url.Values{} - for key, val := range bodyProperties { - values.Set(key, fmt.Sprintf("%v", val)) - } - return strings.NewReader(values.Encode()) -} - -// newFormURLEncodedRequestBody returns a new io.Reader that represents a form URL encoded body -// from the given request struct and body properties. -func newFormURLEncodedRequestBody(request interface{}, bodyProperties map[string]interface{}) (io.Reader, error) { - values := url.Values{} - // Marshal the request to JSON first to respect any custom MarshalJSON methods, - // then unmarshal into a map to extract the field values. - jsonBytes, err := json.Marshal(request) - if err != nil { - return nil, err - } - var jsonMap map[string]interface{} - if err := json.Unmarshal(jsonBytes, &jsonMap); err != nil { - return nil, err - } - // Convert the JSON map to form URL encoded values. - for key, val := range jsonMap { - if val == nil { - continue - } - values.Set(key, fmt.Sprintf("%v", val)) - } - // Add any extra body properties. - for key, val := range bodyProperties { - values.Set(key, fmt.Sprintf("%v", val)) - } - return strings.NewReader(values.Encode()), nil -} - -// decodeError decodes the error from the given HTTP response. Note that -// it's the caller's responsibility to close the response body. -func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { - if errorDecoder != nil { - // This endpoint has custom errors, so we'll - // attempt to unmarshal the error into a structured - // type based on the status code. - return errorDecoder(response.StatusCode, response.Header, response.Body) - } - // This endpoint doesn't have any custom error - // types, so we just read the body as-is, and - // put it into a normal error. - bytes, err := io.ReadAll(response.Body) - if err != nil && err != io.EOF { - return err - } - if err == io.EOF { - // The error didn't have a response body, - // so all we can do is return an error - // with the status code. - return core.NewAPIError(response.StatusCode, response.Header, nil) - } - return core.NewAPIError(response.StatusCode, response.Header, errors.New(string(bytes))) -} - -// isNil is used to determine if the request value is equal to nil (i.e. an interface -// value that holds a nil concrete value is itself non-nil). -func isNil(value interface{}) bool { - if value == nil { - return true - } - v := reflect.ValueOf(value) - switch v.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: - return v.IsNil() - default: - return false - } -} diff --git a/seed/go-sdk/allof/internal/caller_test.go b/seed/go-sdk/allof/internal/caller_test.go deleted file mode 100644 index b3dcb522884a..000000000000 --- a/seed/go-sdk/allof/internal/caller_test.go +++ /dev/null @@ -1,705 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "strings" - "testing" - - "github.com/allof/fern/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// InternalTestCase represents a single test case. -type InternalTestCase struct { - description string - - // Server-side assertions. - givePathSuffix string - giveMethod string - giveResponseIsOptional bool - giveHeader http.Header - giveErrorDecoder ErrorDecoder - giveRequest *InternalTestRequest - giveQueryParams url.Values - giveBodyProperties map[string]interface{} - - // Client-side assertions. - wantResponse *InternalTestResponse - wantError error -} - -// InternalTestRequest a simple request body. -type InternalTestRequest struct { - Id string `json:"id"` -} - -// InternalTestResponse a simple response body. -type InternalTestResponse struct { - Id string `json:"id"` - ExtraBodyProperties map[string]interface{} `json:"extraBodyProperties,omitempty"` - QueryParameters url.Values `json:"queryParameters,omitempty"` -} - -// InternalTestNotFoundError represents a 404. -type InternalTestNotFoundError struct { - *core.APIError - - Message string `json:"message"` -} - -func TestCall(t *testing.T) { - tests := []*InternalTestCase{ - { - description: "GET success", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - wantResponse: &InternalTestResponse{ - Id: "123", - }, - }, - { - description: "GET success with query", - givePathSuffix: "?limit=1", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - wantResponse: &InternalTestResponse{ - Id: "123", - QueryParameters: url.Values{ - "limit": []string{"1"}, - }, - }, - }, - { - description: "GET not found", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"fail"}, - }, - giveRequest: &InternalTestRequest{ - Id: strconv.Itoa(http.StatusNotFound), - }, - giveErrorDecoder: newTestErrorDecoder(t), - wantError: &InternalTestNotFoundError{ - APIError: core.NewAPIError( - http.StatusNotFound, - http.Header{}, - errors.New(`{"message":"ID \"404\" not found"}`), - ), - }, - }, - { - description: "POST empty body", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"fail"}, - }, - giveRequest: nil, - wantError: core.NewAPIError( - http.StatusBadRequest, - http.Header{}, - errors.New("invalid request"), - ), - }, - { - description: "POST optional response", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - giveResponseIsOptional: true, - }, - { - description: "POST API error", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"fail"}, - }, - giveRequest: &InternalTestRequest{ - Id: strconv.Itoa(http.StatusInternalServerError), - }, - wantError: core.NewAPIError( - http.StatusInternalServerError, - http.Header{}, - errors.New("failed to process request"), - ), - }, - { - description: "POST extra properties", - giveMethod: http.MethodPost, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: new(InternalTestRequest), - giveBodyProperties: map[string]interface{}{ - "key": "value", - }, - wantResponse: &InternalTestResponse{ - ExtraBodyProperties: map[string]interface{}{ - "key": "value", - }, - }, - }, - { - description: "GET extra query parameters", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveQueryParams: url.Values{ - "extra": []string{"true"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - wantResponse: &InternalTestResponse{ - Id: "123", - QueryParameters: url.Values{ - "extra": []string{"true"}, - }, - }, - }, - { - description: "GET merge extra query parameters", - givePathSuffix: "?limit=1", - giveMethod: http.MethodGet, - giveHeader: http.Header{ - "X-API-Status": []string{"success"}, - }, - giveRequest: &InternalTestRequest{ - Id: "123", - }, - giveQueryParams: url.Values{ - "extra": []string{"true"}, - }, - wantResponse: &InternalTestResponse{ - Id: "123", - QueryParameters: url.Values{ - "limit": []string{"1"}, - "extra": []string{"true"}, - }, - }, - }, - } - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - var ( - server = newTestServer(t, test) - client = server.Client() - ) - caller := NewCaller( - &CallerParams{ - Client: client, - }, - ) - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL + test.givePathSuffix, - Method: test.giveMethod, - Headers: test.giveHeader, - BodyProperties: test.giveBodyProperties, - QueryParameters: test.giveQueryParams, - Request: test.giveRequest, - Response: &response, - ResponseIsOptional: test.giveResponseIsOptional, - ErrorDecoder: test.giveErrorDecoder, - }, - ) - if test.wantError != nil { - assert.EqualError(t, err, test.wantError.Error()) - return - } - require.NoError(t, err) - assert.Equal(t, test.wantResponse, response) - }) - } -} - -func TestMergeHeaders(t *testing.T) { - t.Run("both empty", func(t *testing.T) { - merged := MergeHeaders(make(http.Header), make(http.Header)) - assert.Empty(t, merged) - }) - - t.Run("empty left", func(t *testing.T) { - left := make(http.Header) - - right := make(http.Header) - right.Set("X-API-Version", "0.0.1") - - merged := MergeHeaders(left, right) - assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) - }) - - t.Run("empty right", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Version", "0.0.1") - - right := make(http.Header) - - merged := MergeHeaders(left, right) - assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) - }) - - t.Run("single value override", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Version", "0.0.0") - - right := make(http.Header) - right.Set("X-API-Version", "0.0.1") - - merged := MergeHeaders(left, right) - assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) - }) - - t.Run("multiple value override", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Versions", "0.0.0") - - right := make(http.Header) - right.Add("X-API-Versions", "0.0.1") - right.Add("X-API-Versions", "0.0.2") - - merged := MergeHeaders(left, right) - assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) - }) - - t.Run("disjoint merge", func(t *testing.T) { - left := make(http.Header) - left.Set("X-API-Tenancy", "test") - - right := make(http.Header) - right.Set("X-API-Version", "0.0.1") - - merged := MergeHeaders(left, right) - assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) - assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) - }) -} - -// newTestServer returns a new *httptest.Server configured with the -// given test parameters. -func newTestServer(t *testing.T, tc *InternalTestCase) *httptest.Server { - return httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, tc.giveMethod, r.Method) - assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) - for header, value := range tc.giveHeader { - assert.Equal(t, value, r.Header.Values(header)) - } - - request := new(InternalTestRequest) - - bytes, err := io.ReadAll(r.Body) - if tc.giveRequest == nil { - require.Empty(t, bytes) - w.WriteHeader(http.StatusBadRequest) - _, err = w.Write([]byte("invalid request")) - require.NoError(t, err) - return - } - require.NoError(t, err) - require.NoError(t, json.Unmarshal(bytes, request)) - - switch request.Id { - case strconv.Itoa(http.StatusNotFound): - notFoundError := &InternalTestNotFoundError{ - APIError: &core.APIError{ - StatusCode: http.StatusNotFound, - }, - Message: fmt.Sprintf("ID %q not found", request.Id), - } - bytes, err = json.Marshal(notFoundError) - require.NoError(t, err) - - w.WriteHeader(http.StatusNotFound) - _, err = w.Write(bytes) - require.NoError(t, err) - return - - case strconv.Itoa(http.StatusInternalServerError): - w.WriteHeader(http.StatusInternalServerError) - _, err = w.Write([]byte("failed to process request")) - require.NoError(t, err) - return - } - - if tc.giveResponseIsOptional { - w.WriteHeader(http.StatusOK) - return - } - - extraBodyProperties := make(map[string]interface{}) - require.NoError(t, json.Unmarshal(bytes, &extraBodyProperties)) - delete(extraBodyProperties, "id") - - response := &InternalTestResponse{ - Id: request.Id, - ExtraBodyProperties: extraBodyProperties, - QueryParameters: r.URL.Query(), - } - bytes, err = json.Marshal(response) - require.NoError(t, err) - - _, err = w.Write(bytes) - require.NoError(t, err) - }, - ), - ) -} - -func TestIsNil(t *testing.T) { - t.Run("nil interface", func(t *testing.T) { - assert.True(t, isNil(nil)) - }) - - t.Run("nil pointer", func(t *testing.T) { - var ptr *string - assert.True(t, isNil(ptr)) - }) - - t.Run("non-nil pointer", func(t *testing.T) { - s := "test" - assert.False(t, isNil(&s)) - }) - - t.Run("nil slice", func(t *testing.T) { - var slice []string - assert.True(t, isNil(slice)) - }) - - t.Run("non-nil slice", func(t *testing.T) { - slice := []string{} - assert.False(t, isNil(slice)) - }) - - t.Run("nil map", func(t *testing.T) { - var m map[string]string - assert.True(t, isNil(m)) - }) - - t.Run("non-nil map", func(t *testing.T) { - m := make(map[string]string) - assert.False(t, isNil(m)) - }) - - t.Run("string value", func(t *testing.T) { - assert.False(t, isNil("test")) - }) - - t.Run("empty string value", func(t *testing.T) { - assert.False(t, isNil("")) - }) - - t.Run("int value", func(t *testing.T) { - assert.False(t, isNil(42)) - }) - - t.Run("zero int value", func(t *testing.T) { - assert.False(t, isNil(0)) - }) - - t.Run("bool value", func(t *testing.T) { - assert.False(t, isNil(true)) - }) - - t.Run("false bool value", func(t *testing.T) { - assert.False(t, isNil(false)) - }) - - t.Run("struct value", func(t *testing.T) { - type testStruct struct { - Field string - } - assert.False(t, isNil(testStruct{Field: "test"})) - }) - - t.Run("empty struct value", func(t *testing.T) { - type testStruct struct { - Field string - } - assert.False(t, isNil(testStruct{})) - }) -} - -// newTestErrorDecoder returns an error decoder suitable for tests. -func newTestErrorDecoder(t *testing.T) func(int, http.Header, io.Reader) error { - return func(statusCode int, header http.Header, body io.Reader) error { - raw, err := io.ReadAll(body) - require.NoError(t, err) - - var ( - apiError = core.NewAPIError(statusCode, header, errors.New(string(raw))) - decoder = json.NewDecoder(bytes.NewReader(raw)) - ) - if statusCode == http.StatusNotFound { - value := new(InternalTestNotFoundError) - value.APIError = apiError - require.NoError(t, decoder.Decode(value)) - - return value - } - return apiError - } -} - -// FormURLEncodedTestRequest is a test struct for form URL encoding tests. -type FormURLEncodedTestRequest struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - GrantType string `json:"grant_type,omitempty"` - Scope *string `json:"scope,omitempty"` - NilPointer *string `json:"nil_pointer,omitempty"` -} - -func TestNewFormURLEncodedBody(t *testing.T) { - t.Run("simple key-value pairs", func(t *testing.T) { - bodyProperties := map[string]interface{}{ - "client_id": "test_client_id", - "client_secret": "test_client_secret", - "grant_type": "client_credentials", - } - reader := newFormURLEncodedBody(bodyProperties) - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Parse the body and verify values - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "client_credentials", values.Get("grant_type")) - - // Verify it's not JSON - bodyStr := string(body) - assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should not be JSON, got: %s", bodyStr) - }) - - t.Run("special characters requiring URL encoding", func(t *testing.T) { - bodyProperties := map[string]interface{}{ - "value_with_space": "hello world", - "value_with_ampersand": "a&b", - "value_with_equals": "a=b", - "value_with_plus": "a+b", - } - reader := newFormURLEncodedBody(bodyProperties) - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Parse the body and verify values are correctly decoded - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "hello world", values.Get("value_with_space")) - assert.Equal(t, "a&b", values.Get("value_with_ampersand")) - assert.Equal(t, "a=b", values.Get("value_with_equals")) - assert.Equal(t, "a+b", values.Get("value_with_plus")) - }) - - t.Run("empty map", func(t *testing.T) { - bodyProperties := map[string]interface{}{} - reader := newFormURLEncodedBody(bodyProperties) - body, err := io.ReadAll(reader) - require.NoError(t, err) - assert.Empty(t, string(body)) - }) -} - -func TestNewFormURLEncodedRequestBody(t *testing.T) { - t.Run("struct with json tags", func(t *testing.T) { - scope := "read write" - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - GrantType: "client_credentials", - Scope: &scope, - NilPointer: nil, - } - reader, err := newFormURLEncodedRequestBody(request, nil) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Parse the body and verify values - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "client_credentials", values.Get("grant_type")) - assert.Equal(t, "read write", values.Get("scope")) - // nil_pointer should not be present (nil pointer with omitempty) - assert.Empty(t, values.Get("nil_pointer")) - - // Verify it's not JSON - bodyStr := string(body) - assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should not be JSON, got: %s", bodyStr) - }) - - t.Run("struct with omitempty and zero values", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - GrantType: "", // empty string with omitempty should be omitted - Scope: nil, - NilPointer: nil, - } - reader, err := newFormURLEncodedRequestBody(request, nil) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - // grant_type should not be present (empty string with omitempty) - assert.Empty(t, values.Get("grant_type")) - assert.Empty(t, values.Get("scope")) - }) - - t.Run("struct with extra body properties", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - } - bodyProperties := map[string]interface{}{ - "extra_param": "extra_value", - } - reader, err := newFormURLEncodedRequestBody(request, bodyProperties) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "extra_value", values.Get("extra_param")) - }) - - t.Run("special characters in struct fields", func(t *testing.T) { - scope := "read&write=all+permissions" - request := &FormURLEncodedTestRequest{ - ClientID: "client with spaces", - ClientSecret: "secret&with=special+chars", - Scope: &scope, - } - reader, err := newFormURLEncodedRequestBody(request, nil) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "client with spaces", values.Get("client_id")) - assert.Equal(t, "secret&with=special+chars", values.Get("client_secret")) - assert.Equal(t, "read&write=all+permissions", values.Get("scope")) - }) -} - -func TestNewRequestBodyFormURLEncoded(t *testing.T) { - t.Run("selects form encoding when content-type is form-urlencoded", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - GrantType: "client_credentials", - } - reader, err := newRequestBody(request, nil, contentTypeFormURLEncoded) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Verify it's form-urlencoded, not JSON - bodyStr := string(body) - assert.False(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should not be JSON when Content-Type is form-urlencoded, got: %s", bodyStr) - - // Parse and verify values - values, err := url.ParseQuery(bodyStr) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - assert.Equal(t, "client_credentials", values.Get("grant_type")) - }) - - t.Run("selects JSON encoding when content-type is application/json", func(t *testing.T) { - request := &FormURLEncodedTestRequest{ - ClientID: "test_client_id", - ClientSecret: "test_client_secret", - } - reader, err := newRequestBody(request, nil, contentType) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - // Verify it's JSON - bodyStr := string(body) - assert.True(t, strings.HasPrefix(strings.TrimSpace(bodyStr), "{"), - "Body should be JSON when Content-Type is application/json, got: %s", bodyStr) - - // Parse and verify it's valid JSON - var parsed map[string]interface{} - err = json.Unmarshal(body, &parsed) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", parsed["client_id"]) - assert.Equal(t, "test_client_secret", parsed["client_secret"]) - }) - - t.Run("form encoding with body properties only (nil request)", func(t *testing.T) { - bodyProperties := map[string]interface{}{ - "client_id": "test_client_id", - "client_secret": "test_client_secret", - } - reader, err := newRequestBody(nil, bodyProperties, contentTypeFormURLEncoded) - require.NoError(t, err) - - body, err := io.ReadAll(reader) - require.NoError(t, err) - - values, err := url.ParseQuery(string(body)) - require.NoError(t, err) - - assert.Equal(t, "test_client_id", values.Get("client_id")) - assert.Equal(t, "test_client_secret", values.Get("client_secret")) - }) -} diff --git a/seed/go-sdk/allof/internal/error_decoder.go b/seed/go-sdk/allof/internal/error_decoder.go deleted file mode 100644 index 10a0b8b9bc93..000000000000 --- a/seed/go-sdk/allof/internal/error_decoder.go +++ /dev/null @@ -1,64 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - - "github.com/allof/fern/core" -) - -// ErrorCodes maps HTTP status codes to error constructors. -type ErrorCodes map[int]func(*core.APIError) error - -// ErrorDecoder decodes *http.Response errors and returns a -// typed API error (e.g. *core.APIError). -type ErrorDecoder func(statusCode int, header http.Header, body io.Reader) error - -// NewErrorDecoder returns a new ErrorDecoder backed by the given error codes. -// errorCodesOverrides is optional and will be merged with the default error codes, -// with overrides taking precedence. -func NewErrorDecoder(errorCodes ErrorCodes, errorCodesOverrides ...ErrorCodes) ErrorDecoder { - // Merge default error codes with overrides - mergedErrorCodes := make(ErrorCodes) - - // Start with default error codes - for statusCode, errorFunc := range errorCodes { - mergedErrorCodes[statusCode] = errorFunc - } - - // Apply overrides if provided - if len(errorCodesOverrides) > 0 && errorCodesOverrides[0] != nil { - for statusCode, errorFunc := range errorCodesOverrides[0] { - mergedErrorCodes[statusCode] = errorFunc - } - } - - return func(statusCode int, header http.Header, body io.Reader) error { - raw, err := io.ReadAll(body) - if err != nil { - return fmt.Errorf("failed to read error from response body: %w", err) - } - apiError := core.NewAPIError( - statusCode, - header, - errors.New(string(raw)), - ) - newErrorFunc, ok := mergedErrorCodes[statusCode] - if !ok { - // This status code isn't recognized, so we return - // the API error as-is. - return apiError - } - customError := newErrorFunc(apiError) - if err := json.NewDecoder(bytes.NewReader(raw)).Decode(customError); err != nil { - // If we fail to decode the error, we return the - // API error as-is. - return apiError - } - return customError - } -} diff --git a/seed/go-sdk/allof/internal/error_decoder_test.go b/seed/go-sdk/allof/internal/error_decoder_test.go deleted file mode 100644 index 09109239d743..000000000000 --- a/seed/go-sdk/allof/internal/error_decoder_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package internal - -import ( - "bytes" - "errors" - "net/http" - "testing" - - "github.com/allof/fern/core" - "github.com/stretchr/testify/assert" -) - -func TestErrorDecoder(t *testing.T) { - decoder := NewErrorDecoder( - ErrorCodes{ - http.StatusNotFound: func(apiError *core.APIError) error { - return &InternalTestNotFoundError{APIError: apiError} - }, - }) - - tests := []struct { - description string - giveStatusCode int - giveHeader http.Header - giveBody string - wantError error - }{ - { - description: "unrecognized status code", - giveStatusCode: http.StatusInternalServerError, - giveHeader: http.Header{}, - giveBody: "Internal Server Error", - wantError: core.NewAPIError(http.StatusInternalServerError, http.Header{}, errors.New("Internal Server Error")), - }, - { - description: "not found with valid JSON", - giveStatusCode: http.StatusNotFound, - giveHeader: http.Header{}, - giveBody: `{"message": "Resource not found"}`, - wantError: &InternalTestNotFoundError{ - APIError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New(`{"message": "Resource not found"}`)), - Message: "Resource not found", - }, - }, - { - description: "not found with invalid JSON", - giveStatusCode: http.StatusNotFound, - giveHeader: http.Header{}, - giveBody: `Resource not found`, - wantError: core.NewAPIError(http.StatusNotFound, http.Header{}, errors.New("Resource not found")), - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - assert.Equal(t, tt.wantError, decoder(tt.giveStatusCode, tt.giveHeader, bytes.NewReader([]byte(tt.giveBody)))) - }) - } -} diff --git a/seed/go-sdk/allof/internal/explicit_fields.go b/seed/go-sdk/allof/internal/explicit_fields.go deleted file mode 100644 index 4bdf34fc2b7c..000000000000 --- a/seed/go-sdk/allof/internal/explicit_fields.go +++ /dev/null @@ -1,116 +0,0 @@ -package internal - -import ( - "math/big" - "reflect" - "strings" -) - -// HandleExplicitFields processes a struct to remove `omitempty` from -// fields that have been explicitly set (as indicated by their corresponding bit in explicitFields). -// Note that `marshaler` should be an embedded struct to avoid infinite recursion. -// Returns an interface{} that can be passed to json.Marshal. -func HandleExplicitFields(marshaler interface{}, explicitFields *big.Int) interface{} { - val := reflect.ValueOf(marshaler) - typ := reflect.TypeOf(marshaler) - - // Handle pointer types - if val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil - } - val = val.Elem() - typ = typ.Elem() - } - - // Only handle struct types - if val.Kind() != reflect.Struct { - return marshaler - } - - // Handle embedded struct pattern - var sourceVal reflect.Value - var sourceType reflect.Type - - // Check if this is an embedded struct pattern - if typ.NumField() == 1 && typ.Field(0).Anonymous { - // This is likely an embedded struct, get the embedded value - embeddedField := val.Field(0) - sourceVal = embeddedField - sourceType = embeddedField.Type() - } else { - // Regular struct - sourceVal = val - sourceType = typ - } - - // If no explicit fields set, use standard marshaling - if explicitFields == nil || explicitFields.Sign() == 0 { - return marshaler - } - - // Create a new struct type with modified tags - fields := make([]reflect.StructField, 0, sourceType.NumField()) - - for i := 0; i < sourceType.NumField(); i++ { - field := sourceType.Field(i) - - // Skip unexported fields and the explicitFields field itself - if !field.IsExported() || field.Name == "explicitFields" { - continue - } - - // Check if this field has been explicitly set - fieldBit := big.NewInt(1) - fieldBit.Lsh(fieldBit, uint(i)) - if big.NewInt(0).And(explicitFields, fieldBit).Sign() != 0 { - // Remove omitempty from the json tag - tag := field.Tag.Get("json") - if tag != "" && tag != "-" { - // Parse the json tag, remove omitempty from options - parts := strings.Split(tag, ",") - if len(parts) > 1 { - var newParts []string - newParts = append(newParts, parts[0]) // Keep the field name - for _, part := range parts[1:] { - if strings.TrimSpace(part) != "omitempty" { - newParts = append(newParts, part) - } - } - tag = strings.Join(newParts, ",") - } - - // Reconstruct the struct tag - newTag := `json:"` + tag + `"` - if urlTag := field.Tag.Get("url"); urlTag != "" { - newTag += ` url:"` + urlTag + `"` - } - - field.Tag = reflect.StructTag(newTag) - } - } - - fields = append(fields, field) - } - - // Create new struct type with modified tags - newType := reflect.StructOf(fields) - newVal := reflect.New(newType).Elem() - - // Copy field values from original struct to new struct - fieldIndex := 0 - for i := 0; i < sourceType.NumField(); i++ { - originalField := sourceType.Field(i) - - // Skip unexported fields and the explicitFields field itself - if !originalField.IsExported() || originalField.Name == "explicitFields" { - continue - } - - originalValue := sourceVal.Field(i) - newVal.Field(fieldIndex).Set(originalValue) - fieldIndex++ - } - - return newVal.Interface() -} diff --git a/seed/go-sdk/allof/internal/explicit_fields_test.go b/seed/go-sdk/allof/internal/explicit_fields_test.go deleted file mode 100644 index f44beec447d6..000000000000 --- a/seed/go-sdk/allof/internal/explicit_fields_test.go +++ /dev/null @@ -1,645 +0,0 @@ -package internal - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testExplicitFieldsStruct struct { - Name *string `json:"name,omitempty"` - Code *string `json:"code,omitempty"` - Count *int `json:"count,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - Tags []string `json:"tags,omitempty"` - unexported string `json:"-"` //nolint:unused - explicitFields *big.Int `json:"-"` -} - -var ( - testFieldName = big.NewInt(1 << 0) - testFieldCode = big.NewInt(1 << 1) - testFieldCount = big.NewInt(1 << 2) - testFieldEnabled = big.NewInt(1 << 3) - testFieldTags = big.NewInt(1 << 4) -) - -func (t *testExplicitFieldsStruct) require(field *big.Int) { - if t.explicitFields == nil { - t.explicitFields = big.NewInt(0) - } - t.explicitFields.Or(t.explicitFields, field) -} - -func (t *testExplicitFieldsStruct) SetName(name *string) { - t.Name = name - t.require(testFieldName) -} - -func (t *testExplicitFieldsStruct) SetCode(code *string) { - t.Code = code - t.require(testFieldCode) -} - -func (t *testExplicitFieldsStruct) SetCount(count *int) { - t.Count = count - t.require(testFieldCount) -} - -func (t *testExplicitFieldsStruct) SetEnabled(enabled *bool) { - t.Enabled = enabled - t.require(testFieldEnabled) -} - -func (t *testExplicitFieldsStruct) SetTags(tags []string) { - t.Tags = tags - t.require(testFieldTags) -} - -func (t *testExplicitFieldsStruct) MarshalJSON() ([]byte, error) { - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*t), - } - return json.Marshal(HandleExplicitFields(marshaler, t.explicitFields)) -} - -type testStructWithoutExplicitFields struct { - Name *string `json:"name,omitempty"` - Code *string `json:"code,omitempty"` -} - -func TestHandleExplicitFields(t *testing.T) { - tests := []struct { - desc string - giveInput interface{} - wantBytes []byte - wantError string - }{ - { - desc: "nil input", - giveInput: nil, - wantBytes: []byte(`null`), - }, - { - desc: "non-struct input", - giveInput: "string", - wantBytes: []byte(`"string"`), - }, - { - desc: "slice input", - giveInput: []string{"a", "b"}, - wantBytes: []byte(`["a","b"]`), - }, - { - desc: "map input", - giveInput: map[string]interface{}{"key": "value"}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "struct without explicitFields field", - giveInput: &testStructWithoutExplicitFields{ - Name: stringPtr("test"), - Code: nil, - }, - wantBytes: []byte(`{"name":"test"}`), - }, - { - desc: "struct with no explicit fields set", - giveInput: &testExplicitFieldsStruct{ - Name: stringPtr("test"), - Code: nil, - }, - wantBytes: []byte(`{"name":"test"}`), - }, - { - desc: "struct with explicit nil field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Name: stringPtr("test"), - } - s.SetCode(nil) - return s - }(), - wantBytes: []byte(`{"name":"test","code":null}`), - }, - { - desc: "struct with explicit non-nil field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{} - s.SetName(stringPtr("explicit")) - s.SetCode(stringPtr("also-explicit")) - return s - }(), - wantBytes: []byte(`{"name":"explicit","code":"also-explicit"}`), - }, - { - desc: "struct with mixed explicit and implicit fields", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Name: stringPtr("implicit"), - Count: intPtr(42), - } - s.SetCode(nil) // explicit nil - return s - }(), - wantBytes: []byte(`{"name":"implicit","code":null,"count":42}`), - }, - { - desc: "struct with multiple explicit nil fields", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Name: stringPtr("test"), - } - s.SetCode(nil) - s.SetCount(nil) - return s - }(), - wantBytes: []byte(`{"name":"test","code":null,"count":null}`), - }, - { - desc: "struct with slice field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{ - Tags: []string{"tag1", "tag2"}, - } - s.SetTags(nil) // explicit nil slice - return s - }(), - wantBytes: []byte(`{"tags":null}`), - }, - { - desc: "struct with boolean field", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{} - s.SetEnabled(boolPtr(false)) // explicit false - return s - }(), - wantBytes: []byte(`{"enabled":false}`), - }, - { - desc: "struct with all fields explicit", - giveInput: func() *testExplicitFieldsStruct { - s := &testExplicitFieldsStruct{} - s.SetName(stringPtr("test")) - s.SetCode(nil) - s.SetCount(intPtr(0)) - s.SetEnabled(boolPtr(false)) - s.SetTags([]string{}) - return s - }(), - wantBytes: []byte(`{"name":"test","code":null,"count":0,"enabled":false,"tags":[]}`), - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - var explicitFields *big.Int - if s, ok := tt.giveInput.(*testExplicitFieldsStruct); ok { - explicitFields = s.explicitFields - } - bytes, err := json.Marshal(HandleExplicitFields(tt.giveInput, explicitFields)) - if tt.wantError != "" { - require.EqualError(t, err, tt.wantError) - assert.Nil(t, tt.wantBytes) - return - } - require.NoError(t, err) - assert.JSONEq(t, string(tt.wantBytes), string(bytes)) - - // Verify it's valid JSON - var value interface{} - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -func TestHandleExplicitFieldsCustomMarshaler(t *testing.T) { - t.Run("custom marshaler with explicit fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - s.SetName(nil) - s.SetCode(stringPtr("test-code")) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) - }) - - t.Run("custom marshaler with no explicit fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("implicit"), - Code: stringPtr("also-implicit"), - } - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) - }) -} - -func TestHandleExplicitFieldsPointerHandling(t *testing.T) { - t.Run("nil pointer", func(t *testing.T) { - var s *testExplicitFieldsStruct - bytes, err := json.Marshal(HandleExplicitFields(s, nil)) - require.NoError(t, err) - assert.Equal(t, []byte(`null`), bytes) - }) - - t.Run("pointer to struct", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - s.SetName(nil) - - bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) - require.NoError(t, err) - assert.JSONEq(t, `{"name":null}`, string(bytes)) - }) -} - -func TestHandleExplicitFieldsEmbeddedStruct(t *testing.T) { - t.Run("embedded struct with explicit fields", func(t *testing.T) { - // Create a struct similar to what MarshalJSON creates - s := &testExplicitFieldsStruct{} - s.SetName(nil) - s.SetCode(stringPtr("test-code")) - - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*s), - } - - bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) - require.NoError(t, err) - // Should include both explicit fields (name as null, code as "test-code") - assert.JSONEq(t, `{"name":null,"code":"test-code"}`, string(bytes)) - }) - - t.Run("embedded struct with no explicit fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("implicit"), - Code: stringPtr("also-implicit"), - } - - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*s), - } - - bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) - require.NoError(t, err) - // Should only include non-nil fields (omitempty behavior) - assert.JSONEq(t, `{"name":"implicit","code":"also-implicit"}`, string(bytes)) - }) - - t.Run("embedded struct with mixed fields", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Count: intPtr(42), // implicit field - } - s.SetName(nil) // explicit nil - s.SetCode(stringPtr("explicit")) // explicit value - - type embed testExplicitFieldsStruct - var marshaler = struct { - embed - }{ - embed: embed(*s), - } - - bytes, err := json.Marshal(HandleExplicitFields(marshaler, s.explicitFields)) - require.NoError(t, err) - // Should include explicit null, explicit value, and implicit value - assert.JSONEq(t, `{"name":null,"code":"explicit","count":42}`, string(bytes)) - }) -} - -func TestHandleExplicitFieldsTagHandling(t *testing.T) { - type testStructWithComplexTags struct { - Field1 *string `json:"field1,omitempty" url:"field1,omitempty"` - Field2 *string `json:"field2,omitempty,string" url:"field2"` - Field3 *string `json:"-"` - Field4 *string `json:"field4"` - explicitFields *big.Int `json:"-"` - } - - s := &testStructWithComplexTags{ - Field1: stringPtr("test1"), - Field4: stringPtr("test4"), - explicitFields: big.NewInt(1), // Only first field is explicit - } - - bytes, err := json.Marshal(HandleExplicitFields(s, s.explicitFields)) - require.NoError(t, err) - - // Field1 should have omitempty removed, Field2 should keep omitempty, Field4 should be included - assert.JSONEq(t, `{"field1":"test1","field4":"test4"}`, string(bytes)) -} - -// Test types for nested struct explicit fields testing -type testNestedStruct struct { - NestedName *string `json:"nested_name,omitempty"` - NestedCode *string `json:"nested_code,omitempty"` - explicitFields *big.Int `json:"-"` -} - -type testParentStruct struct { - ParentName *string `json:"parent_name,omitempty"` - Nested *testNestedStruct `json:"nested,omitempty"` - explicitFields *big.Int `json:"-"` -} - -var ( - nestedFieldName = big.NewInt(1 << 0) - nestedFieldCode = big.NewInt(1 << 1) -) - -var ( - parentFieldName = big.NewInt(1 << 0) - parentFieldNested = big.NewInt(1 << 1) -) - -func (n *testNestedStruct) require(field *big.Int) { - if n.explicitFields == nil { - n.explicitFields = big.NewInt(0) - } - n.explicitFields.Or(n.explicitFields, field) -} - -func (n *testNestedStruct) SetNestedName(name *string) { - n.NestedName = name - n.require(nestedFieldName) -} - -func (n *testNestedStruct) SetNestedCode(code *string) { - n.NestedCode = code - n.require(nestedFieldCode) -} - -func (n *testNestedStruct) MarshalJSON() ([]byte, error) { - type embed testNestedStruct - var marshaler = struct { - embed - }{ - embed: embed(*n), - } - return json.Marshal(HandleExplicitFields(marshaler, n.explicitFields)) -} - -func (p *testParentStruct) require(field *big.Int) { - if p.explicitFields == nil { - p.explicitFields = big.NewInt(0) - } - p.explicitFields.Or(p.explicitFields, field) -} - -func (p *testParentStruct) SetParentName(name *string) { - p.ParentName = name - p.require(parentFieldName) -} - -func (p *testParentStruct) SetNested(nested *testNestedStruct) { - p.Nested = nested - p.require(parentFieldNested) -} - -func (p *testParentStruct) MarshalJSON() ([]byte, error) { - type embed testParentStruct - var marshaler = struct { - embed - }{ - embed: embed(*p), - } - return json.Marshal(HandleExplicitFields(marshaler, p.explicitFields)) -} - -func TestHandleExplicitFieldsNestedStruct(t *testing.T) { - tests := []struct { - desc string - setupFunc func() *testParentStruct - wantBytes []byte - }{ - { - desc: "nested struct with explicit nil in nested object", - setupFunc: func() *testParentStruct { - nested := &testNestedStruct{ - NestedName: stringPtr("implicit-nested"), - } - nested.SetNestedCode(nil) // explicit nil - - return &testParentStruct{ - ParentName: stringPtr("implicit-parent"), - Nested: nested, - } - }, - wantBytes: []byte(`{"parent_name":"implicit-parent","nested":{"nested_name":"implicit-nested","nested_code":null}}`), - }, - { - desc: "parent with explicit nil nested struct", - setupFunc: func() *testParentStruct { - parent := &testParentStruct{ - ParentName: stringPtr("implicit-parent"), - } - parent.SetNested(nil) // explicit nil nested struct - return parent - }, - wantBytes: []byte(`{"parent_name":"implicit-parent","nested":null}`), - }, - { - desc: "all explicit fields in nested structure", - setupFunc: func() *testParentStruct { - nested := &testNestedStruct{} - nested.SetNestedName(stringPtr("explicit-nested")) - nested.SetNestedCode(nil) // explicit nil - - parent := &testParentStruct{} - parent.SetParentName(nil) // explicit nil - parent.SetNested(nested) // explicit nested struct - - return parent - }, - wantBytes: []byte(`{"parent_name":null,"nested":{"nested_name":"explicit-nested","nested_code":null}}`), - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - parent := tt.setupFunc() - bytes, err := parent.MarshalJSON() - require.NoError(t, err) - assert.JSONEq(t, string(tt.wantBytes), string(bytes)) - - // Verify it's valid JSON - var value interface{} - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -// Test for setter method documentation and behavior -func TestSetterMethodsDocumentation(t *testing.T) { - t.Run("setter prevents omitempty for nil values", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Use setter to explicitly set nil - this should prevent omitempty - s.SetName(nil) - s.SetCode(nil) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Both fields should be included as null, not omitted - assert.JSONEq(t, `{"name":null,"code":null}`, string(bytes)) - }) - - t.Run("setter prevents omitempty for empty slice", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Use setter to explicitly set empty slice - s.SetTags([]string{}) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Empty slice should be included as [], not omitted - assert.JSONEq(t, `{"tags":[]}`, string(bytes)) - }) - - t.Run("setter prevents omitempty for zero values", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Use setter to explicitly set zero values - s.SetCount(intPtr(0)) - s.SetEnabled(boolPtr(false)) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Zero values should be included, not omitted - assert.JSONEq(t, `{"count":0,"enabled":false}`, string(bytes)) - }) - - t.Run("direct assignment is omitted when nil", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: nil, // Direct assignment, not using setter - Code: nil, // Direct assignment, not using setter - } - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Fields not set via setter should be omitted when nil - assert.JSONEq(t, `{}`, string(bytes)) - }) - - t.Run("mix of setter and direct assignment", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("direct"), // Direct assignment - Count: intPtr(42), // Direct assignment - } - s.SetCode(nil) // Setter with nil - s.SetEnabled(boolPtr(false)) // Setter with zero value - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Direct assignments included if non-nil, setter fields always included - assert.JSONEq(t, `{"name":"direct","code":null,"count":42,"enabled":false}`, string(bytes)) - }) -} - -// Test for complex scenarios with multiple setters -func TestComplexSetterScenarios(t *testing.T) { - t.Run("multiple setter calls on same field", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - - // Call setter multiple times - last one should win - s.SetName(stringPtr("first")) - s.SetName(stringPtr("second")) - s.SetName(nil) // Final value is nil - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Should serialize the last set value (nil) - assert.JSONEq(t, `{"name":null}`, string(bytes)) - }) - - t.Run("setter after direct assignment", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("direct"), - } - - // Override with setter - s.SetName(nil) - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // Setter should mark field as explicit, so nil is serialized - assert.JSONEq(t, `{"name":null}`, string(bytes)) - }) - - t.Run("all fields set via setters", func(t *testing.T) { - s := &testExplicitFieldsStruct{} - s.SetName(nil) - s.SetCode(stringPtr("")) // Empty string - s.SetCount(intPtr(0)) // Zero - s.SetEnabled(boolPtr(false)) // False - s.SetTags(nil) // Nil slice - - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - // All fields should be present even with nil/zero values - assert.JSONEq(t, `{"name":null,"code":"","count":0,"enabled":false,"tags":null}`, string(bytes)) - }) -} - -// Test for backwards compatibility -func TestBackwardsCompatibility(t *testing.T) { - t.Run("struct without setters behaves normally", func(t *testing.T) { - s := &testStructWithoutExplicitFields{ - Name: stringPtr("test"), - Code: nil, // This should be omitted - } - - bytes, err := json.Marshal(s) - require.NoError(t, err) - - // Without setters, omitempty works normally - assert.JSONEq(t, `{"name":"test"}`, string(bytes)) - }) - - t.Run("struct with explicit fields works with standard json.Marshal", func(t *testing.T) { - s := &testExplicitFieldsStruct{ - Name: stringPtr("test"), - } - s.SetCode(nil) - - // Using the custom MarshalJSON - bytes, err := s.MarshalJSON() - require.NoError(t, err) - - assert.JSONEq(t, `{"name":"test","code":null}`, string(bytes)) - }) -} - -// Helper functions -func stringPtr(s string) *string { - return &s -} - -func intPtr(i int) *int { - return &i -} - -func boolPtr(b bool) *bool { - return &b -} diff --git a/seed/go-sdk/allof/internal/extra_properties.go b/seed/go-sdk/allof/internal/extra_properties.go deleted file mode 100644 index 540c3fd89eeb..000000000000 --- a/seed/go-sdk/allof/internal/extra_properties.go +++ /dev/null @@ -1,141 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "strings" -) - -// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. -func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { - return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) -} - -// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. -func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { - bytes, err := json.Marshal(marshaler) - if err != nil { - return nil, err - } - if len(extraProperties) == 0 { - return bytes, nil - } - keys, err := getKeys(marshaler) - if err != nil { - return nil, err - } - for _, key := range keys { - if _, ok := extraProperties[key]; ok { - return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) - } - } - extraBytes, err := json.Marshal(extraProperties) - if err != nil { - return nil, err - } - if isEmptyJSON(bytes) { - if isEmptyJSON(extraBytes) { - return bytes, nil - } - return extraBytes, nil - } - result := bytes[:len(bytes)-1] - result = append(result, ',') - result = append(result, extraBytes[1:len(extraBytes)-1]...) - result = append(result, '}') - return result, nil -} - -// ExtractExtraProperties extracts any extra properties from the given value. -func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { - val := reflect.ValueOf(value) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil, fmt.Errorf("value must be non-nil to extract extra properties") - } - val = val.Elem() - } - if err := json.Unmarshal(bytes, &value); err != nil { - return nil, err - } - var extraProperties map[string]interface{} - if err := json.Unmarshal(bytes, &extraProperties); err != nil { - return nil, err - } - for i := 0; i < val.Type().NumField(); i++ { - key := jsonKey(val.Type().Field(i)) - if key == "" || key == "-" { - continue - } - delete(extraProperties, key) - } - for _, key := range exclude { - delete(extraProperties, key) - } - if len(extraProperties) == 0 { - return nil, nil - } - return extraProperties, nil -} - -// getKeys returns the keys associated with the given value. The value must be a -// a struct or a map with string keys. -func getKeys(value interface{}) ([]string, error) { - val := reflect.ValueOf(value) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - if !val.IsValid() { - return nil, nil - } - switch val.Kind() { - case reflect.Struct: - return getKeysForStructType(val.Type()), nil - case reflect.Map: - var keys []string - if val.Type().Key().Kind() != reflect.String { - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } - for _, key := range val.MapKeys() { - keys = append(keys, key.String()) - } - return keys, nil - default: - return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) - } -} - -// getKeysForStructType returns all the keys associated with the given struct type, -// visiting embedded fields recursively. -func getKeysForStructType(structType reflect.Type) []string { - if structType.Kind() == reflect.Pointer { - structType = structType.Elem() - } - if structType.Kind() != reflect.Struct { - return nil - } - var keys []string - for i := 0; i < structType.NumField(); i++ { - field := structType.Field(i) - if field.Anonymous { - keys = append(keys, getKeysForStructType(field.Type)...) - continue - } - keys = append(keys, jsonKey(field)) - } - return keys -} - -// jsonKey returns the JSON key from the struct tag of the given field, -// excluding the omitempty flag (if any). -func jsonKey(field reflect.StructField) string { - return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") -} - -// isEmptyJSON returns true if the given data is empty, the empty JSON object, or -// an explicit null. -func isEmptyJSON(data []byte) bool { - return len(data) <= 2 || bytes.Equal(data, []byte("null")) -} diff --git a/seed/go-sdk/allof/internal/extra_properties_test.go b/seed/go-sdk/allof/internal/extra_properties_test.go deleted file mode 100644 index aa2510ee5121..000000000000 --- a/seed/go-sdk/allof/internal/extra_properties_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package internal - -import ( - "encoding/json" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testMarshaler struct { - Name string `json:"name"` - BirthDate time.Time `json:"birthDate"` - CreatedAt time.Time `json:"created_at"` -} - -func (t *testMarshaler) MarshalJSON() ([]byte, error) { - type embed testMarshaler - var marshaler = struct { - embed - BirthDate string `json:"birthDate"` - CreatedAt string `json:"created_at"` - }{ - embed: embed(*t), - BirthDate: t.BirthDate.Format("2006-01-02"), - CreatedAt: t.CreatedAt.Format(time.RFC3339), - } - return MarshalJSONWithExtraProperty(marshaler, "type", "test") -} - -func TestMarshalJSONWithExtraProperties(t *testing.T) { - tests := []struct { - desc string - giveMarshaler interface{} - giveExtraProperties map[string]interface{} - wantBytes []byte - wantError string - }{ - { - desc: "invalid type", - giveMarshaler: []string{"invalid"}, - giveExtraProperties: map[string]interface{}{"key": "overwrite"}, - wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, - }, - { - desc: "invalid key type", - giveMarshaler: map[int]interface{}{42: "value"}, - giveExtraProperties: map[string]interface{}{"key": "overwrite"}, - wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, - }, - { - desc: "invalid map overwrite", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{"key": "overwrite"}, - wantError: `cannot add extra property "key" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, - wantError: `cannot add extra property "birthDate" because it is already defined on the type`, - }, - { - desc: "invalid struct overwrite embedded type", - giveMarshaler: new(testMarshaler), - giveExtraProperties: map[string]interface{}{"name": "bob"}, - wantError: `cannot add extra property "name" because it is already defined on the type`, - }, - { - desc: "nil", - giveMarshaler: nil, - giveExtraProperties: nil, - wantBytes: []byte(`null`), - }, - { - desc: "empty", - giveMarshaler: map[string]interface{}{}, - giveExtraProperties: map[string]interface{}{}, - wantBytes: []byte(`{}`), - }, - { - desc: "no extra properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "only extra properties", - giveMarshaler: map[string]interface{}{}, - giveExtraProperties: map[string]interface{}{"key": "value"}, - wantBytes: []byte(`{"key":"value"}`), - }, - { - desc: "single extra property", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{"extra": "property"}, - wantBytes: []byte(`{"key":"value","extra":"property"}`), - }, - { - desc: "multiple extra properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, - wantBytes: []byte(`{"key":"value","one":1,"two":2}`), - }, - { - desc: "nested properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{ - "user": map[string]interface{}{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), - }, - { - desc: "multiple nested properties", - giveMarshaler: map[string]interface{}{"key": "value"}, - giveExtraProperties: map[string]interface{}{ - "metadata": map[string]interface{}{ - "ip": "127.0.0.1", - }, - "user": map[string]interface{}{ - "age": 42, - "name": "alice", - }, - }, - wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), - }, - { - desc: "custom marshaler", - giveMarshaler: &testMarshaler{ - Name: "alice", - BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - }, - giveExtraProperties: map[string]interface{}{ - "extra": "property", - }, - wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) - if tt.wantError != "" { - require.EqualError(t, err, tt.wantError) - assert.Nil(t, tt.wantBytes) - return - } - require.NoError(t, err) - assert.Equal(t, tt.wantBytes, bytes) - - value := make(map[string]interface{}) - require.NoError(t, json.Unmarshal(bytes, &value)) - }) - } -} - -func TestExtractExtraProperties(t *testing.T) { - t.Run("none", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) - - t.Run("non-nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) - }) - - t.Run("nil pointer", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value *user - _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - assert.EqualError(t, err, "value must be non-nil to extract extra properties") - }) - - t.Run("non-zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) - }) - - t.Run("zero value", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - var value user - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) - require.NoError(t, err) - assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) - }) - - t.Run("exclude", func(t *testing.T) { - type user struct { - Name string `json:"name"` - } - value := &user{ - Name: "alice", - } - extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") - require.NoError(t, err) - assert.Nil(t, extraProperties) - }) -} diff --git a/seed/go-sdk/allof/internal/http.go b/seed/go-sdk/allof/internal/http.go deleted file mode 100644 index 77863752bb58..000000000000 --- a/seed/go-sdk/allof/internal/http.go +++ /dev/null @@ -1,71 +0,0 @@ -package internal - -import ( - "fmt" - "net/http" - "net/url" - "reflect" -) - -// HTTPClient is an interface for a subset of the *http.Client. -type HTTPClient interface { - Do(*http.Request) (*http.Response, error) -} - -// ResolveBaseURL resolves the base URL from the given arguments, -// preferring the first non-empty value. -func ResolveBaseURL(values ...string) string { - for _, value := range values { - if value != "" { - return value - } - } - return "" -} - -// EncodeURL encodes the given arguments into the URL, escaping -// values as needed. Pointer arguments are dereferenced before processing. -func EncodeURL(urlFormat string, args ...interface{}) string { - escapedArgs := make([]interface{}, 0, len(args)) - for _, arg := range args { - // Dereference the argument if it's a pointer - value := dereferenceArg(arg) - escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", value))) - } - return fmt.Sprintf(urlFormat, escapedArgs...) -} - -// dereferenceArg dereferences a pointer argument if necessary, returning the underlying value. -// If the argument is not a pointer or is nil, it returns the argument as-is. -func dereferenceArg(arg interface{}) interface{} { - if arg == nil { - return arg - } - - v := reflect.ValueOf(arg) - - // Keep dereferencing until we get to a non-pointer value or hit nil - for v.Kind() == reflect.Ptr { - if v.IsNil() { - return nil - } - v = v.Elem() - } - - return v.Interface() -} - -// MergeHeaders merges the given headers together, where the right -// takes precedence over the left. -func MergeHeaders(left, right http.Header) http.Header { - for key, values := range right { - if len(values) > 1 { - left[key] = values - continue - } - if value := right.Get(key); value != "" { - left.Set(key, value) - } - } - return left -} diff --git a/seed/go-sdk/allof/internal/query.go b/seed/go-sdk/allof/internal/query.go deleted file mode 100644 index 9b567f7a5563..000000000000 --- a/seed/go-sdk/allof/internal/query.go +++ /dev/null @@ -1,358 +0,0 @@ -package internal - -import ( - "encoding/base64" - "fmt" - "net/url" - "reflect" - "strings" - "time" - - "github.com/google/uuid" -) - -// RFC3339Milli is a time format string for RFC 3339 with millisecond precision. -// Go's time.RFC3339 omits fractional seconds and time.RFC3339Nano trims trailing -// zeros, so neither produces the fixed ".000" millisecond suffix that many APIs expect. -const RFC3339Milli = "2006-01-02T15:04:05.000Z07:00" - -var ( - bytesType = reflect.TypeOf([]byte{}) - queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() - timeType = reflect.TypeOf(time.Time{}) - uuidType = reflect.TypeOf(uuid.UUID{}) -) - -// QueryEncoder is an interface implemented by any type that wishes to encode -// itself into URL values in a non-standard way. -type QueryEncoder interface { - EncodeQueryValues(key string, v *url.Values) error -} - -// prepareValue handles common validation and unwrapping logic for both functions -func prepareValue(v interface{}) (reflect.Value, url.Values, error) { - values := make(url.Values) - val := reflect.ValueOf(v) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return reflect.Value{}, values, nil - } - val = val.Elem() - } - - if v == nil { - return reflect.Value{}, values, nil - } - - if val.Kind() != reflect.Struct { - return reflect.Value{}, nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) - } - - err := reflectValue(values, val, "") - if err != nil { - return reflect.Value{}, nil, err - } - - return val, values, nil -} - -// QueryValues encodes url.Values from request objects. -// -// Note: This type is inspired by Google's query encoding library, but -// supports far less customization and is tailored to fit this SDK's use case. -// -// Ref: https://github.com/google/go-querystring -func QueryValues(v interface{}) (url.Values, error) { - _, values, err := prepareValue(v) - return values, err -} - -// QueryValuesWithDefaults encodes url.Values from request objects -// and default values, merging the defaults into the request. -// It's expected that the values of defaults are wire names. -func QueryValuesWithDefaults(v interface{}, defaults map[string]interface{}) (url.Values, error) { - val, values, err := prepareValue(v) - if err != nil { - return values, err - } - if !val.IsValid() { - return values, nil - } - - // apply defaults to zero-value fields directly on the original struct - valType := val.Type() - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - fieldType := valType.Field(i) - fieldName := fieldType.Name - - if fieldType.PkgPath != "" && !fieldType.Anonymous { - // Skip unexported fields. - continue - } - - // check if field is zero value and we have a default for it - if field.CanSet() && field.IsZero() { - tag := fieldType.Tag.Get("url") - if tag == "" || tag == "-" { - continue - } - wireName, _ := parseTag(tag) - if wireName == "" { - wireName = fieldName - } - if defaultVal, exists := defaults[wireName]; exists { - values.Set(wireName, valueString(reflect.ValueOf(defaultVal), tagOptions{}, reflect.StructField{})) - } - } - } - - return values, err -} - -// reflectValue populates the values parameter from the struct fields in val. -// Embedded structs are followed recursively (using the rules defined in the -// Values function documentation) breadth-first. -func reflectValue(values url.Values, val reflect.Value, scope string) error { - typ := val.Type() - for i := 0; i < typ.NumField(); i++ { - sf := typ.Field(i) - if sf.PkgPath != "" && !sf.Anonymous { - // Skip unexported fields. - continue - } - - sv := val.Field(i) - tag := sf.Tag.Get("url") - if tag == "" || tag == "-" { - continue - } - - name, opts := parseTag(tag) - if name == "" { - name = sf.Name - } - - if scope != "" { - name = scope + "[" + name + "]" - } - - if opts.Contains("omitempty") && isEmptyValue(sv) { - continue - } - - if sv.Type().Implements(queryEncoderType) { - // If sv is a nil pointer and the custom encoder is defined on a non-pointer - // method receiver, set sv to the zero value of the underlying type - if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { - sv = reflect.New(sv.Type().Elem()) - } - - m := sv.Interface().(QueryEncoder) - if err := m.EncodeQueryValues(name, &values); err != nil { - return err - } - continue - } - - // Recursively dereference pointers, but stop at nil pointers. - for sv.Kind() == reflect.Ptr { - if sv.IsNil() { - break - } - sv = sv.Elem() - } - - if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { - values.Add(name, valueString(sv, opts, sf)) - continue - } - - if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { - if sv.Len() == 0 { - // Skip if slice or array is empty. - continue - } - for i := 0; i < sv.Len(); i++ { - value := sv.Index(i) - if isStructPointer(value) && !value.IsNil() { - if err := reflectValue(values, value.Elem(), name); err != nil { - return err - } - } else { - values.Add(name, valueString(value, opts, sf)) - } - } - continue - } - - if sv.Kind() == reflect.Map { - if err := reflectMap(values, sv, name); err != nil { - return err - } - continue - } - - if sv.Kind() == reflect.Struct { - if err := reflectValue(values, sv, name); err != nil { - return err - } - continue - } - - values.Add(name, valueString(sv, opts, sf)) - } - - return nil -} - -// reflectMap handles map types specifically, generating query parameters in the format key[mapkey]=value -func reflectMap(values url.Values, val reflect.Value, scope string) error { - if val.IsNil() { - return nil - } - - iter := val.MapRange() - for iter.Next() { - k := iter.Key() - v := iter.Value() - - key := fmt.Sprint(k.Interface()) - paramName := scope + "[" + key + "]" - - for v.Kind() == reflect.Ptr { - if v.IsNil() { - break - } - v = v.Elem() - } - - for v.Kind() == reflect.Interface { - v = v.Elem() - } - - if v.Kind() == reflect.Map { - if err := reflectMap(values, v, paramName); err != nil { - return err - } - continue - } - - if v.Kind() == reflect.Struct { - if err := reflectValue(values, v, paramName); err != nil { - return err - } - continue - } - - if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { - if v.Len() == 0 { - continue - } - for i := 0; i < v.Len(); i++ { - value := v.Index(i) - if isStructPointer(value) && !value.IsNil() { - if err := reflectValue(values, value.Elem(), paramName); err != nil { - return err - } - } else { - values.Add(paramName, valueString(value, tagOptions{}, reflect.StructField{})) - } - } - continue - } - - values.Add(paramName, valueString(v, tagOptions{}, reflect.StructField{})) - } - - return nil -} - -// valueString returns the string representation of a value. -func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { - for v.Kind() == reflect.Ptr { - if v.IsNil() { - return "" - } - v = v.Elem() - } - - if v.Type() == timeType { - t := v.Interface().(time.Time) - if format := sf.Tag.Get("format"); format == "date" { - return t.Format("2006-01-02") - } - return t.Format(RFC3339Milli) - } - - if v.Type() == uuidType { - u := v.Interface().(uuid.UUID) - return u.String() - } - - if v.Type() == bytesType { - b := v.Interface().([]byte) - return base64.StdEncoding.EncodeToString(b) - } - - return fmt.Sprint(v.Interface()) -} - -// isEmptyValue checks if a value should be considered empty for the purposes -// of omitting fields with the "omitempty" option. -func isEmptyValue(v reflect.Value) bool { - type zeroable interface { - IsZero() bool - } - - if !v.IsZero() { - if z, ok := v.Interface().(zeroable); ok { - return z.IsZero() - } - } - - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: - return false - } - - return false -} - -// isStructPointer returns true if the given reflect.Value is a pointer to a struct. -func isStructPointer(v reflect.Value) bool { - return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct -} - -// tagOptions is the string following a comma in a struct field's "url" tag, or -// the empty string. It does not include the leading comma. -type tagOptions []string - -// parseTag splits a struct field's url tag into its name and comma-separated -// options. -func parseTag(tag string) (string, tagOptions) { - s := strings.Split(tag, ",") - return s[0], s[1:] -} - -// Contains checks whether the tagOptions contains the specified option. -func (o tagOptions) Contains(option string) bool { - for _, s := range o { - if s == option { - return true - } - } - return false -} diff --git a/seed/go-sdk/allof/internal/query_test.go b/seed/go-sdk/allof/internal/query_test.go deleted file mode 100644 index 5b463e297350..000000000000 --- a/seed/go-sdk/allof/internal/query_test.go +++ /dev/null @@ -1,395 +0,0 @@ -package internal - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestQueryValues(t *testing.T) { - t.Run("empty optional", func(t *testing.T) { - type nested struct { - Value *string `json:"value,omitempty" url:"value,omitempty"` - } - type example struct { - Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` - } - - values, err := QueryValues(&example{}) - require.NoError(t, err) - assert.Empty(t, values) - }) - - t.Run("empty required", func(t *testing.T) { - type nested struct { - Value *string `json:"value,omitempty" url:"value,omitempty"` - } - type example struct { - Required string `json:"required" url:"required"` - Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` - } - - values, err := QueryValues(&example{}) - require.NoError(t, err) - assert.Equal(t, "required=", values.Encode()) - }) - - t.Run("allow multiple", func(t *testing.T) { - type example struct { - Values []string `json:"values" url:"values"` - } - - values, err := QueryValues( - &example{ - Values: []string{"foo", "bar", "baz"}, - }, - ) - require.NoError(t, err) - assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) - }) - - t.Run("nested object", func(t *testing.T) { - type nested struct { - Value *string `json:"value,omitempty" url:"value,omitempty"` - } - type example struct { - Required string `json:"required" url:"required"` - Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` - } - - nestedValue := "nestedValue" - values, err := QueryValues( - &example{ - Required: "requiredValue", - Nested: &nested{ - Value: &nestedValue, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) - }) - - t.Run("url unspecified", func(t *testing.T) { - type example struct { - Required string `json:"required" url:"required"` - NotFound string `json:"notFound"` - } - - values, err := QueryValues( - &example{ - Required: "requiredValue", - NotFound: "notFound", - }, - ) - require.NoError(t, err) - assert.Equal(t, "required=requiredValue", values.Encode()) - }) - - t.Run("url ignored", func(t *testing.T) { - type example struct { - Required string `json:"required" url:"required"` - NotFound string `json:"notFound" url:"-"` - } - - values, err := QueryValues( - &example{ - Required: "requiredValue", - NotFound: "notFound", - }, - ) - require.NoError(t, err) - assert.Equal(t, "required=requiredValue", values.Encode()) - }) - - t.Run("datetime", func(t *testing.T) { - type example struct { - DateTime time.Time `json:"dateTime" url:"dateTime"` - } - - values, err := QueryValues( - &example{ - DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), - }, - ) - require.NoError(t, err) - assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56.000Z", values.Encode()) - }) - - t.Run("date", func(t *testing.T) { - type example struct { - Date time.Time `json:"date" url:"date" format:"date"` - } - - values, err := QueryValues( - &example{ - Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), - }, - ) - require.NoError(t, err) - assert.Equal(t, "date=1994-03-16", values.Encode()) - }) - - t.Run("optional time", func(t *testing.T) { - type example struct { - Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` - } - - values, err := QueryValues( - &example{}, - ) - require.NoError(t, err) - assert.Empty(t, values.Encode()) - }) - - t.Run("omitempty with non-pointer zero value", func(t *testing.T) { - type enum string - - type example struct { - Enum enum `json:"enum,omitempty" url:"enum,omitempty"` - } - - values, err := QueryValues( - &example{}, - ) - require.NoError(t, err) - assert.Empty(t, values.Encode()) - }) - - t.Run("object array", func(t *testing.T) { - type object struct { - Key string `json:"key" url:"key"` - Value string `json:"value" url:"value"` - } - type example struct { - Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` - } - - values, err := QueryValues( - &example{ - Objects: []*object{ - { - Key: "hello", - Value: "world", - }, - { - Key: "foo", - Value: "bar", - }, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) - }) - - t.Run("map", func(t *testing.T) { - type request struct { - Metadata map[string]interface{} `json:"metadata" url:"metadata"` - } - values, err := QueryValues( - &request{ - Metadata: map[string]interface{}{ - "foo": "bar", - "baz": "qux", - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "metadata%5Bbaz%5D=qux&metadata%5Bfoo%5D=bar", values.Encode()) - }) - - t.Run("nested map", func(t *testing.T) { - type request struct { - Metadata map[string]interface{} `json:"metadata" url:"metadata"` - } - values, err := QueryValues( - &request{ - Metadata: map[string]interface{}{ - "inner": map[string]interface{}{ - "foo": "bar", - }, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "metadata%5Binner%5D%5Bfoo%5D=bar", values.Encode()) - }) - - t.Run("nested map array", func(t *testing.T) { - type request struct { - Metadata map[string]interface{} `json:"metadata" url:"metadata"` - } - values, err := QueryValues( - &request{ - Metadata: map[string]interface{}{ - "inner": []string{ - "one", - "two", - "three", - }, - }, - }, - ) - require.NoError(t, err) - assert.Equal(t, "metadata%5Binner%5D=one&metadata%5Binner%5D=two&metadata%5Binner%5D=three", values.Encode()) - }) -} - -func TestQueryValuesWithDefaults(t *testing.T) { - t.Run("apply defaults to zero values", func(t *testing.T) { - type example struct { - Name string `json:"name" url:"name"` - Age int `json:"age" url:"age"` - Enabled bool `json:"enabled" url:"enabled"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "enabled": true, - } - - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) - }) - - t.Run("preserve non-zero values over defaults", func(t *testing.T) { - type example struct { - Name string `json:"name" url:"name"` - Age int `json:"age" url:"age"` - Enabled bool `json:"enabled" url:"enabled"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "enabled": true, - } - - values, err := QueryValuesWithDefaults(&example{ - Name: "actual-name", - Age: 30, - // Enabled remains false (zero value), should get default - }, defaults) - require.NoError(t, err) - assert.Equal(t, "age=30&enabled=true&name=actual-name", values.Encode()) - }) - - t.Run("ignore defaults for fields not in struct", func(t *testing.T) { - type example struct { - Name string `json:"name" url:"name"` - Age int `json:"age" url:"age"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "nonexistent": "should-be-ignored", - } - - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "age=25&name=default-name", values.Encode()) - }) - - t.Run("type conversion for compatible defaults", func(t *testing.T) { - type example struct { - Count int64 `json:"count" url:"count"` - Rate float64 `json:"rate" url:"rate"` - Message string `json:"message" url:"message"` - } - - defaults := map[string]interface{}{ - "count": int(100), // int -> int64 conversion - "rate": float32(2.5), // float32 -> float64 conversion - "message": "hello", // string -> string (no conversion needed) - } - - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "count=100&message=hello&rate=2.5", values.Encode()) - }) - - t.Run("mixed with pointer fields and omitempty", func(t *testing.T) { - type example struct { - Required string `json:"required" url:"required"` - Optional *string `json:"optional,omitempty" url:"optional,omitempty"` - Count int `json:"count,omitempty" url:"count,omitempty"` - } - - defaultOptional := "default-optional" - defaults := map[string]interface{}{ - "required": "default-required", - "optional": &defaultOptional, // pointer type - "count": 42, - } - - values, err := QueryValuesWithDefaults(&example{ - Required: "custom-required", // should override default - // Optional is nil, should get default - // Count is 0, should get default - }, defaults) - require.NoError(t, err) - assert.Equal(t, "count=42&optional=default-optional&required=custom-required", values.Encode()) - }) - - t.Run("override non-zero defaults with explicit zero values", func(t *testing.T) { - type example struct { - Name *string `json:"name" url:"name"` - Age *int `json:"age" url:"age"` - Enabled *bool `json:"enabled" url:"enabled"` - } - - defaults := map[string]interface{}{ - "name": "default-name", - "age": 25, - "enabled": true, - } - - // first, test that a properly empty request is overridden: - { - values, err := QueryValuesWithDefaults(&example{}, defaults) - require.NoError(t, err) - assert.Equal(t, "age=25&enabled=true&name=default-name", values.Encode()) - } - - // second, test that a request that contains zeros is not overridden: - var ( - name = "" - age = 0 - enabled = false - ) - values, err := QueryValuesWithDefaults(&example{ - Name: &name, // explicit empty string should override default - Age: &age, // explicit zero should override default - Enabled: &enabled, // explicit false should override default - }, defaults) - require.NoError(t, err) - assert.Equal(t, "age=0&enabled=false&name=", values.Encode()) - }) - - t.Run("nil input returns empty values", func(t *testing.T) { - defaults := map[string]any{ - "name": "default-name", - "age": 25, - } - - // Test with nil - values, err := QueryValuesWithDefaults(nil, defaults) - require.NoError(t, err) - assert.Empty(t, values) - - // Test with nil pointer - type example struct { - Name string `json:"name" url:"name"` - } - var nilPtr *example - values, err = QueryValuesWithDefaults(nilPtr, defaults) - require.NoError(t, err) - assert.Empty(t, values) - }) -} diff --git a/seed/go-sdk/allof/internal/retrier.go b/seed/go-sdk/allof/internal/retrier.go deleted file mode 100644 index 02fd1fb7d3f1..000000000000 --- a/seed/go-sdk/allof/internal/retrier.go +++ /dev/null @@ -1,239 +0,0 @@ -package internal - -import ( - "crypto/rand" - "math/big" - "net/http" - "strconv" - "time" -) - -const ( - defaultRetryAttempts = 2 - minRetryDelay = 1000 * time.Millisecond - maxRetryDelay = 60000 * time.Millisecond -) - -// RetryOption adapts the behavior the *Retrier. -type RetryOption func(*retryOptions) - -// RetryFunc is a retryable HTTP function call (i.e. *http.Client.Do). -type RetryFunc func(*http.Request) (*http.Response, error) - -// WithMaxAttempts configures the maximum number of attempts -// of the *Retrier. -func WithMaxAttempts(attempts uint) RetryOption { - return func(opts *retryOptions) { - opts.attempts = attempts - } -} - -// Retrier retries failed requests a configurable number of times with an -// exponential back-off between each retry. -type Retrier struct { - attempts uint -} - -// NewRetrier constructs a new *Retrier with the given options, if any. -func NewRetrier(opts ...RetryOption) *Retrier { - options := new(retryOptions) - for _, opt := range opts { - opt(options) - } - attempts := uint(defaultRetryAttempts) - if options.attempts > 0 { - attempts = options.attempts - } - return &Retrier{ - attempts: attempts, - } -} - -// Run issues the request and, upon failure, retries the request if possible. -// -// The request will be retried as long as the request is deemed retryable and the -// number of retry attempts has not grown larger than the configured retry limit. -func (r *Retrier) Run( - fn RetryFunc, - request *http.Request, - errorDecoder ErrorDecoder, - opts ...RetryOption, -) (*http.Response, error) { - options := new(retryOptions) - for _, opt := range opts { - opt(options) - } - maxRetryAttempts := r.attempts - if options.attempts > 0 { - maxRetryAttempts = options.attempts - } - var ( - retryAttempt uint - previousError error - ) - return r.run( - fn, - request, - errorDecoder, - maxRetryAttempts, - retryAttempt, - previousError, - ) -} - -func (r *Retrier) run( - fn RetryFunc, - request *http.Request, - errorDecoder ErrorDecoder, - maxRetryAttempts uint, - retryAttempt uint, - previousError error, -) (*http.Response, error) { - if retryAttempt >= maxRetryAttempts { - return nil, previousError - } - - // If the call has been cancelled, don't issue the request. - if err := request.Context().Err(); err != nil { - return nil, err - } - - // Reset the request body for retries since the body may have already been read. - if retryAttempt > 0 && request.GetBody != nil { - requestBody, err := request.GetBody() - if err != nil { - return nil, err - } - request.Body = requestBody - } - - response, err := fn(request) - if err != nil { - return nil, err - } - - if r.shouldRetry(response) { - defer func() { _ = response.Body.Close() }() - - delay, err := r.retryDelay(response, retryAttempt) - if err != nil { - return nil, err - } - - time.Sleep(delay) - - return r.run( - fn, - request, - errorDecoder, - maxRetryAttempts, - retryAttempt+1, - decodeError(response, errorDecoder), - ) - } - - return response, nil -} - -// shouldRetry returns true if the request should be retried based on the given -// response status code. -func (r *Retrier) shouldRetry(response *http.Response) bool { - return response.StatusCode == http.StatusTooManyRequests || - response.StatusCode == http.StatusRequestTimeout || - response.StatusCode >= http.StatusInternalServerError -} - -// retryDelay calculates the delay time based on response headers, -// falling back to exponential backoff if no headers are present. -func (r *Retrier) retryDelay(response *http.Response, retryAttempt uint) (time.Duration, error) { - // Check for Retry-After header first (RFC 7231), applying no jitter - if retryAfter := response.Header.Get("Retry-After"); retryAfter != "" { - // Parse as number of seconds... - if seconds, err := strconv.Atoi(retryAfter); err == nil { - delay := time.Duration(seconds) * time.Second - if delay > 0 { - if delay > maxRetryDelay { - delay = maxRetryDelay - } - return delay, nil - } - } - - // ...or as an HTTP date; both are valid - if retryTime, err := time.Parse(time.RFC1123, retryAfter); err == nil { - delay := time.Until(retryTime) - if delay > 0 { - if delay > maxRetryDelay { - delay = maxRetryDelay - } - return delay, nil - } - } - } - - // Then check for industry-standard X-RateLimit-Reset header, applying positive jitter - if rateLimitReset := response.Header.Get("X-RateLimit-Reset"); rateLimitReset != "" { - if resetTimestamp, err := strconv.ParseInt(rateLimitReset, 10, 64); err == nil { - // Assume Unix timestamp in seconds - resetTime := time.Unix(resetTimestamp, 0) - delay := time.Until(resetTime) - if delay > 0 { - if delay > maxRetryDelay { - delay = maxRetryDelay - } - return r.addPositiveJitter(delay) - } - } - } - - // Fall back to exponential backoff - return r.exponentialBackoff(retryAttempt) -} - -// exponentialBackoff calculates the delay time based on the retry attempt -// and applies symmetric jitter (±10% around the delay). -func (r *Retrier) exponentialBackoff(retryAttempt uint) (time.Duration, error) { - if retryAttempt > 63 { // 2^63+ would overflow uint64 - retryAttempt = 63 - } - - delay := minRetryDelay << retryAttempt - if delay > maxRetryDelay { - delay = maxRetryDelay - } - - return r.addSymmetricJitter(delay) -} - -// addJitterWithRange applies jitter to the given delay. -// minPercent and maxPercent define the jitter range (e.g., 100, 120 for +0% to +20%). -func (r *Retrier) addJitterWithRange(delay time.Duration, minPercent, maxPercent int) (time.Duration, error) { - jitterRange := big.NewInt(int64(delay * time.Duration(maxPercent-minPercent) / 100)) - jitter, err := rand.Int(rand.Reader, jitterRange) - if err != nil { - return 0, err - } - - jitteredDelay := delay + time.Duration(jitter.Int64()) + delay*time.Duration(minPercent-100)/100 - if jitteredDelay < minRetryDelay { - jitteredDelay = minRetryDelay - } - if jitteredDelay > maxRetryDelay { - jitteredDelay = maxRetryDelay - } - return jitteredDelay, nil -} - -// addPositiveJitter applies positive jitter to the given delay (100%-120% range). -func (r *Retrier) addPositiveJitter(delay time.Duration) (time.Duration, error) { - return r.addJitterWithRange(delay, 100, 120) -} - -// addSymmetricJitter applies symmetric jitter to the given delay (90%-110% range). -func (r *Retrier) addSymmetricJitter(delay time.Duration) (time.Duration, error) { - return r.addJitterWithRange(delay, 90, 110) -} - -type retryOptions struct { - attempts uint -} diff --git a/seed/go-sdk/allof/internal/retrier_test.go b/seed/go-sdk/allof/internal/retrier_test.go deleted file mode 100644 index abe73758708b..000000000000 --- a/seed/go-sdk/allof/internal/retrier_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package internal - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/allof/fern/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type RetryTestCase struct { - description string - - giveAttempts uint - giveStatusCodes []int - giveResponse *InternalTestResponse - - wantResponse *InternalTestResponse - wantError *core.APIError -} - -func TestRetrier(t *testing.T) { - tests := []*RetryTestCase{ - { - description: "retry request succeeds after multiple failures", - giveAttempts: 3, - giveStatusCodes: []int{ - http.StatusServiceUnavailable, - http.StatusServiceUnavailable, - http.StatusOK, - }, - giveResponse: &InternalTestResponse{ - Id: "1", - }, - wantResponse: &InternalTestResponse{ - Id: "1", - }, - }, - { - description: "retry request fails if MaxAttempts is exceeded", - giveAttempts: 3, - giveStatusCodes: []int{ - http.StatusRequestTimeout, - http.StatusRequestTimeout, - http.StatusRequestTimeout, - http.StatusOK, - }, - wantError: &core.APIError{ - StatusCode: http.StatusRequestTimeout, - }, - }, - { - description: "retry durations increase exponentially and stay within the min and max delay values", - giveAttempts: 4, - giveStatusCodes: []int{ - http.StatusServiceUnavailable, - http.StatusServiceUnavailable, - http.StatusServiceUnavailable, - http.StatusOK, - }, - }, - { - description: "retry does not occur on status code 404", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusNotFound, http.StatusOK}, - wantError: &core.APIError{ - StatusCode: http.StatusNotFound, - }, - }, - { - description: "retries occur on status code 429", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusTooManyRequests, http.StatusOK}, - }, - { - description: "retries occur on status code 408", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusRequestTimeout, http.StatusOK}, - }, - { - description: "retries occur on status code 500", - giveAttempts: 2, - giveStatusCodes: []int{http.StatusInternalServerError, http.StatusOK}, - }, - } - - for _, tc := range tests { - t.Run(tc.description, func(t *testing.T) { - var ( - test = tc - server = newTestRetryServer(t, test) - client = server.Client() - ) - - t.Parallel() - - caller := NewCaller( - &CallerParams{ - Client: client, - }, - ) - - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL, - Method: http.MethodGet, - Request: &InternalTestRequest{}, - Response: &response, - MaxAttempts: test.giveAttempts, - ResponseIsOptional: true, - }, - ) - - if test.wantError != nil { - require.IsType(t, err, &core.APIError{}) - expectedErrorCode := test.wantError.StatusCode - actualErrorCode := err.(*core.APIError).StatusCode - assert.Equal(t, expectedErrorCode, actualErrorCode) - return - } - - require.NoError(t, err) - assert.Equal(t, test.wantResponse, response) - }) - } -} - -// newTestRetryServer returns a new *httptest.Server configured with the -// given test parameters, suitable for testing retries. -func newTestRetryServer(t *testing.T, tc *RetryTestCase) *httptest.Server { - var index int - timestamps := make([]time.Time, 0, len(tc.giveStatusCodes)) - - return httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - timestamps = append(timestamps, time.Now()) - if index > 0 && index < len(expectedRetryDurations) { - // Ensure that the duration between retries increases exponentially, - // and that it is within the minimum and maximum retry delay values. - actualDuration := timestamps[index].Sub(timestamps[index-1]) - expectedDurationMin := expectedRetryDurations[index-1] * 50 / 100 - expectedDurationMax := expectedRetryDurations[index-1] * 150 / 100 - assert.True( - t, - actualDuration >= expectedDurationMin && actualDuration <= expectedDurationMax, - "expected duration to be in range [%v, %v], got %v", - expectedDurationMin, - expectedDurationMax, - actualDuration, - ) - assert.LessOrEqual( - t, - actualDuration, - maxRetryDelay, - "expected duration to be less than the maxRetryDelay (%v), got %v", - maxRetryDelay, - actualDuration, - ) - assert.GreaterOrEqual( - t, - actualDuration, - minRetryDelay, - "expected duration to be greater than the minRetryDelay (%v), got %v", - minRetryDelay, - actualDuration, - ) - } - - request := new(InternalTestRequest) - bytes, err := io.ReadAll(r.Body) - require.NoError(t, err) - require.NoError(t, json.Unmarshal(bytes, request)) - require.LessOrEqual(t, index, len(tc.giveStatusCodes)) - - statusCode := tc.giveStatusCodes[index] - - w.WriteHeader(statusCode) - - if tc.giveResponse != nil && statusCode == http.StatusOK { - bytes, err = json.Marshal(tc.giveResponse) - require.NoError(t, err) - _, err = w.Write(bytes) - require.NoError(t, err) - } - - index++ - }, - ), - ) -} - -// expectedRetryDurations holds an array of calculated retry durations, -// where the index of the array should correspond to the retry attempt. -// -// Values are calculated based off of `minRetryDelay * 2^i`. -var expectedRetryDurations = []time.Duration{ - 1000 * time.Millisecond, // 500ms * 2^1 = 1000ms - 2000 * time.Millisecond, // 500ms * 2^2 = 2000ms - 4000 * time.Millisecond, // 500ms * 2^3 = 4000ms - 8000 * time.Millisecond, // 500ms * 2^4 = 8000ms -} - -func TestRetryWithRequestBody(t *testing.T) { - // This test verifies that POST requests with a body are properly retried. - // The request body should be re-sent on each retry attempt. - expectedBody := `{"id":"test-id"}` - var requestBodies []string - var requestCount int - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestCount++ - bodyBytes, err := io.ReadAll(r.Body) - require.NoError(t, err) - requestBodies = append(requestBodies, string(bodyBytes)) - - if requestCount == 1 { - // First request - return retryable error - w.WriteHeader(http.StatusServiceUnavailable) - return - } - // Second request - return success - w.WriteHeader(http.StatusOK) - response := &InternalTestResponse{Id: "success"} - bytes, _ := json.Marshal(response) - _, _ = w.Write(bytes) - })) - defer server.Close() - - caller := NewCaller(&CallerParams{ - Client: server.Client(), - }) - - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL, - Method: http.MethodPost, - Request: &InternalTestRequest{Id: "test-id"}, - Response: &response, - MaxAttempts: 2, - ResponseIsOptional: true, - }, - ) - - require.NoError(t, err) - require.Equal(t, 2, requestCount, "Expected exactly 2 requests") - require.Len(t, requestBodies, 2, "Expected 2 request bodies to be captured") - - // Both requests should have the same non-empty body - assert.Equal(t, expectedBody, requestBodies[0], "First request body should match expected") - assert.Equal(t, expectedBody, requestBodies[1], "Second request body should match expected (retry should re-send body)") -} - -func TestRetryDelayTiming(t *testing.T) { - tests := []struct { - name string - headerName string - headerValueFunc func() string - expectedMinMs int64 - expectedMaxMs int64 - }{ - { - name: "retry-after with seconds value", - headerName: "retry-after", - headerValueFunc: func() string { - return "1" - }, - expectedMinMs: 500, - expectedMaxMs: 1500, - }, - { - name: "retry-after with HTTP date", - headerName: "retry-after", - headerValueFunc: func() string { - return time.Now().Add(3 * time.Second).Format(time.RFC1123) - }, - expectedMinMs: 1500, - expectedMaxMs: 4500, - }, - { - name: "x-ratelimit-reset with future timestamp", - headerName: "x-ratelimit-reset", - headerValueFunc: func() string { - return fmt.Sprintf("%d", time.Now().Add(3*time.Second).Unix()) - }, - expectedMinMs: 1500, - expectedMaxMs: 4500, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - var timestamps []time.Time - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - timestamps = append(timestamps, time.Now()) - if len(timestamps) == 1 { - // First request - return retryable error with header - w.Header().Set(tt.headerName, tt.headerValueFunc()) - w.WriteHeader(http.StatusTooManyRequests) - } else { - // Second request - return success - w.WriteHeader(http.StatusOK) - response := &InternalTestResponse{Id: "success"} - bytes, _ := json.Marshal(response) - _, _ = w.Write(bytes) - } - })) - defer server.Close() - - caller := NewCaller(&CallerParams{ - Client: server.Client(), - }) - - var response *InternalTestResponse - _, err := caller.Call( - context.Background(), - &CallParams{ - URL: server.URL, - Method: http.MethodGet, - Request: &InternalTestRequest{}, - Response: &response, - MaxAttempts: 2, - ResponseIsOptional: true, - }, - ) - - require.NoError(t, err) - require.Len(t, timestamps, 2, "Expected exactly 2 requests") - - actualDelayMs := timestamps[1].Sub(timestamps[0]).Milliseconds() - - assert.GreaterOrEqual(t, actualDelayMs, tt.expectedMinMs, - "Actual delay %dms should be >= expected min %dms", actualDelayMs, tt.expectedMinMs) - assert.LessOrEqual(t, actualDelayMs, tt.expectedMaxMs, - "Actual delay %dms should be <= expected max %dms", actualDelayMs, tt.expectedMaxMs) - }) - } -} diff --git a/seed/go-sdk/allof/internal/stringer.go b/seed/go-sdk/allof/internal/stringer.go deleted file mode 100644 index 312801851e0e..000000000000 --- a/seed/go-sdk/allof/internal/stringer.go +++ /dev/null @@ -1,13 +0,0 @@ -package internal - -import "encoding/json" - -// StringifyJSON returns a pretty JSON string representation of -// the given value. -func StringifyJSON(value interface{}) (string, error) { - bytes, err := json.MarshalIndent(value, "", " ") - if err != nil { - return "", err - } - return string(bytes), nil -} diff --git a/seed/go-sdk/allof/internal/time.go b/seed/go-sdk/allof/internal/time.go deleted file mode 100644 index 57f901a35ed8..000000000000 --- a/seed/go-sdk/allof/internal/time.go +++ /dev/null @@ -1,165 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "time" -) - -const dateFormat = "2006-01-02" - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date (e.g. 2006-01-02). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type Date struct { - t *time.Time -} - -// NewDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewDate(t time.Time) *Date { - return &Date{t: &t} -} - -// NewOptionalDate returns a new *Date. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDate(t *time.Time) *Date { - if t == nil { - return nil - } - return &Date{t: t} -} - -// Time returns the Date's underlying time, if any. If the -// date is nil, the zero value is returned. -func (d *Date) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the Date's underlying time.Time, if any. -func (d *Date) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *Date) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(dateFormat)) -} - -func (d *Date) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - parsedTime, err := time.Parse(dateFormat, raw) - if err != nil { - return err - } - - *d = Date{t: &parsedTime} - return nil -} - -// DateTime wraps time.Time and adapts its JSON representation -// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). -// -// Ref: https://ijmacd.github.io/rfc3339-iso8601 -type DateTime struct { - t *time.Time -} - -// NewDateTime returns a new *DateTime. -func NewDateTime(t time.Time) *DateTime { - return &DateTime{t: &t} -} - -// NewOptionalDateTime returns a new *DateTime. If the given time.Time -// is nil, nil will be returned. -func NewOptionalDateTime(t *time.Time) *DateTime { - if t == nil { - return nil - } - return &DateTime{t: t} -} - -// Time returns the DateTime's underlying time, if any. If the -// date-time is nil, the zero value is returned. -func (d *DateTime) Time() time.Time { - if d == nil || d.t == nil { - return time.Time{} - } - return *d.t -} - -// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. -func (d *DateTime) TimePtr() *time.Time { - if d == nil || d.t == nil { - return nil - } - if d.t.IsZero() { - return nil - } - return d.t -} - -func (d *DateTime) MarshalJSON() ([]byte, error) { - if d == nil || d.t == nil { - return nil, nil - } - return json.Marshal(d.t.Format(time.RFC3339)) -} - -func (d *DateTime) UnmarshalJSON(data []byte) error { - var raw string - if err := json.Unmarshal(data, &raw); err != nil { - // If the value is not a string, check if it is a number (unix epoch seconds). - var epoch int64 - if numErr := json.Unmarshal(data, &epoch); numErr == nil { - t := time.Unix(epoch, 0).UTC() - *d = DateTime{t: &t} - return nil - } - return err - } - - // Try RFC3339Nano first (superset of RFC3339, supports fractional seconds). - parsedTime, err := time.Parse(time.RFC3339Nano, raw) - if err == nil { - *d = DateTime{t: &parsedTime} - return nil - } - rfc3339NanoErr := err - - // Fall back to ISO 8601 without timezone (assume UTC). - parsedTime, err = time.Parse("2006-01-02T15:04:05", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - iso8601Err := err - - // Fall back to date-only format. - parsedTime, err = time.Parse("2006-01-02", raw) - if err == nil { - parsedTime = parsedTime.UTC() - *d = DateTime{t: &parsedTime} - return nil - } - dateOnlyErr := err - - return fmt.Errorf("unable to parse datetime string %q: tried RFC3339Nano (%v), ISO8601 (%v), date-only (%v)", raw, rfc3339NanoErr, iso8601Err, dateOnlyErr) -} diff --git a/seed/go-sdk/allof/option/request_option.go b/seed/go-sdk/allof/option/request_option.go deleted file mode 100644 index d9ffe1640240..000000000000 --- a/seed/go-sdk/allof/option/request_option.go +++ /dev/null @@ -1,73 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package option - -import ( - core "github.com/allof/fern/core" - http "net/http" - url "net/url" -) - -// RequestOption adapts the behavior of an individual request. -type RequestOption = core.RequestOption - -// WithBaseURL sets the base URL, overriding the default -// environment, if any. -func WithBaseURL(baseURL string) *core.BaseURLOption { - return &core.BaseURLOption{ - BaseURL: baseURL, - } -} - -// WithHTTPClient uses the given HTTPClient to issue the request. -func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { - return &core.HTTPClientOption{ - HTTPClient: httpClient, - } -} - -// WithHTTPHeader adds the given http.Header to the request. -func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { - return &core.HTTPHeaderOption{ - // Clone the headers so they can't be modified after the option call. - HTTPHeader: httpHeader.Clone(), - } -} - -// WithBodyProperties adds the given body properties to the request. -func WithBodyProperties(bodyProperties map[string]interface{}) *core.BodyPropertiesOption { - copiedBodyProperties := make(map[string]interface{}, len(bodyProperties)) - for key, value := range bodyProperties { - copiedBodyProperties[key] = value - } - return &core.BodyPropertiesOption{ - BodyProperties: copiedBodyProperties, - } -} - -// WithQueryParameters adds the given query parameters to the request. -func WithQueryParameters(queryParameters url.Values) *core.QueryParametersOption { - copiedQueryParameters := make(url.Values, len(queryParameters)) - for key, values := range queryParameters { - copiedQueryParameters[key] = values - } - return &core.QueryParametersOption{ - QueryParameters: copiedQueryParameters, - } -} - -// WithMaxAttempts configures the maximum number of retry attempts. -func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { - return &core.MaxAttemptsOption{ - MaxAttempts: attempts, - } -} - -// WithMaxStreamBufSize configures the maximum buffer size for streaming responses. -// This controls the maximum size of a single message (in bytes) that the stream -// can process. By default, this is set to 1MB. -func WithMaxStreamBufSize(size int) *core.MaxBufSizeOption { - return &core.MaxBufSizeOption{ - MaxBufSize: size, - } -} diff --git a/seed/go-sdk/allof/pointer.go b/seed/go-sdk/allof/pointer.go deleted file mode 100644 index f9a8177e734f..000000000000 --- a/seed/go-sdk/allof/pointer.go +++ /dev/null @@ -1,137 +0,0 @@ -package api - -import ( - "time" - - "github.com/google/uuid" -) - -// Bool returns a pointer to the given bool value. -func Bool(b bool) *bool { - return &b -} - -// Byte returns a pointer to the given byte value. -func Byte(b byte) *byte { - return &b -} - -// Bytes returns a pointer to the given []byte value. -func Bytes(b []byte) *[]byte { - return &b -} - -// Complex64 returns a pointer to the given complex64 value. -func Complex64(c complex64) *complex64 { - return &c -} - -// Complex128 returns a pointer to the given complex128 value. -func Complex128(c complex128) *complex128 { - return &c -} - -// Float32 returns a pointer to the given float32 value. -func Float32(f float32) *float32 { - return &f -} - -// Float64 returns a pointer to the given float64 value. -func Float64(f float64) *float64 { - return &f -} - -// Int returns a pointer to the given int value. -func Int(i int) *int { - return &i -} - -// Int8 returns a pointer to the given int8 value. -func Int8(i int8) *int8 { - return &i -} - -// Int16 returns a pointer to the given int16 value. -func Int16(i int16) *int16 { - return &i -} - -// Int32 returns a pointer to the given int32 value. -func Int32(i int32) *int32 { - return &i -} - -// Int64 returns a pointer to the given int64 value. -func Int64(i int64) *int64 { - return &i -} - -// Rune returns a pointer to the given rune value. -func Rune(r rune) *rune { - return &r -} - -// String returns a pointer to the given string value. -func String(s string) *string { - return &s -} - -// Uint returns a pointer to the given uint value. -func Uint(u uint) *uint { - return &u -} - -// Uint8 returns a pointer to the given uint8 value. -func Uint8(u uint8) *uint8 { - return &u -} - -// Uint16 returns a pointer to the given uint16 value. -func Uint16(u uint16) *uint16 { - return &u -} - -// Uint32 returns a pointer to the given uint32 value. -func Uint32(u uint32) *uint32 { - return &u -} - -// Uint64 returns a pointer to the given uint64 value. -func Uint64(u uint64) *uint64 { - return &u -} - -// Uintptr returns a pointer to the given uintptr value. -func Uintptr(u uintptr) *uintptr { - return &u -} - -// UUID returns a pointer to the given uuid.UUID value. -func UUID(u uuid.UUID) *uuid.UUID { - return &u -} - -// Time returns a pointer to the given time.Time value. -func Time(t time.Time) *time.Time { - return &t -} - -// MustParseDate attempts to parse the given string as a -// date time.Time, and panics upon failure. -func MustParseDate(date string) time.Time { - t, err := time.Parse("2006-01-02", date) - if err != nil { - panic(err) - } - return t -} - -// MustParseDateTime attempts to parse the given string as a -// datetime time.Time, and panics upon failure. -func MustParseDateTime(datetime string) time.Time { - t, err := time.Parse(time.RFC3339, datetime) - if err != nil { - panic(err) - } - return t -} diff --git a/seed/go-sdk/allof/pointer_test.go b/seed/go-sdk/allof/pointer_test.go deleted file mode 100644 index 06d62d2410b2..000000000000 --- a/seed/go-sdk/allof/pointer_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package api - -import ( - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestBool(t *testing.T) { - value := true - ptr := Bool(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestByte(t *testing.T) { - value := byte(42) - ptr := Byte(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestComplex64(t *testing.T) { - value := complex64(1 + 2i) - ptr := Complex64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestComplex128(t *testing.T) { - value := complex128(1 + 2i) - ptr := Complex128(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestFloat32(t *testing.T) { - value := float32(3.14) - ptr := Float32(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestFloat64(t *testing.T) { - value := 3.14159 - ptr := Float64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt(t *testing.T) { - value := 42 - ptr := Int(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt8(t *testing.T) { - value := int8(42) - ptr := Int8(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt16(t *testing.T) { - value := int16(42) - ptr := Int16(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt32(t *testing.T) { - value := int32(42) - ptr := Int32(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestInt64(t *testing.T) { - value := int64(42) - ptr := Int64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestRune(t *testing.T) { - value := 'A' - ptr := Rune(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestString(t *testing.T) { - value := "hello" - ptr := String(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint(t *testing.T) { - value := uint(42) - ptr := Uint(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint8(t *testing.T) { - value := uint8(42) - ptr := Uint8(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint16(t *testing.T) { - value := uint16(42) - ptr := Uint16(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint32(t *testing.T) { - value := uint32(42) - ptr := Uint32(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUint64(t *testing.T) { - value := uint64(42) - ptr := Uint64(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUintptr(t *testing.T) { - value := uintptr(42) - ptr := Uintptr(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestUUID(t *testing.T) { - value := uuid.New() - ptr := UUID(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestTime(t *testing.T) { - value := time.Now() - ptr := Time(value) - assert.NotNil(t, ptr) - assert.Equal(t, value, *ptr) -} - -func TestMustParseDate(t *testing.T) { - t.Run("valid date", func(t *testing.T) { - result := MustParseDate("2024-01-15") - expected, _ := time.Parse("2006-01-02", "2024-01-15") - assert.Equal(t, expected, result) - }) - - t.Run("invalid date panics", func(t *testing.T) { - assert.Panics(t, func() { - MustParseDate("invalid-date") - }) - }) -} - -func TestMustParseDateTime(t *testing.T) { - t.Run("valid datetime", func(t *testing.T) { - result := MustParseDateTime("2024-01-15T10:30:00Z") - expected, _ := time.Parse(time.RFC3339, "2024-01-15T10:30:00Z") - assert.Equal(t, expected, result) - }) - - t.Run("invalid datetime panics", func(t *testing.T) { - assert.Panics(t, func() { - MustParseDateTime("invalid-datetime") - }) - }) -} - -func TestPointerHelpersWithZeroValues(t *testing.T) { - t.Run("zero bool", func(t *testing.T) { - ptr := Bool(false) - assert.NotNil(t, ptr) - assert.Equal(t, false, *ptr) - }) - - t.Run("zero int", func(t *testing.T) { - ptr := Int(0) - assert.NotNil(t, ptr) - assert.Equal(t, 0, *ptr) - }) - - t.Run("empty string", func(t *testing.T) { - ptr := String("") - assert.NotNil(t, ptr) - assert.Equal(t, "", *ptr) - }) - - t.Run("zero time", func(t *testing.T) { - zeroTime := time.Time{} - ptr := Time(zeroTime) - assert.NotNil(t, ptr) - assert.Equal(t, zeroTime, *ptr) - }) -} diff --git a/seed/go-sdk/allof/reference.md b/seed/go-sdk/allof/reference.md deleted file mode 100644 index 86fef51f321b..000000000000 --- a/seed/go-sdk/allof/reference.md +++ /dev/null @@ -1,186 +0,0 @@ -# Reference -
client.SearchRuleTypes() -> *fern.RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -request := &fern.SearchRuleTypesRequest{} -client.SearchRuleTypes( - context.TODO(), - request, - ) -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `*string` - -
-
-
-
- - -
-
-
- -
client.CreateRule(request) -> *fern.RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -request := &fern.RuleCreateRequest{ - Name: "name", - ExecutionContext: fern.RuleExecutionContextProd, - } -client.CreateRule( - context.TODO(), - request, - ) -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `string` - -
-
- -
-
- -**executionContext:** `*fern.RuleExecutionContext` - -
-
-
-
- - -
-
-
- -
client.ListUsers() -> *fern.UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -client.ListUsers( - context.TODO(), - ) -} -``` -
-
-
-
- - -
-
-
- -
client.GetEntity() -> *fern.CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -client.GetEntity( - context.TODO(), - ) -} -``` -
-
-
-
- - -
-
-
- -
client.GetOrganization() -> *fern.Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```go -client.GetOrganization( - context.TODO(), - ) -} -``` -
-
-
-
- - -
-
-
- diff --git a/seed/go-sdk/allof/snippet.json b/seed/go-sdk/allof/snippet.json deleted file mode 100644 index 900f2d6c1dbc..000000000000 --- a/seed/go-sdk/allof/snippet.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "endpoints": [ - { - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetEntity(\n\tcontext.TODO(),\n)\n" - } - }, - { - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.GetOrganization(\n\tcontext.TODO(),\n)\n" - } - }, - { - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof/fern\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.SearchRuleTypes(\n\tcontext.TODO(),\n\t\u0026fern.SearchRuleTypesRequest{},\n)\n" - } - }, - { - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/allof/fern\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.CreateRule(\n\tcontext.TODO(),\n\t\u0026fern.RuleCreateRequest{\n\t\tName: \"name\",\n\t\tExecutionContext: fern.RuleExecutionContextProd,\n\t},\n)\n" - } - }, - { - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "go", - "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/allof/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.ListUsers(\n\tcontext.TODO(),\n)\n" - } - } - ] -} \ No newline at end of file diff --git a/seed/go-sdk/allof/types.go b/seed/go-sdk/allof/types.go deleted file mode 100644 index b3827eba21fc..000000000000 --- a/seed/go-sdk/allof/types.go +++ /dev/null @@ -1,1989 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - json "encoding/json" - fmt "fmt" - internal "github.com/allof/fern/internal" - big "math/big" - time "time" -) - -var ( - ruleCreateRequestFieldName = big.NewInt(1 << 0) - ruleCreateRequestFieldExecutionContext = big.NewInt(1 << 1) -) - -type RuleCreateRequest struct { - Name string `json:"name" url:"-"` - ExecutionContext RuleExecutionContext `json:"executionContext" url:"-"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` -} - -func (r *RuleCreateRequest) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleCreateRequest) SetName(name string) { - r.Name = name - r.require(ruleCreateRequestFieldName) -} - -// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleCreateRequest) SetExecutionContext(executionContext RuleExecutionContext) { - r.ExecutionContext = executionContext - r.require(ruleCreateRequestFieldExecutionContext) -} - -func (r *RuleCreateRequest) UnmarshalJSON(data []byte) error { - type unmarshaler RuleCreateRequest - var body unmarshaler - if err := json.Unmarshal(data, &body); err != nil { - return err - } - *r = RuleCreateRequest(body) - return nil -} - -func (r *RuleCreateRequest) MarshalJSON() ([]byte, error) { - type embed RuleCreateRequest - var marshaler = struct { - embed - }{ - embed: embed(*r), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -var ( - searchRuleTypesRequestFieldQuery = big.NewInt(1 << 0) -) - -type SearchRuleTypesRequest struct { - Query *string `json:"-" url:"query,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` -} - -func (s *SearchRuleTypesRequest) require(field *big.Int) { - if s.explicitFields == nil { - s.explicitFields = big.NewInt(0) - } - s.explicitFields.Or(s.explicitFields, field) -} - -// SetQuery sets the Query field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (s *SearchRuleTypesRequest) SetQuery(query *string) { - s.Query = query - s.require(searchRuleTypesRequestFieldQuery) -} - -// Common audit metadata. -var ( - auditInfoFieldCreatedBy = big.NewInt(1 << 0) - auditInfoFieldCreatedDateTime = big.NewInt(1 << 1) - auditInfoFieldModifiedBy = big.NewInt(1 << 2) - auditInfoFieldModifiedDateTime = big.NewInt(1 << 3) -) - -type AuditInfo struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (a *AuditInfo) GetCreatedBy() *string { - if a == nil { - return nil - } - return a.CreatedBy -} - -func (a *AuditInfo) GetCreatedDateTime() *time.Time { - if a == nil { - return nil - } - return a.CreatedDateTime -} - -func (a *AuditInfo) GetModifiedBy() *string { - if a == nil { - return nil - } - return a.ModifiedBy -} - -func (a *AuditInfo) GetModifiedDateTime() *time.Time { - if a == nil { - return nil - } - return a.ModifiedDateTime -} - -func (a *AuditInfo) GetExtraProperties() map[string]interface{} { - if a == nil { - return nil - } - return a.extraProperties -} - -func (a *AuditInfo) require(field *big.Int) { - if a.explicitFields == nil { - a.explicitFields = big.NewInt(0) - } - a.explicitFields.Or(a.explicitFields, field) -} - -// SetCreatedBy sets the CreatedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetCreatedBy(createdBy *string) { - a.CreatedBy = createdBy - a.require(auditInfoFieldCreatedBy) -} - -// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetCreatedDateTime(createdDateTime *time.Time) { - a.CreatedDateTime = createdDateTime - a.require(auditInfoFieldCreatedDateTime) -} - -// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetModifiedBy(modifiedBy *string) { - a.ModifiedBy = modifiedBy - a.require(auditInfoFieldModifiedBy) -} - -// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (a *AuditInfo) SetModifiedDateTime(modifiedDateTime *time.Time) { - a.ModifiedDateTime = modifiedDateTime - a.require(auditInfoFieldModifiedDateTime) -} - -func (a *AuditInfo) UnmarshalJSON(data []byte) error { - type embed AuditInfo - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*a), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *a = AuditInfo(unmarshaler.embed) - a.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - a.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *a) - if err != nil { - return err - } - a.extraProperties = extraProperties - a.rawJSON = json.RawMessage(data) - return nil -} - -func (a *AuditInfo) MarshalJSON() ([]byte, error) { - type embed AuditInfo - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*a), - CreatedDateTime: internal.NewOptionalDateTime(a.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(a.ModifiedDateTime), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, a.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (a *AuditInfo) String() string { - if a == nil { - return "" - } - if len(a.rawJSON) > 0 { - if value, err := internal.StringifyJSON(a.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(a); err == nil { - return value - } - return fmt.Sprintf("%#v", a) -} - -var ( - baseOrgFieldID = big.NewInt(1 << 0) - baseOrgFieldMetadata = big.NewInt(1 << 1) -) - -type BaseOrg struct { - ID string `json:"id" url:"id"` - Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (b *BaseOrg) GetID() string { - if b == nil { - return "" - } - return b.ID -} - -func (b *BaseOrg) GetMetadata() *BaseOrgMetadata { - if b == nil { - return nil - } - return b.Metadata -} - -func (b *BaseOrg) GetExtraProperties() map[string]interface{} { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrg) require(field *big.Int) { - if b.explicitFields == nil { - b.explicitFields = big.NewInt(0) - } - b.explicitFields.Or(b.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrg) SetID(id string) { - b.ID = id - b.require(baseOrgFieldID) -} - -// SetMetadata sets the Metadata field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrg) SetMetadata(metadata *BaseOrgMetadata) { - b.Metadata = metadata - b.require(baseOrgFieldMetadata) -} - -func (b *BaseOrg) UnmarshalJSON(data []byte) error { - type unmarshaler BaseOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrg) MarshalJSON() ([]byte, error) { - type embed BaseOrg - var marshaler = struct { - embed - }{ - embed: embed(*b), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (b *BaseOrg) String() string { - if b == nil { - return "" - } - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -var ( - baseOrgMetadataFieldRegion = big.NewInt(1 << 0) - baseOrgMetadataFieldTier = big.NewInt(1 << 1) -) - -type BaseOrgMetadata struct { - // Deployment region from BaseOrg. - Region string `json:"region" url:"region"` - // Subscription tier. - Tier *string `json:"tier,omitempty" url:"tier,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (b *BaseOrgMetadata) GetRegion() string { - if b == nil { - return "" - } - return b.Region -} - -func (b *BaseOrgMetadata) GetTier() *string { - if b == nil { - return nil - } - return b.Tier -} - -func (b *BaseOrgMetadata) GetExtraProperties() map[string]interface{} { - if b == nil { - return nil - } - return b.extraProperties -} - -func (b *BaseOrgMetadata) require(field *big.Int) { - if b.explicitFields == nil { - b.explicitFields = big.NewInt(0) - } - b.explicitFields.Or(b.explicitFields, field) -} - -// SetRegion sets the Region field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrgMetadata) SetRegion(region string) { - b.Region = region - b.require(baseOrgMetadataFieldRegion) -} - -// SetTier sets the Tier field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (b *BaseOrgMetadata) SetTier(tier *string) { - b.Tier = tier - b.require(baseOrgMetadataFieldTier) -} - -func (b *BaseOrgMetadata) UnmarshalJSON(data []byte) error { - type unmarshaler BaseOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *b = BaseOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *b) - if err != nil { - return err - } - b.extraProperties = extraProperties - b.rawJSON = json.RawMessage(data) - return nil -} - -func (b *BaseOrgMetadata) MarshalJSON() ([]byte, error) { - type embed BaseOrgMetadata - var marshaler = struct { - embed - }{ - embed: embed(*b), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, b.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (b *BaseOrgMetadata) String() string { - if b == nil { - return "" - } - if len(b.rawJSON) > 0 { - if value, err := internal.StringifyJSON(b.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(b); err == nil { - return value - } - return fmt.Sprintf("%#v", b) -} - -var ( - combinedEntityFieldStatus = big.NewInt(1 << 0) - combinedEntityFieldID = big.NewInt(1 << 1) - combinedEntityFieldName = big.NewInt(1 << 2) - combinedEntityFieldSummary = big.NewInt(1 << 3) -) - -type CombinedEntity struct { - Status CombinedEntityStatus `json:"status" url:"status"` - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Identifiable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (c *CombinedEntity) GetStatus() CombinedEntityStatus { - if c == nil { - return "" - } - return c.Status -} - -func (c *CombinedEntity) GetID() string { - if c == nil { - return "" - } - return c.ID -} - -func (c *CombinedEntity) GetName() *string { - if c == nil { - return nil - } - return c.Name -} - -func (c *CombinedEntity) GetSummary() *string { - if c == nil { - return nil - } - return c.Summary -} - -func (c *CombinedEntity) GetExtraProperties() map[string]interface{} { - if c == nil { - return nil - } - return c.extraProperties -} - -func (c *CombinedEntity) require(field *big.Int) { - if c.explicitFields == nil { - c.explicitFields = big.NewInt(0) - } - c.explicitFields.Or(c.explicitFields, field) -} - -// SetStatus sets the Status field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetStatus(status CombinedEntityStatus) { - c.Status = status - c.require(combinedEntityFieldStatus) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetID(id string) { - c.ID = id - c.require(combinedEntityFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetName(name *string) { - c.Name = name - c.require(combinedEntityFieldName) -} - -// SetSummary sets the Summary field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (c *CombinedEntity) SetSummary(summary *string) { - c.Summary = summary - c.require(combinedEntityFieldSummary) -} - -func (c *CombinedEntity) UnmarshalJSON(data []byte) error { - type unmarshaler CombinedEntity - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *c = CombinedEntity(value) - extraProperties, err := internal.ExtractExtraProperties(data, *c) - if err != nil { - return err - } - c.extraProperties = extraProperties - c.rawJSON = json.RawMessage(data) - return nil -} - -func (c *CombinedEntity) MarshalJSON() ([]byte, error) { - type embed CombinedEntity - var marshaler = struct { - embed - }{ - embed: embed(*c), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, c.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (c *CombinedEntity) String() string { - if c == nil { - return "" - } - if len(c.rawJSON) > 0 { - if value, err := internal.StringifyJSON(c.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(c); err == nil { - return value - } - return fmt.Sprintf("%#v", c) -} - -type CombinedEntityStatus string - -const ( - CombinedEntityStatusActive CombinedEntityStatus = "active" - CombinedEntityStatusArchived CombinedEntityStatus = "archived" -) - -func NewCombinedEntityStatusFromString(s string) (CombinedEntityStatus, error) { - switch s { - case "active": - return CombinedEntityStatusActive, nil - case "archived": - return CombinedEntityStatusArchived, nil - } - var t CombinedEntityStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (c CombinedEntityStatus) Ptr() *CombinedEntityStatus { - return &c -} - -var ( - describableFieldName = big.NewInt(1 << 0) - describableFieldSummary = big.NewInt(1 << 1) -) - -type Describable struct { - // Display name from Describable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - // A short summary. - Summary *string `json:"summary,omitempty" url:"summary,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (d *Describable) GetName() *string { - if d == nil { - return nil - } - return d.Name -} - -func (d *Describable) GetSummary() *string { - if d == nil { - return nil - } - return d.Summary -} - -func (d *Describable) GetExtraProperties() map[string]interface{} { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *Describable) require(field *big.Int) { - if d.explicitFields == nil { - d.explicitFields = big.NewInt(0) - } - d.explicitFields.Or(d.explicitFields, field) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *Describable) SetName(name *string) { - d.Name = name - d.require(describableFieldName) -} - -// SetSummary sets the Summary field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *Describable) SetSummary(summary *string) { - d.Summary = summary - d.require(describableFieldSummary) -} - -func (d *Describable) UnmarshalJSON(data []byte) error { - type unmarshaler Describable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = Describable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *Describable) MarshalJSON() ([]byte, error) { - type embed Describable - var marshaler = struct { - embed - }{ - embed: embed(*d), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (d *Describable) String() string { - if d == nil { - return "" - } - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -var ( - detailedOrgFieldMetadata = big.NewInt(1 << 0) -) - -type DetailedOrg struct { - Metadata *DetailedOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (d *DetailedOrg) GetMetadata() *DetailedOrgMetadata { - if d == nil { - return nil - } - return d.Metadata -} - -func (d *DetailedOrg) GetExtraProperties() map[string]interface{} { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrg) require(field *big.Int) { - if d.explicitFields == nil { - d.explicitFields = big.NewInt(0) - } - d.explicitFields.Or(d.explicitFields, field) -} - -// SetMetadata sets the Metadata field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *DetailedOrg) SetMetadata(metadata *DetailedOrgMetadata) { - d.Metadata = metadata - d.require(detailedOrgFieldMetadata) -} - -func (d *DetailedOrg) UnmarshalJSON(data []byte) error { - type unmarshaler DetailedOrg - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrg(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrg) MarshalJSON() ([]byte, error) { - type embed DetailedOrg - var marshaler = struct { - embed - }{ - embed: embed(*d), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (d *DetailedOrg) String() string { - if d == nil { - return "" - } - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -var ( - detailedOrgMetadataFieldRegion = big.NewInt(1 << 0) - detailedOrgMetadataFieldDomain = big.NewInt(1 << 1) -) - -type DetailedOrgMetadata struct { - // Deployment region from DetailedOrg. - Region string `json:"region" url:"region"` - // Custom domain name. - Domain *string `json:"domain,omitempty" url:"domain,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (d *DetailedOrgMetadata) GetRegion() string { - if d == nil { - return "" - } - return d.Region -} - -func (d *DetailedOrgMetadata) GetDomain() *string { - if d == nil { - return nil - } - return d.Domain -} - -func (d *DetailedOrgMetadata) GetExtraProperties() map[string]interface{} { - if d == nil { - return nil - } - return d.extraProperties -} - -func (d *DetailedOrgMetadata) require(field *big.Int) { - if d.explicitFields == nil { - d.explicitFields = big.NewInt(0) - } - d.explicitFields.Or(d.explicitFields, field) -} - -// SetRegion sets the Region field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *DetailedOrgMetadata) SetRegion(region string) { - d.Region = region - d.require(detailedOrgMetadataFieldRegion) -} - -// SetDomain sets the Domain field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (d *DetailedOrgMetadata) SetDomain(domain *string) { - d.Domain = domain - d.require(detailedOrgMetadataFieldDomain) -} - -func (d *DetailedOrgMetadata) UnmarshalJSON(data []byte) error { - type unmarshaler DetailedOrgMetadata - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *d = DetailedOrgMetadata(value) - extraProperties, err := internal.ExtractExtraProperties(data, *d) - if err != nil { - return err - } - d.extraProperties = extraProperties - d.rawJSON = json.RawMessage(data) - return nil -} - -func (d *DetailedOrgMetadata) MarshalJSON() ([]byte, error) { - type embed DetailedOrgMetadata - var marshaler = struct { - embed - }{ - embed: embed(*d), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, d.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (d *DetailedOrgMetadata) String() string { - if d == nil { - return "" - } - if len(d.rawJSON) > 0 { - if value, err := internal.StringifyJSON(d.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(d); err == nil { - return value - } - return fmt.Sprintf("%#v", d) -} - -var ( - identifiableFieldID = big.NewInt(1 << 0) - identifiableFieldName = big.NewInt(1 << 1) -) - -type Identifiable struct { - // Unique identifier. - ID string `json:"id" url:"id"` - // Display name from Identifiable. - Name *string `json:"name,omitempty" url:"name,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (i *Identifiable) GetID() string { - if i == nil { - return "" - } - return i.ID -} - -func (i *Identifiable) GetName() *string { - if i == nil { - return nil - } - return i.Name -} - -func (i *Identifiable) GetExtraProperties() map[string]interface{} { - if i == nil { - return nil - } - return i.extraProperties -} - -func (i *Identifiable) require(field *big.Int) { - if i.explicitFields == nil { - i.explicitFields = big.NewInt(0) - } - i.explicitFields.Or(i.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (i *Identifiable) SetID(id string) { - i.ID = id - i.require(identifiableFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (i *Identifiable) SetName(name *string) { - i.Name = name - i.require(identifiableFieldName) -} - -func (i *Identifiable) UnmarshalJSON(data []byte) error { - type unmarshaler Identifiable - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *i = Identifiable(value) - extraProperties, err := internal.ExtractExtraProperties(data, *i) - if err != nil { - return err - } - i.extraProperties = extraProperties - i.rawJSON = json.RawMessage(data) - return nil -} - -func (i *Identifiable) MarshalJSON() ([]byte, error) { - type embed Identifiable - var marshaler = struct { - embed - }{ - embed: embed(*i), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, i.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (i *Identifiable) String() string { - if i == nil { - return "" - } - if len(i.rawJSON) > 0 { - if value, err := internal.StringifyJSON(i.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(i); err == nil { - return value - } - return fmt.Sprintf("%#v", i) -} - -var ( - organizationFieldName = big.NewInt(1 << 0) - organizationFieldID = big.NewInt(1 << 1) - organizationFieldMetadata = big.NewInt(1 << 2) -) - -type Organization struct { - Name string `json:"name" url:"name"` - ID string `json:"id" url:"id"` - Metadata *BaseOrgMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (o *Organization) GetName() string { - if o == nil { - return "" - } - return o.Name -} - -func (o *Organization) GetID() string { - if o == nil { - return "" - } - return o.ID -} - -func (o *Organization) GetMetadata() *BaseOrgMetadata { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *Organization) GetExtraProperties() map[string]interface{} { - if o == nil { - return nil - } - return o.extraProperties -} - -func (o *Organization) require(field *big.Int) { - if o.explicitFields == nil { - o.explicitFields = big.NewInt(0) - } - o.explicitFields.Or(o.explicitFields, field) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *Organization) SetName(name string) { - o.Name = name - o.require(organizationFieldName) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *Organization) SetID(id string) { - o.ID = id - o.require(organizationFieldID) -} - -// SetMetadata sets the Metadata field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (o *Organization) SetMetadata(metadata *BaseOrgMetadata) { - o.Metadata = metadata - o.require(organizationFieldMetadata) -} - -func (o *Organization) UnmarshalJSON(data []byte) error { - type unmarshaler Organization - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *o = Organization(value) - extraProperties, err := internal.ExtractExtraProperties(data, *o) - if err != nil { - return err - } - o.extraProperties = extraProperties - o.rawJSON = json.RawMessage(data) - return nil -} - -func (o *Organization) MarshalJSON() ([]byte, error) { - type embed Organization - var marshaler = struct { - embed - }{ - embed: embed(*o), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, o.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (o *Organization) String() string { - if o == nil { - return "" - } - if len(o.rawJSON) > 0 { - if value, err := internal.StringifyJSON(o.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(o); err == nil { - return value - } - return fmt.Sprintf("%#v", o) -} - -var ( - paginatedResultFieldPaging = big.NewInt(1 << 0) - paginatedResultFieldResults = big.NewInt(1 << 1) -) - -type PaginatedResult struct { - Paging *PagingCursors `json:"paging" url:"paging"` - // Current page of results from the requested resource. - Results []any `json:"results" url:"results"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (p *PaginatedResult) GetPaging() *PagingCursors { - if p == nil { - return nil - } - return p.Paging -} - -func (p *PaginatedResult) GetResults() []any { - if p == nil { - return nil - } - return p.Results -} - -func (p *PaginatedResult) GetExtraProperties() map[string]interface{} { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PaginatedResult) require(field *big.Int) { - if p.explicitFields == nil { - p.explicitFields = big.NewInt(0) - } - p.explicitFields.Or(p.explicitFields, field) -} - -// SetPaging sets the Paging field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PaginatedResult) SetPaging(paging *PagingCursors) { - p.Paging = paging - p.require(paginatedResultFieldPaging) -} - -// SetResults sets the Results field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PaginatedResult) SetResults(results []any) { - p.Results = results - p.require(paginatedResultFieldResults) -} - -func (p *PaginatedResult) UnmarshalJSON(data []byte) error { - type unmarshaler PaginatedResult - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PaginatedResult(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PaginatedResult) MarshalJSON() ([]byte, error) { - type embed PaginatedResult - var marshaler = struct { - embed - }{ - embed: embed(*p), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (p *PaginatedResult) String() string { - if p == nil { - return "" - } - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -var ( - pagingCursorsFieldNext = big.NewInt(1 << 0) - pagingCursorsFieldPrevious = big.NewInt(1 << 1) -) - -type PagingCursors struct { - // Cursor for the next page of results. - Next string `json:"next" url:"next"` - // Cursor for the previous page of results. - Previous *string `json:"previous,omitempty" url:"previous,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (p *PagingCursors) GetNext() string { - if p == nil { - return "" - } - return p.Next -} - -func (p *PagingCursors) GetPrevious() *string { - if p == nil { - return nil - } - return p.Previous -} - -func (p *PagingCursors) GetExtraProperties() map[string]interface{} { - if p == nil { - return nil - } - return p.extraProperties -} - -func (p *PagingCursors) require(field *big.Int) { - if p.explicitFields == nil { - p.explicitFields = big.NewInt(0) - } - p.explicitFields.Or(p.explicitFields, field) -} - -// SetNext sets the Next field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PagingCursors) SetNext(next string) { - p.Next = next - p.require(pagingCursorsFieldNext) -} - -// SetPrevious sets the Previous field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (p *PagingCursors) SetPrevious(previous *string) { - p.Previous = previous - p.require(pagingCursorsFieldPrevious) -} - -func (p *PagingCursors) UnmarshalJSON(data []byte) error { - type unmarshaler PagingCursors - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *p = PagingCursors(value) - extraProperties, err := internal.ExtractExtraProperties(data, *p) - if err != nil { - return err - } - p.extraProperties = extraProperties - p.rawJSON = json.RawMessage(data) - return nil -} - -func (p *PagingCursors) MarshalJSON() ([]byte, error) { - type embed PagingCursors - var marshaler = struct { - embed - }{ - embed: embed(*p), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, p.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (p *PagingCursors) String() string { - if p == nil { - return "" - } - if len(p.rawJSON) > 0 { - if value, err := internal.StringifyJSON(p.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(p); err == nil { - return value - } - return fmt.Sprintf("%#v", p) -} - -// Execution environment for a rule. -type RuleExecutionContext string - -const ( - RuleExecutionContextProd RuleExecutionContext = "prod" - RuleExecutionContextStaging RuleExecutionContext = "staging" - RuleExecutionContextDev RuleExecutionContext = "dev" -) - -func NewRuleExecutionContextFromString(s string) (RuleExecutionContext, error) { - switch s { - case "prod": - return RuleExecutionContextProd, nil - case "staging": - return RuleExecutionContextStaging, nil - case "dev": - return RuleExecutionContextDev, nil - } - var t RuleExecutionContext - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleExecutionContext) Ptr() *RuleExecutionContext { - return &r -} - -var ( - ruleResponseFieldCreatedBy = big.NewInt(1 << 0) - ruleResponseFieldCreatedDateTime = big.NewInt(1 << 1) - ruleResponseFieldModifiedBy = big.NewInt(1 << 2) - ruleResponseFieldModifiedDateTime = big.NewInt(1 << 3) - ruleResponseFieldID = big.NewInt(1 << 4) - ruleResponseFieldName = big.NewInt(1 << 5) - ruleResponseFieldStatus = big.NewInt(1 << 6) - ruleResponseFieldExecutionContext = big.NewInt(1 << 7) -) - -type RuleResponse struct { - // The user who created this resource. - CreatedBy *string `json:"createdBy,omitempty" url:"createdBy,omitempty"` - // When this resource was created. - CreatedDateTime *time.Time `json:"createdDateTime,omitempty" url:"createdDateTime,omitempty"` - // The user who last modified this resource. - ModifiedBy *string `json:"modifiedBy,omitempty" url:"modifiedBy,omitempty"` - // When this resource was last modified. - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty" url:"modifiedDateTime,omitempty"` - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Status RuleResponseStatus `json:"status" url:"status"` - ExecutionContext *RuleExecutionContext `json:"executionContext,omitempty" url:"executionContext,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (r *RuleResponse) GetCreatedBy() *string { - if r == nil { - return nil - } - return r.CreatedBy -} - -func (r *RuleResponse) GetCreatedDateTime() *time.Time { - if r == nil { - return nil - } - return r.CreatedDateTime -} - -func (r *RuleResponse) GetModifiedBy() *string { - if r == nil { - return nil - } - return r.ModifiedBy -} - -func (r *RuleResponse) GetModifiedDateTime() *time.Time { - if r == nil { - return nil - } - return r.ModifiedDateTime -} - -func (r *RuleResponse) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleResponse) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleResponse) GetStatus() RuleResponseStatus { - if r == nil { - return "" - } - return r.Status -} - -func (r *RuleResponse) GetExecutionContext() *RuleExecutionContext { - if r == nil { - return nil - } - return r.ExecutionContext -} - -func (r *RuleResponse) GetExtraProperties() map[string]interface{} { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleResponse) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetCreatedBy sets the CreatedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetCreatedBy(createdBy *string) { - r.CreatedBy = createdBy - r.require(ruleResponseFieldCreatedBy) -} - -// SetCreatedDateTime sets the CreatedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetCreatedDateTime(createdDateTime *time.Time) { - r.CreatedDateTime = createdDateTime - r.require(ruleResponseFieldCreatedDateTime) -} - -// SetModifiedBy sets the ModifiedBy field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetModifiedBy(modifiedBy *string) { - r.ModifiedBy = modifiedBy - r.require(ruleResponseFieldModifiedBy) -} - -// SetModifiedDateTime sets the ModifiedDateTime field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetModifiedDateTime(modifiedDateTime *time.Time) { - r.ModifiedDateTime = modifiedDateTime - r.require(ruleResponseFieldModifiedDateTime) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetID(id string) { - r.ID = id - r.require(ruleResponseFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetName(name string) { - r.Name = name - r.require(ruleResponseFieldName) -} - -// SetStatus sets the Status field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetStatus(status RuleResponseStatus) { - r.Status = status - r.require(ruleResponseFieldStatus) -} - -// SetExecutionContext sets the ExecutionContext field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleResponse) SetExecutionContext(executionContext *RuleExecutionContext) { - r.ExecutionContext = executionContext - r.require(ruleResponseFieldExecutionContext) -} - -func (r *RuleResponse) UnmarshalJSON(data []byte) error { - type embed RuleResponse - var unmarshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*r), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *r = RuleResponse(unmarshaler.embed) - r.CreatedDateTime = unmarshaler.CreatedDateTime.TimePtr() - r.ModifiedDateTime = unmarshaler.ModifiedDateTime.TimePtr() - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleResponse) MarshalJSON() ([]byte, error) { - type embed RuleResponse - var marshaler = struct { - embed - CreatedDateTime *internal.DateTime `json:"createdDateTime,omitempty"` - ModifiedDateTime *internal.DateTime `json:"modifiedDateTime,omitempty"` - }{ - embed: embed(*r), - CreatedDateTime: internal.NewOptionalDateTime(r.CreatedDateTime), - ModifiedDateTime: internal.NewOptionalDateTime(r.ModifiedDateTime), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (r *RuleResponse) String() string { - if r == nil { - return "" - } - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -type RuleResponseStatus string - -const ( - RuleResponseStatusActive RuleResponseStatus = "active" - RuleResponseStatusInactive RuleResponseStatus = "inactive" - RuleResponseStatusDraft RuleResponseStatus = "draft" -) - -func NewRuleResponseStatusFromString(s string) (RuleResponseStatus, error) { - switch s { - case "active": - return RuleResponseStatusActive, nil - case "inactive": - return RuleResponseStatusInactive, nil - case "draft": - return RuleResponseStatusDraft, nil - } - var t RuleResponseStatus - return "", fmt.Errorf("%s is not a valid %T", s, t) -} - -func (r RuleResponseStatus) Ptr() *RuleResponseStatus { - return &r -} - -var ( - ruleTypeFieldID = big.NewInt(1 << 0) - ruleTypeFieldName = big.NewInt(1 << 1) - ruleTypeFieldDescription = big.NewInt(1 << 2) -) - -type RuleType struct { - ID string `json:"id" url:"id"` - Name string `json:"name" url:"name"` - Description *string `json:"description,omitempty" url:"description,omitempty"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (r *RuleType) GetID() string { - if r == nil { - return "" - } - return r.ID -} - -func (r *RuleType) GetName() string { - if r == nil { - return "" - } - return r.Name -} - -func (r *RuleType) GetDescription() *string { - if r == nil { - return nil - } - return r.Description -} - -func (r *RuleType) GetExtraProperties() map[string]interface{} { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleType) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleType) SetID(id string) { - r.ID = id - r.require(ruleTypeFieldID) -} - -// SetName sets the Name field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleType) SetName(name string) { - r.Name = name - r.require(ruleTypeFieldName) -} - -// SetDescription sets the Description field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleType) SetDescription(description *string) { - r.Description = description - r.require(ruleTypeFieldDescription) -} - -func (r *RuleType) UnmarshalJSON(data []byte) error { - type unmarshaler RuleType - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleType(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleType) MarshalJSON() ([]byte, error) { - type embed RuleType - var marshaler = struct { - embed - }{ - embed: embed(*r), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (r *RuleType) String() string { - if r == nil { - return "" - } - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -var ( - ruleTypeSearchResponseFieldResults = big.NewInt(1 << 0) - ruleTypeSearchResponseFieldPaging = big.NewInt(1 << 1) -) - -type RuleTypeSearchResponse struct { - // Current page of results from the requested resource. - Results []*RuleType `json:"results,omitempty" url:"results,omitempty"` - Paging *PagingCursors `json:"paging" url:"paging"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (r *RuleTypeSearchResponse) GetResults() []*RuleType { - if r == nil { - return nil - } - return r.Results -} - -func (r *RuleTypeSearchResponse) GetPaging() *PagingCursors { - if r == nil { - return nil - } - return r.Paging -} - -func (r *RuleTypeSearchResponse) GetExtraProperties() map[string]interface{} { - if r == nil { - return nil - } - return r.extraProperties -} - -func (r *RuleTypeSearchResponse) require(field *big.Int) { - if r.explicitFields == nil { - r.explicitFields = big.NewInt(0) - } - r.explicitFields.Or(r.explicitFields, field) -} - -// SetResults sets the Results field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleTypeSearchResponse) SetResults(results []*RuleType) { - r.Results = results - r.require(ruleTypeSearchResponseFieldResults) -} - -// SetPaging sets the Paging field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (r *RuleTypeSearchResponse) SetPaging(paging *PagingCursors) { - r.Paging = paging - r.require(ruleTypeSearchResponseFieldPaging) -} - -func (r *RuleTypeSearchResponse) UnmarshalJSON(data []byte) error { - type unmarshaler RuleTypeSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *r = RuleTypeSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *r) - if err != nil { - return err - } - r.extraProperties = extraProperties - r.rawJSON = json.RawMessage(data) - return nil -} - -func (r *RuleTypeSearchResponse) MarshalJSON() ([]byte, error) { - type embed RuleTypeSearchResponse - var marshaler = struct { - embed - }{ - embed: embed(*r), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, r.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (r *RuleTypeSearchResponse) String() string { - if r == nil { - return "" - } - if len(r.rawJSON) > 0 { - if value, err := internal.StringifyJSON(r.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(r); err == nil { - return value - } - return fmt.Sprintf("%#v", r) -} - -var ( - userFieldID = big.NewInt(1 << 0) - userFieldEmail = big.NewInt(1 << 1) -) - -type User struct { - ID string `json:"id" url:"id"` - Email string `json:"email" url:"email"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (u *User) GetID() string { - if u == nil { - return "" - } - return u.ID -} - -func (u *User) GetEmail() string { - if u == nil { - return "" - } - return u.Email -} - -func (u *User) GetExtraProperties() map[string]interface{} { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *User) require(field *big.Int) { - if u.explicitFields == nil { - u.explicitFields = big.NewInt(0) - } - u.explicitFields.Or(u.explicitFields, field) -} - -// SetID sets the ID field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *User) SetID(id string) { - u.ID = id - u.require(userFieldID) -} - -// SetEmail sets the Email field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *User) SetEmail(email string) { - u.Email = email - u.require(userFieldEmail) -} - -func (u *User) UnmarshalJSON(data []byte) error { - type unmarshaler User - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = User(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *User) MarshalJSON() ([]byte, error) { - type embed User - var marshaler = struct { - embed - }{ - embed: embed(*u), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (u *User) String() string { - if u == nil { - return "" - } - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} - -var ( - userSearchResponseFieldResults = big.NewInt(1 << 0) - userSearchResponseFieldPaging = big.NewInt(1 << 1) -) - -type UserSearchResponse struct { - // Current page of results from the requested resource. - Results []*User `json:"results,omitempty" url:"results,omitempty"` - Paging *PagingCursors `json:"paging" url:"paging"` - - // Private bitmask of fields set to an explicit value and therefore not to be omitted - explicitFields *big.Int `json:"-" url:"-"` - - extraProperties map[string]interface{} - rawJSON json.RawMessage -} - -func (u *UserSearchResponse) GetResults() []*User { - if u == nil { - return nil - } - return u.Results -} - -func (u *UserSearchResponse) GetPaging() *PagingCursors { - if u == nil { - return nil - } - return u.Paging -} - -func (u *UserSearchResponse) GetExtraProperties() map[string]interface{} { - if u == nil { - return nil - } - return u.extraProperties -} - -func (u *UserSearchResponse) require(field *big.Int) { - if u.explicitFields == nil { - u.explicitFields = big.NewInt(0) - } - u.explicitFields.Or(u.explicitFields, field) -} - -// SetResults sets the Results field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *UserSearchResponse) SetResults(results []*User) { - u.Results = results - u.require(userSearchResponseFieldResults) -} - -// SetPaging sets the Paging field and marks it as non-optional; -// this prevents an empty or null value for this field from being omitted during serialization. -func (u *UserSearchResponse) SetPaging(paging *PagingCursors) { - u.Paging = paging - u.require(userSearchResponseFieldPaging) -} - -func (u *UserSearchResponse) UnmarshalJSON(data []byte) error { - type unmarshaler UserSearchResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *u = UserSearchResponse(value) - extraProperties, err := internal.ExtractExtraProperties(data, *u) - if err != nil { - return err - } - u.extraProperties = extraProperties - u.rawJSON = json.RawMessage(data) - return nil -} - -func (u *UserSearchResponse) MarshalJSON() ([]byte, error) { - type embed UserSearchResponse - var marshaler = struct { - embed - }{ - embed: embed(*u), - } - explicitMarshaler := internal.HandleExplicitFields(marshaler, u.explicitFields) - return json.Marshal(explicitMarshaler) -} - -func (u *UserSearchResponse) String() string { - if u == nil { - return "" - } - if len(u.rawJSON) > 0 { - if value, err := internal.StringifyJSON(u.rawJSON); err == nil { - return value - } - } - if value, err := internal.StringifyJSON(u); err == nil { - return value - } - return fmt.Sprintf("%#v", u) -} diff --git a/seed/go-sdk/allof/types_test.go b/seed/go-sdk/allof/types_test.go deleted file mode 100644 index bbbf7f274167..000000000000 --- a/seed/go-sdk/allof/types_test.go +++ /dev/null @@ -1,4473 +0,0 @@ -// Code generated by Fern. DO NOT EDIT. - -package api - -import ( - json "encoding/json" - assert "github.com/stretchr/testify/assert" - require "github.com/stretchr/testify/require" - testing "testing" - time "time" -) - -func TestSettersRuleCreateRequest(t *testing.T) { - t.Run("SetName", func(t *testing.T) { - obj := &RuleCreateRequest{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetExecutionContext", func(t *testing.T) { - obj := &RuleCreateRequest{} - var fernTestValueExecutionContext RuleExecutionContext - obj.SetExecutionContext(fernTestValueExecutionContext) - assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestSettersMarkExplicitRuleCreateRequest(t *testing.T) { - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleCreateRequest{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleCreateRequest{} - var fernTestValueExecutionContext RuleExecutionContext - - // Act - obj.SetExecutionContext(fernTestValueExecutionContext) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersSearchRuleTypesRequest(t *testing.T) { - t.Run("SetQuery", func(t *testing.T) { - obj := &SearchRuleTypesRequest{} - var fernTestValueQuery *string - obj.SetQuery(fernTestValueQuery) - assert.Equal(t, fernTestValueQuery, obj.Query) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestSettersMarkExplicitSearchRuleTypesRequest(t *testing.T) { - t.Run("SetQuery_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &SearchRuleTypesRequest{} - var fernTestValueQuery *string - - // Act - obj.SetQuery(fernTestValueQuery) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersAuditInfo(t *testing.T) { - t.Run("SetCreatedBy", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueCreatedBy *string - obj.SetCreatedBy(fernTestValueCreatedBy) - assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetCreatedDateTime", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueCreatedDateTime *time.Time - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedBy", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueModifiedBy *string - obj.SetModifiedBy(fernTestValueModifiedBy) - assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedDateTime", func(t *testing.T) { - obj := &AuditInfo{} - var fernTestValueModifiedDateTime *time.Time - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersAuditInfo(t *testing.T) { - t.Run("GetCreatedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *string - obj.CreatedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") - }) - - t.Run("GetCreatedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.CreatedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedBy() // Should return zero value - }) - - t.Run("GetCreatedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *time.Time - obj.CreatedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") - }) - - t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.CreatedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedDateTime() // Should return zero value - }) - - t.Run("GetModifiedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *string - obj.ModifiedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") - }) - - t.Run("GetModifiedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.ModifiedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedBy() // Should return zero value - }) - - t.Run("GetModifiedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var expected *time.Time - obj.ModifiedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") - }) - - t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - obj.ModifiedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedDateTime() // Should return zero value - }) - -} - -func TestSettersMarkExplicitAuditInfo(t *testing.T) { - t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueCreatedBy *string - - // Act - obj.SetCreatedBy(fernTestValueCreatedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueCreatedDateTime *time.Time - - // Act - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueModifiedBy *string - - // Act - obj.SetModifiedBy(fernTestValueModifiedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - var fernTestValueModifiedDateTime *time.Time - - // Act - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersBaseOrg(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &BaseOrg{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetMetadata", func(t *testing.T) { - obj := &BaseOrg{} - var fernTestValueMetadata *BaseOrgMetadata - obj.SetMetadata(fernTestValueMetadata) - assert.Equal(t, fernTestValueMetadata, obj.Metadata) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersBaseOrg(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetMetadata", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var expected *BaseOrgMetadata - obj.Metadata = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") - }) - - t.Run("GetMetadata_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - obj.Metadata = nil - - // Act & Assert - assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") - }) - - t.Run("GetMetadata_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetMetadata() // Should return zero value - }) - -} - -func TestSettersMarkExplicitBaseOrg(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - var fernTestValueMetadata *BaseOrgMetadata - - // Act - obj.SetMetadata(fernTestValueMetadata) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersBaseOrgMetadata(t *testing.T) { - t.Run("SetRegion", func(t *testing.T) { - obj := &BaseOrgMetadata{} - var fernTestValueRegion string - obj.SetRegion(fernTestValueRegion) - assert.Equal(t, fernTestValueRegion, obj.Region) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetTier", func(t *testing.T) { - obj := &BaseOrgMetadata{} - var fernTestValueTier *string - obj.SetTier(fernTestValueTier) - assert.Equal(t, fernTestValueTier, obj.Tier) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersBaseOrgMetadata(t *testing.T) { - t.Run("GetRegion", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var expected string - obj.Region = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") - }) - - t.Run("GetRegion_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetRegion() // Should return zero value - }) - - t.Run("GetTier", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var expected *string - obj.Tier = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetTier(), "getter should return the property value") - }) - - t.Run("GetTier_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - obj.Tier = nil - - // Act & Assert - assert.Nil(t, obj.GetTier(), "getter should return nil when property is nil") - }) - - t.Run("GetTier_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetTier() // Should return zero value - }) - -} - -func TestSettersMarkExplicitBaseOrgMetadata(t *testing.T) { - t.Run("SetRegion_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var fernTestValueRegion string - - // Act - obj.SetRegion(fernTestValueRegion) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetTier_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - var fernTestValueTier *string - - // Act - obj.SetTier(fernTestValueTier) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersCombinedEntity(t *testing.T) { - t.Run("SetStatus", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueStatus CombinedEntityStatus - obj.SetStatus(fernTestValueStatus) - assert.Equal(t, fernTestValueStatus, obj.Status) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetID", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueName *string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetSummary", func(t *testing.T) { - obj := &CombinedEntity{} - var fernTestValueSummary *string - obj.SetSummary(fernTestValueSummary) - assert.Equal(t, fernTestValueSummary, obj.Summary) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersCombinedEntity(t *testing.T) { - t.Run("GetStatus", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected CombinedEntityStatus - obj.Status = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") - }) - - t.Run("GetStatus_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetStatus() // Should return zero value - }) - - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected *string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - obj.Name = nil - - // Act & Assert - assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetSummary", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var expected *string - obj.Summary = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") - }) - - t.Run("GetSummary_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - obj.Summary = nil - - // Act & Assert - assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") - }) - - t.Run("GetSummary_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetSummary() // Should return zero value - }) - -} - -func TestSettersMarkExplicitCombinedEntity(t *testing.T) { - t.Run("SetStatus_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueStatus CombinedEntityStatus - - // Act - obj.SetStatus(fernTestValueStatus) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueName *string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetSummary_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - var fernTestValueSummary *string - - // Act - obj.SetSummary(fernTestValueSummary) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersDescribable(t *testing.T) { - t.Run("SetName", func(t *testing.T) { - obj := &Describable{} - var fernTestValueName *string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetSummary", func(t *testing.T) { - obj := &Describable{} - var fernTestValueSummary *string - obj.SetSummary(fernTestValueSummary) - assert.Equal(t, fernTestValueSummary, obj.Summary) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersDescribable(t *testing.T) { - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var expected *string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - obj.Name = nil - - // Act & Assert - assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetSummary", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var expected *string - obj.Summary = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetSummary(), "getter should return the property value") - }) - - t.Run("GetSummary_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - obj.Summary = nil - - // Act & Assert - assert.Nil(t, obj.GetSummary(), "getter should return nil when property is nil") - }) - - t.Run("GetSummary_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetSummary() // Should return zero value - }) - -} - -func TestSettersMarkExplicitDescribable(t *testing.T) { - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var fernTestValueName *string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetSummary_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - var fernTestValueSummary *string - - // Act - obj.SetSummary(fernTestValueSummary) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersDetailedOrg(t *testing.T) { - t.Run("SetMetadata", func(t *testing.T) { - obj := &DetailedOrg{} - var fernTestValueMetadata *DetailedOrgMetadata - obj.SetMetadata(fernTestValueMetadata) - assert.Equal(t, fernTestValueMetadata, obj.Metadata) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersDetailedOrg(t *testing.T) { - t.Run("GetMetadata", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - var expected *DetailedOrgMetadata - obj.Metadata = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") - }) - - t.Run("GetMetadata_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - obj.Metadata = nil - - // Act & Assert - assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") - }) - - t.Run("GetMetadata_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrg - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetMetadata() // Should return zero value - }) - -} - -func TestSettersMarkExplicitDetailedOrg(t *testing.T) { - t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - var fernTestValueMetadata *DetailedOrgMetadata - - // Act - obj.SetMetadata(fernTestValueMetadata) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersDetailedOrgMetadata(t *testing.T) { - t.Run("SetRegion", func(t *testing.T) { - obj := &DetailedOrgMetadata{} - var fernTestValueRegion string - obj.SetRegion(fernTestValueRegion) - assert.Equal(t, fernTestValueRegion, obj.Region) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetDomain", func(t *testing.T) { - obj := &DetailedOrgMetadata{} - var fernTestValueDomain *string - obj.SetDomain(fernTestValueDomain) - assert.Equal(t, fernTestValueDomain, obj.Domain) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersDetailedOrgMetadata(t *testing.T) { - t.Run("GetRegion", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var expected string - obj.Region = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetRegion(), "getter should return the property value") - }) - - t.Run("GetRegion_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetRegion() // Should return zero value - }) - - t.Run("GetDomain", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var expected *string - obj.Domain = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetDomain(), "getter should return the property value") - }) - - t.Run("GetDomain_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - obj.Domain = nil - - // Act & Assert - assert.Nil(t, obj.GetDomain(), "getter should return nil when property is nil") - }) - - t.Run("GetDomain_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetDomain() // Should return zero value - }) - -} - -func TestSettersMarkExplicitDetailedOrgMetadata(t *testing.T) { - t.Run("SetRegion_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var fernTestValueRegion string - - // Act - obj.SetRegion(fernTestValueRegion) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetDomain_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - var fernTestValueDomain *string - - // Act - obj.SetDomain(fernTestValueDomain) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersIdentifiable(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &Identifiable{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &Identifiable{} - var fernTestValueName *string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersIdentifiable(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var expected *string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - obj.Name = nil - - // Act & Assert - assert.Nil(t, obj.GetName(), "getter should return nil when property is nil") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - -} - -func TestSettersMarkExplicitIdentifiable(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - var fernTestValueName *string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersOrganization(t *testing.T) { - t.Run("SetName", func(t *testing.T) { - obj := &Organization{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetID", func(t *testing.T) { - obj := &Organization{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetMetadata", func(t *testing.T) { - obj := &Organization{} - var fernTestValueMetadata *BaseOrgMetadata - obj.SetMetadata(fernTestValueMetadata) - assert.Equal(t, fernTestValueMetadata, obj.Metadata) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersOrganization(t *testing.T) { - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var expected string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetMetadata", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var expected *BaseOrgMetadata - obj.Metadata = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetMetadata(), "getter should return the property value") - }) - - t.Run("GetMetadata_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - obj.Metadata = nil - - // Act & Assert - assert.Nil(t, obj.GetMetadata(), "getter should return nil when property is nil") - }) - - t.Run("GetMetadata_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetMetadata() // Should return zero value - }) - -} - -func TestSettersMarkExplicitOrganization(t *testing.T) { - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetMetadata_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - var fernTestValueMetadata *BaseOrgMetadata - - // Act - obj.SetMetadata(fernTestValueMetadata) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersPaginatedResult(t *testing.T) { - t.Run("SetPaging", func(t *testing.T) { - obj := &PaginatedResult{} - var fernTestValuePaging *PagingCursors - obj.SetPaging(fernTestValuePaging) - assert.Equal(t, fernTestValuePaging, obj.Paging) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetResults", func(t *testing.T) { - obj := &PaginatedResult{} - var fernTestValueResults []any - obj.SetResults(fernTestValueResults) - assert.Equal(t, fernTestValueResults, obj.Results) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersPaginatedResult(t *testing.T) { - t.Run("GetPaging", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var expected *PagingCursors - obj.Paging = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") - }) - - t.Run("GetPaging_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - obj.Paging = nil - - // Act & Assert - assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") - }) - - t.Run("GetPaging_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPaging() // Should return zero value - }) - - t.Run("GetResults", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var expected []any - obj.Results = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") - }) - - t.Run("GetResults_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - obj.Results = nil - - // Act & Assert - assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") - }) - - t.Run("GetResults_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetResults() // Should return zero value - }) - -} - -func TestSettersMarkExplicitPaginatedResult(t *testing.T) { - t.Run("SetPaging_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var fernTestValuePaging *PagingCursors - - // Act - obj.SetPaging(fernTestValuePaging) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetResults_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - var fernTestValueResults []any - - // Act - obj.SetResults(fernTestValueResults) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersPagingCursors(t *testing.T) { - t.Run("SetNext", func(t *testing.T) { - obj := &PagingCursors{} - var fernTestValueNext string - obj.SetNext(fernTestValueNext) - assert.Equal(t, fernTestValueNext, obj.Next) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetPrevious", func(t *testing.T) { - obj := &PagingCursors{} - var fernTestValuePrevious *string - obj.SetPrevious(fernTestValuePrevious) - assert.Equal(t, fernTestValuePrevious, obj.Previous) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersPagingCursors(t *testing.T) { - t.Run("GetNext", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var expected string - obj.Next = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetNext(), "getter should return the property value") - }) - - t.Run("GetNext_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetNext() // Should return zero value - }) - - t.Run("GetPrevious", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var expected *string - obj.Previous = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPrevious(), "getter should return the property value") - }) - - t.Run("GetPrevious_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - obj.Previous = nil - - // Act & Assert - assert.Nil(t, obj.GetPrevious(), "getter should return nil when property is nil") - }) - - t.Run("GetPrevious_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPrevious() // Should return zero value - }) - -} - -func TestSettersMarkExplicitPagingCursors(t *testing.T) { - t.Run("SetNext_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var fernTestValueNext string - - // Act - obj.SetNext(fernTestValueNext) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetPrevious_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - var fernTestValuePrevious *string - - // Act - obj.SetPrevious(fernTestValuePrevious) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersRuleResponse(t *testing.T) { - t.Run("SetCreatedBy", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueCreatedBy *string - obj.SetCreatedBy(fernTestValueCreatedBy) - assert.Equal(t, fernTestValueCreatedBy, obj.CreatedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetCreatedDateTime", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueCreatedDateTime *time.Time - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - assert.Equal(t, fernTestValueCreatedDateTime, obj.CreatedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedBy", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueModifiedBy *string - obj.SetModifiedBy(fernTestValueModifiedBy) - assert.Equal(t, fernTestValueModifiedBy, obj.ModifiedBy) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetModifiedDateTime", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueModifiedDateTime *time.Time - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - assert.Equal(t, fernTestValueModifiedDateTime, obj.ModifiedDateTime) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetID", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetStatus", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueStatus RuleResponseStatus - obj.SetStatus(fernTestValueStatus) - assert.Equal(t, fernTestValueStatus, obj.Status) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetExecutionContext", func(t *testing.T) { - obj := &RuleResponse{} - var fernTestValueExecutionContext *RuleExecutionContext - obj.SetExecutionContext(fernTestValueExecutionContext) - assert.Equal(t, fernTestValueExecutionContext, obj.ExecutionContext) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersRuleResponse(t *testing.T) { - t.Run("GetCreatedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *string - obj.CreatedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedBy(), "getter should return the property value") - }) - - t.Run("GetCreatedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.CreatedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedBy() // Should return zero value - }) - - t.Run("GetCreatedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *time.Time - obj.CreatedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetCreatedDateTime(), "getter should return the property value") - }) - - t.Run("GetCreatedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.CreatedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetCreatedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetCreatedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetCreatedDateTime() // Should return zero value - }) - - t.Run("GetModifiedBy", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *string - obj.ModifiedBy = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedBy(), "getter should return the property value") - }) - - t.Run("GetModifiedBy_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.ModifiedBy = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedBy(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedBy_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedBy() // Should return zero value - }) - - t.Run("GetModifiedDateTime", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *time.Time - obj.ModifiedDateTime = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetModifiedDateTime(), "getter should return the property value") - }) - - t.Run("GetModifiedDateTime_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.ModifiedDateTime = nil - - // Act & Assert - assert.Nil(t, obj.GetModifiedDateTime(), "getter should return nil when property is nil") - }) - - t.Run("GetModifiedDateTime_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetModifiedDateTime() // Should return zero value - }) - - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetStatus", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected RuleResponseStatus - obj.Status = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetStatus(), "getter should return the property value") - }) - - t.Run("GetStatus_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetStatus() // Should return zero value - }) - - t.Run("GetExecutionContext", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var expected *RuleExecutionContext - obj.ExecutionContext = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetExecutionContext(), "getter should return the property value") - }) - - t.Run("GetExecutionContext_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - obj.ExecutionContext = nil - - // Act & Assert - assert.Nil(t, obj.GetExecutionContext(), "getter should return nil when property is nil") - }) - - t.Run("GetExecutionContext_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetExecutionContext() // Should return zero value - }) - -} - -func TestSettersMarkExplicitRuleResponse(t *testing.T) { - t.Run("SetCreatedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueCreatedBy *string - - // Act - obj.SetCreatedBy(fernTestValueCreatedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetCreatedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueCreatedDateTime *time.Time - - // Act - obj.SetCreatedDateTime(fernTestValueCreatedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedBy_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueModifiedBy *string - - // Act - obj.SetModifiedBy(fernTestValueModifiedBy) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetModifiedDateTime_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueModifiedDateTime *time.Time - - // Act - obj.SetModifiedDateTime(fernTestValueModifiedDateTime) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetStatus_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueStatus RuleResponseStatus - - // Act - obj.SetStatus(fernTestValueStatus) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetExecutionContext_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - var fernTestValueExecutionContext *RuleExecutionContext - - // Act - obj.SetExecutionContext(fernTestValueExecutionContext) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersRuleType(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &RuleType{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetName", func(t *testing.T) { - obj := &RuleType{} - var fernTestValueName string - obj.SetName(fernTestValueName) - assert.Equal(t, fernTestValueName, obj.Name) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetDescription", func(t *testing.T) { - obj := &RuleType{} - var fernTestValueDescription *string - obj.SetDescription(fernTestValueDescription) - assert.Equal(t, fernTestValueDescription, obj.Description) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersRuleType(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetName", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var expected string - obj.Name = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetName(), "getter should return the property value") - }) - - t.Run("GetName_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetName() // Should return zero value - }) - - t.Run("GetDescription", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var expected *string - obj.Description = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetDescription(), "getter should return the property value") - }) - - t.Run("GetDescription_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - obj.Description = nil - - // Act & Assert - assert.Nil(t, obj.GetDescription(), "getter should return nil when property is nil") - }) - - t.Run("GetDescription_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetDescription() // Should return zero value - }) - -} - -func TestSettersMarkExplicitRuleType(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetName_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var fernTestValueName string - - // Act - obj.SetName(fernTestValueName) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetDescription_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - var fernTestValueDescription *string - - // Act - obj.SetDescription(fernTestValueDescription) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersRuleTypeSearchResponse(t *testing.T) { - t.Run("SetResults", func(t *testing.T) { - obj := &RuleTypeSearchResponse{} - var fernTestValueResults []*RuleType - obj.SetResults(fernTestValueResults) - assert.Equal(t, fernTestValueResults, obj.Results) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetPaging", func(t *testing.T) { - obj := &RuleTypeSearchResponse{} - var fernTestValuePaging *PagingCursors - obj.SetPaging(fernTestValuePaging) - assert.Equal(t, fernTestValuePaging, obj.Paging) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersRuleTypeSearchResponse(t *testing.T) { - t.Run("GetResults", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var expected []*RuleType - obj.Results = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") - }) - - t.Run("GetResults_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - obj.Results = nil - - // Act & Assert - assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") - }) - - t.Run("GetResults_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetResults() // Should return zero value - }) - - t.Run("GetPaging", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var expected *PagingCursors - obj.Paging = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") - }) - - t.Run("GetPaging_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - obj.Paging = nil - - // Act & Assert - assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") - }) - - t.Run("GetPaging_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPaging() // Should return zero value - }) - -} - -func TestSettersMarkExplicitRuleTypeSearchResponse(t *testing.T) { - t.Run("SetResults_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var fernTestValueResults []*RuleType - - // Act - obj.SetResults(fernTestValueResults) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetPaging_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - var fernTestValuePaging *PagingCursors - - // Act - obj.SetPaging(fernTestValuePaging) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersUser(t *testing.T) { - t.Run("SetID", func(t *testing.T) { - obj := &User{} - var fernTestValueID string - obj.SetID(fernTestValueID) - assert.Equal(t, fernTestValueID, obj.ID) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetEmail", func(t *testing.T) { - obj := &User{} - var fernTestValueEmail string - obj.SetEmail(fernTestValueEmail) - assert.Equal(t, fernTestValueEmail, obj.Email) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersUser(t *testing.T) { - t.Run("GetID", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var expected string - obj.ID = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetID(), "getter should return the property value") - }) - - t.Run("GetID_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetID() // Should return zero value - }) - - t.Run("GetEmail", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var expected string - obj.Email = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetEmail(), "getter should return the property value") - }) - - t.Run("GetEmail_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetEmail() // Should return zero value - }) - -} - -func TestSettersMarkExplicitUser(t *testing.T) { - t.Run("SetID_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var fernTestValueID string - - // Act - obj.SetID(fernTestValueID) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetEmail_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - var fernTestValueEmail string - - // Act - obj.SetEmail(fernTestValueEmail) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestSettersUserSearchResponse(t *testing.T) { - t.Run("SetResults", func(t *testing.T) { - obj := &UserSearchResponse{} - var fernTestValueResults []*User - obj.SetResults(fernTestValueResults) - assert.Equal(t, fernTestValueResults, obj.Results) - assert.NotNil(t, obj.explicitFields) - }) - - t.Run("SetPaging", func(t *testing.T) { - obj := &UserSearchResponse{} - var fernTestValuePaging *PagingCursors - obj.SetPaging(fernTestValuePaging) - assert.Equal(t, fernTestValuePaging, obj.Paging) - assert.NotNil(t, obj.explicitFields) - }) - -} - -func TestGettersUserSearchResponse(t *testing.T) { - t.Run("GetResults", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var expected []*User - obj.Results = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetResults(), "getter should return the property value") - }) - - t.Run("GetResults_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - obj.Results = nil - - // Act & Assert - assert.Nil(t, obj.GetResults(), "getter should return nil when property is nil") - }) - - t.Run("GetResults_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetResults() // Should return zero value - }) - - t.Run("GetPaging", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var expected *PagingCursors - obj.Paging = expected - - // Act & Assert - assert.Equal(t, expected, obj.GetPaging(), "getter should return the property value") - }) - - t.Run("GetPaging_NilValue", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - obj.Paging = nil - - // Act & Assert - assert.Nil(t, obj.GetPaging(), "getter should return nil when property is nil") - }) - - t.Run("GetPaging_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - // Should not panic - getters should handle nil receiver gracefully - defer func() { - if r := recover(); r != nil { - t.Errorf("Getter panicked on nil receiver: %v", r) - } - }() - _ = obj.GetPaging() // Should return zero value - }) - -} - -func TestSettersMarkExplicitUserSearchResponse(t *testing.T) { - t.Run("SetResults_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var fernTestValueResults []*User - - // Act - obj.SetResults(fernTestValueResults) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - - t.Run("SetPaging_MarksExplicit", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - var fernTestValuePaging *PagingCursors - - // Act - obj.SetPaging(fernTestValuePaging) - - // Assert - object with explicitly set field can be marshaled/unmarshaled - bytes, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed for test setup") - - // This test ensures JSON marshaling and unmarshaling succeed when the field has a zero/nil value - // Detect if marshaled JSON is an object or primitive to use correct unmarshal target - if len(bytes) > 0 && bytes[0] == '{' { - // JSON object - unmarshal into map - var unmarshaled map[string]interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } else { - // JSON primitive (string, number, boolean, null) - unmarshal into interface{} - var unmarshaled interface{} - err = json.Unmarshal(bytes, &unmarshaled) - require.NoError(t, err, "unmarshaling should succeed for test verification") - } - - // Note: This does not explicitly assert the presence of a specific JSON field - // It verifies that setting a field via setter allows successful JSON round-trip - }) - -} - -func TestJSONMarshalingAuditInfo(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &AuditInfo{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled AuditInfo - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj AuditInfo - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj AuditInfo - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingBaseOrg(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrg{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled BaseOrg - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj BaseOrg - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj BaseOrg - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingBaseOrgMetadata(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &BaseOrgMetadata{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled BaseOrgMetadata - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj BaseOrgMetadata - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj BaseOrgMetadata - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingCombinedEntity(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &CombinedEntity{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled CombinedEntity - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj CombinedEntity - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj CombinedEntity - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingDescribable(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Describable{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled Describable - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj Describable - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj Describable - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingDetailedOrg(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrg{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled DetailedOrg - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj DetailedOrg - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj DetailedOrg - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingDetailedOrgMetadata(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &DetailedOrgMetadata{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled DetailedOrgMetadata - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj DetailedOrgMetadata - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj DetailedOrgMetadata - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingIdentifiable(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Identifiable{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled Identifiable - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj Identifiable - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj Identifiable - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingOrganization(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &Organization{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled Organization - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj Organization - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj Organization - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingPaginatedResult(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PaginatedResult{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled PaginatedResult - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj PaginatedResult - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj PaginatedResult - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingPagingCursors(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &PagingCursors{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled PagingCursors - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj PagingCursors - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj PagingCursors - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingRuleResponse(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleResponse{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled RuleResponse - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj RuleResponse - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj RuleResponse - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingRuleType(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleType{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled RuleType - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj RuleType - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj RuleType - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingRuleTypeSearchResponse(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &RuleTypeSearchResponse{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled RuleTypeSearchResponse - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj RuleTypeSearchResponse - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj RuleTypeSearchResponse - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingUser(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &User{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled User - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj User - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj User - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestJSONMarshalingUserSearchResponse(t *testing.T) { - t.Run("MarshalUnmarshal", func(t *testing.T) { - t.Parallel() - // Arrange - obj := &UserSearchResponse{} - - // Act - Marshal to JSON - data, err := json.Marshal(obj) - require.NoError(t, err, "marshaling should succeed") - assert.NotNil(t, data, "marshaled data should not be nil") - assert.NotEmpty(t, data, "marshaled data should not be empty") - - // Unmarshal back and verify round-trip - var unmarshaled UserSearchResponse - err = json.Unmarshal(data, &unmarshaled) - assert.NoError(t, err, "round-trip unmarshal should succeed") - }) - - t.Run("UnmarshalInvalidJSON", func(t *testing.T) { - t.Parallel() - var obj UserSearchResponse - err := json.Unmarshal([]byte(`{invalid json}`), &obj) - assert.Error(t, err, "unmarshaling invalid JSON should return an error") - }) - - t.Run("UnmarshalEmptyObject", func(t *testing.T) { - t.Parallel() - var obj UserSearchResponse - err := json.Unmarshal([]byte(`{}`), &obj) - assert.NoError(t, err, "unmarshaling empty object should succeed") - }) -} - -func TestStringAuditInfo(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &AuditInfo{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringBaseOrg(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &BaseOrg{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringBaseOrgMetadata(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &BaseOrgMetadata{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringCombinedEntity(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &CombinedEntity{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringDescribable(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &Describable{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringDetailedOrg(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrg{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrg - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringDetailedOrgMetadata(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrgMetadata{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringIdentifiable(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &Identifiable{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringOrganization(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &Organization{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringPaginatedResult(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &PaginatedResult{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringPagingCursors(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &PagingCursors{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringRuleResponse(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &RuleResponse{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringRuleType(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &RuleType{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringRuleTypeSearchResponse(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &RuleTypeSearchResponse{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringUser(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &User{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestStringUserSearchResponse(t *testing.T) { - t.Run("StringMethod", func(t *testing.T) { - t.Parallel() - obj := &UserSearchResponse{} - result := obj.String() - assert.NotEmpty(t, result, "String() should return a non-empty representation") - }) - - t.Run("StringMethod_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - result := obj.String() - assert.Equal(t, "", result, "String() should return for nil receiver") - }) -} - -func TestEnumCombinedEntityStatus(t *testing.T) { - t.Run("NewFromString_active", func(t *testing.T) { - t.Parallel() - val, err := NewCombinedEntityStatusFromString("active") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, CombinedEntityStatus("active"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_archived", func(t *testing.T) { - t.Parallel() - val, err := NewCombinedEntityStatusFromString("archived") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, CombinedEntityStatus("archived"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_Invalid", func(t *testing.T) { - _, err := NewCombinedEntityStatusFromString("invalid_value_that_does_not_exist") - assert.Error(t, err) - }) - - t.Run("Ptr", func(t *testing.T) { - val, err := NewCombinedEntityStatusFromString("active") - assert.NoError(t, err) - ptr := val.Ptr() - assert.NotNil(t, ptr) - assert.Equal(t, val, *ptr) - }) -} - -func TestEnumRuleExecutionContext(t *testing.T) { - t.Run("NewFromString_prod", func(t *testing.T) { - t.Parallel() - val, err := NewRuleExecutionContextFromString("prod") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleExecutionContext("prod"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_staging", func(t *testing.T) { - t.Parallel() - val, err := NewRuleExecutionContextFromString("staging") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleExecutionContext("staging"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_dev", func(t *testing.T) { - t.Parallel() - val, err := NewRuleExecutionContextFromString("dev") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleExecutionContext("dev"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_Invalid", func(t *testing.T) { - _, err := NewRuleExecutionContextFromString("invalid_value_that_does_not_exist") - assert.Error(t, err) - }) - - t.Run("Ptr", func(t *testing.T) { - val, err := NewRuleExecutionContextFromString("prod") - assert.NoError(t, err) - ptr := val.Ptr() - assert.NotNil(t, ptr) - assert.Equal(t, val, *ptr) - }) -} - -func TestEnumRuleResponseStatus(t *testing.T) { - t.Run("NewFromString_active", func(t *testing.T) { - t.Parallel() - val, err := NewRuleResponseStatusFromString("active") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleResponseStatus("active"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_inactive", func(t *testing.T) { - t.Parallel() - val, err := NewRuleResponseStatusFromString("inactive") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleResponseStatus("inactive"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_draft", func(t *testing.T) { - t.Parallel() - val, err := NewRuleResponseStatusFromString("draft") - assert.NoError(t, err, "valid enum value should not return error") - assert.Equal(t, RuleResponseStatus("draft"), val, "enum value should match expected wire value") - }) - - t.Run("NewFromString_Invalid", func(t *testing.T) { - _, err := NewRuleResponseStatusFromString("invalid_value_that_does_not_exist") - assert.Error(t, err) - }) - - t.Run("Ptr", func(t *testing.T) { - val, err := NewRuleResponseStatusFromString("active") - assert.NoError(t, err) - ptr := val.Ptr() - assert.NotNil(t, ptr) - assert.Equal(t, val, *ptr) - }) -} - -func TestExtraPropertiesAuditInfo(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &AuditInfo{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *AuditInfo - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesBaseOrg(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &BaseOrg{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrg - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesBaseOrgMetadata(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &BaseOrgMetadata{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *BaseOrgMetadata - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesCombinedEntity(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &CombinedEntity{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *CombinedEntity - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesDescribable(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &Describable{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Describable - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesDetailedOrg(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrg{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrg - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesDetailedOrgMetadata(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &DetailedOrgMetadata{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *DetailedOrgMetadata - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesIdentifiable(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &Identifiable{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Identifiable - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesOrganization(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &Organization{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *Organization - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesPaginatedResult(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &PaginatedResult{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PaginatedResult - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesPagingCursors(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &PagingCursors{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *PagingCursors - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesRuleResponse(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &RuleResponse{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleResponse - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesRuleType(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &RuleType{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleType - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesRuleTypeSearchResponse(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &RuleTypeSearchResponse{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *RuleTypeSearchResponse - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesUser(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &User{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *User - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} - -func TestExtraPropertiesUserSearchResponse(t *testing.T) { - t.Run("GetExtraProperties", func(t *testing.T) { - t.Parallel() - obj := &UserSearchResponse{} - // Should not panic when calling GetExtraProperties() - defer func() { - if r := recover(); r != nil { - t.Errorf("GetExtraProperties() panicked: %v", r) - } - }() - extraProps := obj.GetExtraProperties() - // Result can be nil or an empty/non-empty map - _ = extraProps - }) - - t.Run("GetExtraProperties_NilReceiver", func(t *testing.T) { - t.Parallel() - var obj *UserSearchResponse - extraProps := obj.GetExtraProperties() - assert.Nil(t, extraProps, "nil receiver should return nil without panicking") - }) -} diff --git a/seed/java-model/allof-inline/.github/workflows/ci.yml b/seed/java-model/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 09c8c666ad73..000000000000 --- a/seed/java-model/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Compile - run: ./gradlew compileJava - - test: - needs: [ compile ] - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Test - run: ./gradlew test - publish: - needs: [ compile, test ] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Publish to maven - run: | - ./gradlew publish - env: - MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-model/allof-inline/.gitignore b/seed/java-model/allof-inline/.gitignore deleted file mode 100644 index d4199abc2cd4..000000000000 --- a/seed/java-model/allof-inline/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -*.class -.project -.gradle -? -.classpath -.checkstyle -.settings -.node -build - -# IntelliJ -*.iml -*.ipr -*.iws -.idea/ -out/ - -# Eclipse/IntelliJ APT -generated_src/ -generated_testSrc/ -generated/ - -bin -build \ No newline at end of file diff --git a/seed/java-model/allof-inline/build.gradle b/seed/java-model/allof-inline/build.gradle deleted file mode 100644 index d56d2277685f..000000000000 --- a/seed/java-model/allof-inline/build.gradle +++ /dev/null @@ -1,98 +0,0 @@ -plugins { - id 'java-library' - id 'maven-publish' - id 'com.diffplug.spotless' version '6.11.0' -} - -repositories { - mavenCentral() - maven { - url 'https://s01.oss.sonatype.org/content/repositories/releases/' - } -} - -dependencies { - api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' -} - - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -tasks.withType(Javadoc) { - failOnError false - options.addStringOption('Xdoclint:none', '-quiet') -} - -spotless { - java { - palantirJavaFormat() - } -} - - -java { - withSourcesJar() - withJavadocJar() -} - - -group = 'com.fern' - -version = '0.0.1' - -jar { - dependsOn(":generatePomFileForMavenPublication") - archiveBaseName = "allof-inline" -} - -sourcesJar { - archiveBaseName = "allof-inline" -} - -javadocJar { - archiveBaseName = "allof-inline" -} - -test { - useJUnitPlatform() - testLogging { - showStandardStreams = true - } -} - -publishing { - publications { - maven(MavenPublication) { - groupId = 'com.fern' - artifactId = 'allof-inline' - version = '0.0.1' - from components.java - pom { - licenses { - license { - name = 'The MIT License (MIT)' - url = 'https://mit-license.org/' - } - } - scm { - connection = 'scm:git:git://github.com/allof-inline/fern.git' - developerConnection = 'scm:git:git://github.com/allof-inline/fern.git' - url = 'https://github.com/allof-inline/fern' - } - } - } - } - repositories { - maven { - url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" - credentials { - username "$System.env.MAVEN_USERNAME" - password "$System.env.MAVEN_PASSWORD" - } - } - } -} - diff --git a/seed/java-model/allof-inline/settings.gradle b/seed/java-model/allof-inline/settings.gradle deleted file mode 100644 index be2d3eef00b7..000000000000 --- a/seed/java-model/allof-inline/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'allof-inline' - diff --git a/seed/java-model/allof-inline/snippet.json b/seed/java-model/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java deleted file mode 100644 index b67a9041b1f5..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalQueries; - -/** - * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. - */ -class DateTimeDeserializer extends JsonDeserializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); - } - - /** - * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - JsonToken token = parser.currentToken(); - if (token == JsonToken.VALUE_NUMBER_INT) { - return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); - } else { - TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( - parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); - - if (temporal.query(TemporalQueries.offset()) == null) { - return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); - } else { - return OffsetDateTime.from(temporal); - } - } - } -} \ No newline at end of file diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java deleted file mode 100644 index 0d2114931cb1..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; - -/** - * Custom serializer that writes integer-valued doubles without a decimal point. - * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. - * Non-integer values like {@code 3.14} are serialized normally. - */ -class DoubleSerializer extends JsonSerializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule() - .addSerializer(Double.class, new DoubleSerializer()) - .addSerializer(double.class, new DoubleSerializer()); - } - - /** - * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { - gen.writeNumber(value.longValue()); - } else { - gen.writeNumber(value); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java deleted file mode 100644 index f294ae327e6d..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.io.IOException; -import java.lang.Integer; -import java.lang.Object; -import java.lang.String; - -public final class ObjectMappers { - public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() - .addModule(new Jdk8Module()) - .addModule(new JavaTimeModule()) - .addModule(DateTimeDeserializer.getModule()) - .addModule(DoubleSerializer.getModule()) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .build(); - - private ObjectMappers() { - } - - public static String stringify(Object o) { - try { - return JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS) - .writerWithDefaultPrettyPrinter() - .writeValueAsString(o); - } - catch (IOException e) { - return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); - } - } - - public static Object parseErrorBody(String responseBodyString) { - try { - return JSON_MAPPER.readValue(responseBodyString, Object.class); - } - catch (JsonProcessingException ignored) { - return responseBodyString; - } - } - } diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java deleted file mode 100644 index a75a230a0fe6..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import java.io.IOException; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; - -/** - * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. - * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. - */ -public class Rfc2822DateTimeDeserializer extends JsonDeserializer { - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - String raw = parser.getValueAsString(); - return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java deleted file mode 100644 index 134efb3b95ee..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/AuditInfo.java +++ /dev/null @@ -1,192 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.time.OffsetDateTime; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = AuditInfo.Builder.class -) -public final class AuditInfo { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private AuditInfo(Optional createdBy, Optional createdDateTime, - Optional modifiedBy, Optional modifiedDateTime) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AuditInfo && equalTo((AuditInfo) other); - } - - private boolean equalTo(AuditInfo other) { - return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder { - private Optional createdBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - private Builder() { - } - - public Builder from(AuditInfo other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - return this; - } - - /** - *

The user who created this resource.

- */ - @JsonSetter( - value = "createdBy", - nulls = Nulls.SKIP - ) - public Builder createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - public Builder createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

When this resource was created.

- */ - @JsonSetter( - value = "createdDateTime", - nulls = Nulls.SKIP - ) - public Builder createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - public Builder createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @JsonSetter( - value = "modifiedBy", - nulls = Nulls.SKIP - ) - public Builder modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - public Builder modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

When this resource was last modified.

- */ - @JsonSetter( - value = "modifiedDateTime", - nulls = Nulls.SKIP - ) - public Builder modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - public AuditInfo build() { - return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java deleted file mode 100644 index 00535bf9e0c9..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrg.java +++ /dev/null @@ -1,127 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = BaseOrg.Builder.class -) -public final class BaseOrg { - private final String id; - - private final Optional metadata; - - private BaseOrg(String id, Optional metadata) { - this.id = id; - this.metadata = metadata; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrg && equalTo((BaseOrg) other); - } - - private boolean equalTo(BaseOrg other) { - return id.equals(other.id) && metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - _FinalStage id(String id); - - Builder from(BaseOrg other); - } - - public interface _FinalStage { - BaseOrg build(); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(BaseOrgMetadata metadata); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional metadata = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(BaseOrg other) { - id(other.getId()); - metadata(other.getMetadata()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(BaseOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "metadata", - nulls = Nulls.SKIP - ) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public BaseOrg build() { - return new BaseOrg(id, metadata); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java deleted file mode 100644 index 6ccd08740868..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/BaseOrgMetadata.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = BaseOrgMetadata.Builder.class -) -public final class BaseOrgMetadata { - private final String region; - - private final Optional tier; - - private BaseOrgMetadata(String region, Optional tier) { - this.region = region; - this.tier = tier; - } - - /** - * @return Deployment region from BaseOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Subscription tier. - */ - @JsonProperty("tier") - public Optional getTier() { - return tier; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); - } - - private boolean equalTo(BaseOrgMetadata other) { - return region.equals(other.region) && tier.equals(other.tier); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.tier); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from BaseOrg.

- */ - _FinalStage region(String region); - - Builder from(BaseOrgMetadata other); - } - - public interface _FinalStage { - BaseOrgMetadata build(); - - /** - *

Subscription tier.

- */ - _FinalStage tier(Optional tier); - - _FinalStage tier(String tier); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional tier = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(BaseOrgMetadata other) { - region(other.getRegion()); - tier(other.getTier()); - return this; - } - - /** - *

Deployment region from BaseOrg.

- *

Deployment region from BaseOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Subscription tier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage tier(String tier) { - this.tier = Optional.ofNullable(tier); - return this; - } - - /** - *

Subscription tier.

- */ - @java.lang.Override - @JsonSetter( - value = "tier", - nulls = Nulls.SKIP - ) - public _FinalStage tier(Optional tier) { - this.tier = tier; - return this; - } - - @java.lang.Override - public BaseOrgMetadata build() { - return new BaseOrgMetadata(region, tier); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java deleted file mode 100644 index 95f15ae22959..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntity.java +++ /dev/null @@ -1,218 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = CombinedEntity.Builder.class -) -public final class CombinedEntity { - private final String id; - - private final Optional name; - - private final Optional summary; - - private final CombinedEntityStatus status; - - private CombinedEntity(String id, Optional name, Optional summary, - CombinedEntityStatus status) { - this.id = id; - this.name = name; - this.summary = summary; - this.status = status; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Describable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @JsonProperty("status") - public CombinedEntityStatus getStatus() { - return status; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof CombinedEntity && equalTo((CombinedEntity) other); - } - - private boolean equalTo(CombinedEntity other) { - return id.equals(other.id) && name.equals(other.name) && summary.equals(other.summary) && status.equals(other.status); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.summary, this.status); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - StatusStage id(String id); - - Builder from(CombinedEntity other); - } - - public interface StatusStage { - _FinalStage status(CombinedEntityStatus status); - } - - public interface _FinalStage { - CombinedEntity build(); - - /** - *

Display name from Describable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - - /** - *

A short summary.

- */ - _FinalStage summary(Optional summary); - - _FinalStage summary(String summary); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, StatusStage, _FinalStage { - private String id; - - private CombinedEntityStatus status; - - private Optional summary = Optional.empty(); - - private Optional name = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(CombinedEntity other) { - id(other.getId()); - name(other.getName()); - summary(other.getSummary()); - status(other.getStatus()); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public StatusStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public _FinalStage status(CombinedEntityStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - /** - *

A short summary.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - /** - *

A short summary.

- */ - @java.lang.Override - @JsonSetter( - value = "summary", - nulls = Nulls.SKIP - ) - public _FinalStage summary(Optional summary) { - this.summary = summary; - return this; - } - - /** - *

Display name from Describable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Describable.

- */ - @java.lang.Override - @JsonSetter( - value = "name", - nulls = Nulls.SKIP - ) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public CombinedEntity build() { - return new CombinedEntity(id, name, summary, status); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java deleted file mode 100644 index 12b6e20c62fa..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/CombinedEntityStatus.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonValue; -import java.lang.String; - -public enum CombinedEntityStatus { - ACTIVE("active"), - - ARCHIVED("archived"); - - private final String value; - - CombinedEntityStatus(String value) { - this.value = value; - } - - @JsonValue - @java.lang.Override - public String toString() { - return this.value; - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java deleted file mode 100644 index 47749f51c511..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Describable.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = Describable.Builder.class -) -public final class Describable { - private final Optional name; - - private final Optional summary; - - private Describable(Optional name, Optional summary) { - this.name = name; - this.summary = summary; - } - - /** - * @return Display name from Describable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Describable && equalTo((Describable) other); - } - - private boolean equalTo(Describable other) { - return name.equals(other.name) && summary.equals(other.summary); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.summary); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder { - private Optional name = Optional.empty(); - - private Optional summary = Optional.empty(); - - private Builder() { - } - - public Builder from(Describable other) { - name(other.getName()); - summary(other.getSummary()); - return this; - } - - /** - *

Display name from Describable.

- */ - @JsonSetter( - value = "name", - nulls = Nulls.SKIP - ) - public Builder name(Optional name) { - this.name = name; - return this; - } - - public Builder name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

A short summary.

- */ - @JsonSetter( - value = "summary", - nulls = Nulls.SKIP - ) - public Builder summary(Optional summary) { - this.summary = summary; - return this; - } - - public Builder summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - public Describable build() { - return new Describable(name, summary); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java deleted file mode 100644 index 2673e956d206..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrg.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = DetailedOrg.Builder.class -) -public final class DetailedOrg { - private final Optional metadata; - - private DetailedOrg(Optional metadata) { - this.metadata = metadata; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrg && equalTo((DetailedOrg) other); - } - - private boolean equalTo(DetailedOrg other) { - return metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder { - private Optional metadata = Optional.empty(); - - private Builder() { - } - - public Builder from(DetailedOrg other) { - metadata(other.getMetadata()); - return this; - } - - @JsonSetter( - value = "metadata", - nulls = Nulls.SKIP - ) - public Builder metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - public Builder metadata(DetailedOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - public DetailedOrg build() { - return new DetailedOrg(metadata); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java deleted file mode 100644 index db3256745178..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/DetailedOrgMetadata.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = DetailedOrgMetadata.Builder.class -) -public final class DetailedOrgMetadata { - private final String region; - - private final Optional domain; - - private DetailedOrgMetadata(String region, Optional domain) { - this.region = region; - this.domain = domain; - } - - /** - * @return Deployment region from DetailedOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Custom domain name. - */ - @JsonProperty("domain") - public Optional getDomain() { - return domain; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); - } - - private boolean equalTo(DetailedOrgMetadata other) { - return region.equals(other.region) && domain.equals(other.domain); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.domain); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from DetailedOrg.

- */ - _FinalStage region(String region); - - Builder from(DetailedOrgMetadata other); - } - - public interface _FinalStage { - DetailedOrgMetadata build(); - - /** - *

Custom domain name.

- */ - _FinalStage domain(Optional domain); - - _FinalStage domain(String domain); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional domain = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(DetailedOrgMetadata other) { - region(other.getRegion()); - domain(other.getDomain()); - return this; - } - - /** - *

Deployment region from DetailedOrg.

- *

Deployment region from DetailedOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Custom domain name.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage domain(String domain) { - this.domain = Optional.ofNullable(domain); - return this; - } - - /** - *

Custom domain name.

- */ - @java.lang.Override - @JsonSetter( - value = "domain", - nulls = Nulls.SKIP - ) - public _FinalStage domain(Optional domain) { - this.domain = domain; - return this; - } - - @java.lang.Override - public DetailedOrgMetadata build() { - return new DetailedOrgMetadata(region, domain); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java deleted file mode 100644 index c9dfcb3179ad..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Identifiable.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = Identifiable.Builder.class -) -public final class Identifiable { - private final String id; - - private final Optional name; - - private Identifiable(String id, Optional name) { - this.id = id; - this.name = name; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Identifiable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Identifiable && equalTo((Identifiable) other); - } - - private boolean equalTo(Identifiable other) { - return id.equals(other.id) && name.equals(other.name); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - _FinalStage id(String id); - - Builder from(Identifiable other); - } - - public interface _FinalStage { - Identifiable build(); - - /** - *

Display name from Identifiable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional name = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(Identifiable other) { - id(other.getId()); - name(other.getName()); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - /** - *

Display name from Identifiable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Identifiable.

- */ - @java.lang.Override - @JsonSetter( - value = "name", - nulls = Nulls.SKIP - ) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public Identifiable build() { - return new Identifiable(id, name); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java deleted file mode 100644 index a619c1e30d41..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/Organization.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = Organization.Builder.class -) -public final class Organization { - private final String id; - - private final Optional metadata; - - private final String name; - - private Organization(String id, Optional metadata, String name) { - this.id = id; - this.metadata = metadata; - this.name = name; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Organization && equalTo((Organization) other); - } - - private boolean equalTo(Organization other) { - return id.equals(other.id) && metadata.equals(other.metadata) && name.equals(other.name); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.metadata, this.name); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(String id); - - Builder from(Organization other); - } - - public interface NameStage { - _FinalStage name(String name); - } - - public interface _FinalStage { - Organization build(); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(OrganizationMetadata metadata); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, NameStage, _FinalStage { - private String id; - - private String name; - - private Optional metadata = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(Organization other) { - id(other.getId()); - metadata(other.getMetadata()); - name(other.getName()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public _FinalStage name(String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(OrganizationMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "metadata", - nulls = Nulls.SKIP - ) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public Organization build() { - return new Organization(id, metadata, name); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java deleted file mode 100644 index 3dd26d99224f..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/OrganizationMetadata.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = OrganizationMetadata.Builder.class -) -public final class OrganizationMetadata { - private final String region; - - private final Optional domain; - - private OrganizationMetadata(String region, Optional domain) { - this.region = region; - this.domain = domain; - } - - /** - * @return Deployment region from DetailedOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Custom domain name. - */ - @JsonProperty("domain") - public Optional getDomain() { - return domain; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof OrganizationMetadata && equalTo((OrganizationMetadata) other); - } - - private boolean equalTo(OrganizationMetadata other) { - return region.equals(other.region) && domain.equals(other.domain); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.domain); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from DetailedOrg.

- */ - _FinalStage region(String region); - - Builder from(OrganizationMetadata other); - } - - public interface _FinalStage { - OrganizationMetadata build(); - - /** - *

Custom domain name.

- */ - _FinalStage domain(Optional domain); - - _FinalStage domain(String domain); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional domain = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(OrganizationMetadata other) { - region(other.getRegion()); - domain(other.getDomain()); - return this; - } - - /** - *

Deployment region from DetailedOrg.

- *

Deployment region from DetailedOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Custom domain name.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage domain(String domain) { - this.domain = Optional.ofNullable(domain); - return this; - } - - /** - *

Custom domain name.

- */ - @java.lang.Override - @JsonSetter( - value = "domain", - nulls = Nulls.SKIP - ) - public _FinalStage domain(Optional domain) { - this.domain = domain; - return this; - } - - @java.lang.Override - public OrganizationMetadata build() { - return new OrganizationMetadata(region, domain); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java deleted file mode 100644 index f01c8cb01a89..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PaginatedResult.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = PaginatedResult.Builder.class -) -public final class PaginatedResult { - private final PagingCursors paging; - - private final List results; - - private PaginatedResult(PagingCursors paging, List results) { - this.paging = paging; - this.results = results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public List getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PaginatedResult && equalTo((PaginatedResult) other); - } - - private boolean equalTo(PaginatedResult other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(PagingCursors paging); - - Builder from(PaginatedResult other); - } - - public interface _FinalStage { - PaginatedResult build(); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(List results); - - _FinalStage addResults(Object results); - - _FinalStage addAllResults(List results); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private List results = new ArrayList<>(); - - private Builder() { - } - - @java.lang.Override - public Builder from(PaginatedResult other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addAllResults(List results) { - if (results != null) { - this.results.addAll(results); - } - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addResults(Object results) { - this.results.add(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter( - value = "results", - nulls = Nulls.SKIP - ) - public _FinalStage results(List results) { - this.results.clear(); - if (results != null) { - this.results.addAll(results); - } - return this; - } - - @java.lang.Override - public PaginatedResult build() { - return new PaginatedResult(paging, results); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java deleted file mode 100644 index 0d89da26698b..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/PagingCursors.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = PagingCursors.Builder.class -) -public final class PagingCursors { - private final String next; - - private final Optional previous; - - private PagingCursors(String next, Optional previous) { - this.next = next; - this.previous = previous; - } - - /** - * @return Cursor for the next page of results. - */ - @JsonProperty("next") - public String getNext() { - return next; - } - - /** - * @return Cursor for the previous page of results. - */ - @JsonProperty("previous") - public Optional getPrevious() { - return previous; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PagingCursors && equalTo((PagingCursors) other); - } - - private boolean equalTo(PagingCursors other) { - return next.equals(other.next) && previous.equals(other.previous); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.next, this.previous); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NextStage builder() { - return new Builder(); - } - - public interface NextStage { - /** - *

Cursor for the next page of results.

- */ - _FinalStage next(String next); - - Builder from(PagingCursors other); - } - - public interface _FinalStage { - PagingCursors build(); - - /** - *

Cursor for the previous page of results.

- */ - _FinalStage previous(Optional previous); - - _FinalStage previous(String previous); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements NextStage, _FinalStage { - private String next; - - private Optional previous = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(PagingCursors other) { - next(other.getNext()); - previous(other.getPrevious()); - return this; - } - - /** - *

Cursor for the next page of results.

- *

Cursor for the next page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("next") - public _FinalStage next(String next) { - this.next = Objects.requireNonNull(next, "next must not be null"); - return this; - } - - /** - *

Cursor for the previous page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage previous(String previous) { - this.previous = Optional.ofNullable(previous); - return this; - } - - /** - *

Cursor for the previous page of results.

- */ - @java.lang.Override - @JsonSetter( - value = "previous", - nulls = Nulls.SKIP - ) - public _FinalStage previous(Optional previous) { - this.previous = previous; - return this; - } - - @java.lang.Override - public PagingCursors build() { - return new PagingCursors(next, previous); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java deleted file mode 100644 index 93327278e6e3..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleExecutionContext.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonValue; -import java.lang.String; - -public enum RuleExecutionContext { - PROD("prod"), - - STAGING("staging"), - - DEV("dev"); - - private final String value; - - RuleExecutionContext(String value) { - this.value = value; - } - - @JsonValue - @java.lang.Override - public String toString() { - return this.value; - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java deleted file mode 100644 index a8f62579a85c..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponse.java +++ /dev/null @@ -1,350 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.time.OffsetDateTime; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = RuleResponse.Builder.class -) -public final class RuleResponse { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private final String id; - - private final String name; - - private final RuleResponseStatus status; - - private final Optional executionContext; - - private RuleResponse(Optional createdBy, Optional createdDateTime, - Optional modifiedBy, Optional modifiedDateTime, String id, - String name, RuleResponseStatus status, Optional executionContext) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - this.id = id; - this.name = name; - this.status = status; - this.executionContext = executionContext; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("status") - public RuleResponseStatus getStatus() { - return status; - } - - @JsonProperty("executionContext") - public Optional getExecutionContext() { - return executionContext; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleResponse && equalTo((RuleResponse) other); - } - - private boolean equalTo(RuleResponse other) { - return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime) && id.equals(other.id) && name.equals(other.name) && status.equals(other.status) && executionContext.equals(other.executionContext); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime, this.id, this.name, this.status, this.executionContext); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(String id); - - Builder from(RuleResponse other); - } - - public interface NameStage { - StatusStage name(String name); - } - - public interface StatusStage { - _FinalStage status(RuleResponseStatus status); - } - - public interface _FinalStage { - RuleResponse build(); - - /** - *

The user who created this resource.

- */ - _FinalStage createdBy(Optional createdBy); - - _FinalStage createdBy(String createdBy); - - /** - *

When this resource was created.

- */ - _FinalStage createdDateTime(Optional createdDateTime); - - _FinalStage createdDateTime(OffsetDateTime createdDateTime); - - /** - *

The user who last modified this resource.

- */ - _FinalStage modifiedBy(Optional modifiedBy); - - _FinalStage modifiedBy(String modifiedBy); - - /** - *

When this resource was last modified.

- */ - _FinalStage modifiedDateTime(Optional modifiedDateTime); - - _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); - - _FinalStage executionContext(Optional executionContext); - - _FinalStage executionContext(RuleExecutionContext executionContext); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { - private String id; - - private String name; - - private RuleResponseStatus status; - - private Optional executionContext = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional createdBy = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(RuleResponse other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - id(other.getId()); - name(other.getName()); - status(other.getStatus()); - executionContext(other.getExecutionContext()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public StatusStage name(String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public _FinalStage status(RuleResponseStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage executionContext(RuleExecutionContext executionContext) { - this.executionContext = Optional.ofNullable(executionContext); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "executionContext", - nulls = Nulls.SKIP - ) - public _FinalStage executionContext(Optional executionContext) { - this.executionContext = executionContext; - return this; - } - - /** - *

When this resource was last modified.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - /** - *

When this resource was last modified.

- */ - @java.lang.Override - @JsonSetter( - value = "modifiedDateTime", - nulls = Nulls.SKIP - ) - public _FinalStage modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - /** - *

The user who last modified this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @java.lang.Override - @JsonSetter( - value = "modifiedBy", - nulls = Nulls.SKIP - ) - public _FinalStage modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - /** - *

When this resource was created.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

When this resource was created.

- */ - @java.lang.Override - @JsonSetter( - value = "createdDateTime", - nulls = Nulls.SKIP - ) - public _FinalStage createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - /** - *

The user who created this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

The user who created this resource.

- */ - @java.lang.Override - @JsonSetter( - value = "createdBy", - nulls = Nulls.SKIP - ) - public _FinalStage createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - @java.lang.Override - public RuleResponse build() { - return new RuleResponse(createdBy, createdDateTime, modifiedBy, modifiedDateTime, id, name, status, executionContext); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java deleted file mode 100644 index 302cf6780280..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleResponseStatus.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonValue; -import java.lang.String; - -public enum RuleResponseStatus { - ACTIVE("active"), - - INACTIVE("inactive"), - - DRAFT("draft"); - - private final String value; - - RuleResponseStatus(String value) { - this.value = value; - } - - @JsonValue - @java.lang.Override - public String toString() { - return this.value; - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java deleted file mode 100644 index daf82b139790..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleType.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = RuleType.Builder.class -) -public final class RuleType { - private final String id; - - private final String name; - - private final Optional description; - - private RuleType(String id, String name, Optional description) { - this.id = id; - this.name = name; - this.description = description; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("description") - public Optional getDescription() { - return description; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleType && equalTo((RuleType) other); - } - - private boolean equalTo(RuleType other) { - return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.description); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(String id); - - Builder from(RuleType other); - } - - public interface NameStage { - _FinalStage name(String name); - } - - public interface _FinalStage { - RuleType build(); - - _FinalStage description(Optional description); - - _FinalStage description(String description); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, NameStage, _FinalStage { - private String id; - - private String name; - - private Optional description = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(RuleType other) { - id(other.getId()); - name(other.getName()); - description(other.getDescription()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public _FinalStage name(String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage description(String description) { - this.description = Optional.ofNullable(description); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "description", - nulls = Nulls.SKIP - ) - public _FinalStage description(Optional description) { - this.description = description; - return this; - } - - @java.lang.Override - public RuleType build() { - return new RuleType(id, name, description); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java deleted file mode 100644 index 3aa67d711e00..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = RuleTypeSearchResponse.Builder.class -) -public final class RuleTypeSearchResponse { - private final PagingCursors paging; - - private final Optional> results; - - private RuleTypeSearchResponse(PagingCursors paging, Optional> results) { - this.paging = paging; - this.results = results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); - } - - private boolean equalTo(RuleTypeSearchResponse other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(PagingCursors paging); - - Builder from(RuleTypeSearchResponse other); - } - - public interface _FinalStage { - RuleTypeSearchResponse build(); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(RuleTypeSearchResponse other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter( - value = "results", - nulls = Nulls.SKIP - ) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public RuleTypeSearchResponse build() { - return new RuleTypeSearchResponse(paging, results); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java deleted file mode 100644 index 12f5b0483346..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/User.java +++ /dev/null @@ -1,116 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = User.Builder.class -) -public final class User { - private final String id; - - private final String email; - - private User(String id, String email) { - this.id = id; - this.email = email; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("email") - public String getEmail() { - return email; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof User && equalTo((User) other); - } - - private boolean equalTo(User other) { - return id.equals(other.id) && email.equals(other.email); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.email); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - EmailStage id(String id); - - Builder from(User other); - } - - public interface EmailStage { - _FinalStage email(String email); - } - - public interface _FinalStage { - User build(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, EmailStage, _FinalStage { - private String id; - - private String email; - - private Builder() { - } - - @java.lang.Override - public Builder from(User other) { - id(other.getId()); - email(other.getEmail()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public EmailStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("email") - public _FinalStage email(String email) { - this.email = Objects.requireNonNull(email, "email must not be null"); - return this; - } - - @java.lang.Override - public User build() { - return new User(id, email); - } - } -} diff --git a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java b/seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java deleted file mode 100644 index b78b2fb2b2e2..000000000000 --- a/seed/java-model/allof-inline/src/main/java/com/seed/api/model/UserSearchResponse.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = UserSearchResponse.Builder.class -) -public final class UserSearchResponse { - private final PagingCursors paging; - - private final Optional> results; - - private UserSearchResponse(PagingCursors paging, Optional> results) { - this.paging = paging; - this.results = results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); - } - - private boolean equalTo(UserSearchResponse other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(PagingCursors paging); - - Builder from(UserSearchResponse other); - } - - public interface _FinalStage { - UserSearchResponse build(); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(UserSearchResponse other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter( - value = "results", - nulls = Nulls.SKIP - ) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public UserSearchResponse build() { - return new UserSearchResponse(paging, results); - } - } -} diff --git a/seed/java-model/allof/.github/workflows/ci.yml b/seed/java-model/allof/.github/workflows/ci.yml deleted file mode 100644 index 09c8c666ad73..000000000000 --- a/seed/java-model/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Compile - run: ./gradlew compileJava - - test: - needs: [ compile ] - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Test - run: ./gradlew test - publish: - needs: [ compile, test ] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Publish to maven - run: | - ./gradlew publish - env: - MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-model/allof/.gitignore b/seed/java-model/allof/.gitignore deleted file mode 100644 index d4199abc2cd4..000000000000 --- a/seed/java-model/allof/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -*.class -.project -.gradle -? -.classpath -.checkstyle -.settings -.node -build - -# IntelliJ -*.iml -*.ipr -*.iws -.idea/ -out/ - -# Eclipse/IntelliJ APT -generated_src/ -generated_testSrc/ -generated/ - -bin -build \ No newline at end of file diff --git a/seed/java-model/allof/build.gradle b/seed/java-model/allof/build.gradle deleted file mode 100644 index 79416f4ae6b9..000000000000 --- a/seed/java-model/allof/build.gradle +++ /dev/null @@ -1,98 +0,0 @@ -plugins { - id 'java-library' - id 'maven-publish' - id 'com.diffplug.spotless' version '6.11.0' -} - -repositories { - mavenCentral() - maven { - url 'https://s01.oss.sonatype.org/content/repositories/releases/' - } -} - -dependencies { - api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' -} - - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -tasks.withType(Javadoc) { - failOnError false - options.addStringOption('Xdoclint:none', '-quiet') -} - -spotless { - java { - palantirJavaFormat() - } -} - - -java { - withSourcesJar() - withJavadocJar() -} - - -group = 'com.fern' - -version = '0.0.1' - -jar { - dependsOn(":generatePomFileForMavenPublication") - archiveBaseName = "allof" -} - -sourcesJar { - archiveBaseName = "allof" -} - -javadocJar { - archiveBaseName = "allof" -} - -test { - useJUnitPlatform() - testLogging { - showStandardStreams = true - } -} - -publishing { - publications { - maven(MavenPublication) { - groupId = 'com.fern' - artifactId = 'allof' - version = '0.0.1' - from components.java - pom { - licenses { - license { - name = 'The MIT License (MIT)' - url = 'https://mit-license.org/' - } - } - scm { - connection = 'scm:git:git://github.com/allof/fern.git' - developerConnection = 'scm:git:git://github.com/allof/fern.git' - url = 'https://github.com/allof/fern' - } - } - } - } - repositories { - maven { - url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" - credentials { - username "$System.env.MAVEN_USERNAME" - password "$System.env.MAVEN_PASSWORD" - } - } - } -} - diff --git a/seed/java-model/allof/settings.gradle b/seed/java-model/allof/settings.gradle deleted file mode 100644 index 418cc33300ad..000000000000 --- a/seed/java-model/allof/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'allof' - diff --git a/seed/java-model/allof/snippet.json b/seed/java-model/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java deleted file mode 100644 index b67a9041b1f5..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalQueries; - -/** - * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. - */ -class DateTimeDeserializer extends JsonDeserializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); - } - - /** - * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - JsonToken token = parser.currentToken(); - if (token == JsonToken.VALUE_NUMBER_INT) { - return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); - } else { - TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( - parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); - - if (temporal.query(TemporalQueries.offset()) == null) { - return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); - } else { - return OffsetDateTime.from(temporal); - } - } - } -} \ No newline at end of file diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java deleted file mode 100644 index 0d2114931cb1..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/core/DoubleSerializer.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; - -/** - * Custom serializer that writes integer-valued doubles without a decimal point. - * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. - * Non-integer values like {@code 3.14} are serialized normally. - */ -class DoubleSerializer extends JsonSerializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule() - .addSerializer(Double.class, new DoubleSerializer()) - .addSerializer(double.class, new DoubleSerializer()); - } - - /** - * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { - gen.writeNumber(value.longValue()); - } else { - gen.writeNumber(value); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java deleted file mode 100644 index f294ae327e6d..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/core/ObjectMappers.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.io.IOException; -import java.lang.Integer; -import java.lang.Object; -import java.lang.String; - -public final class ObjectMappers { - public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() - .addModule(new Jdk8Module()) - .addModule(new JavaTimeModule()) - .addModule(DateTimeDeserializer.getModule()) - .addModule(DoubleSerializer.getModule()) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .build(); - - private ObjectMappers() { - } - - public static String stringify(Object o) { - try { - return JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS) - .writerWithDefaultPrettyPrinter() - .writeValueAsString(o); - } - catch (IOException e) { - return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); - } - } - - public static Object parseErrorBody(String responseBodyString) { - try { - return JSON_MAPPER.readValue(responseBodyString, Object.class); - } - catch (JsonProcessingException ignored) { - return responseBodyString; - } - } - } diff --git a/seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java deleted file mode 100644 index a75a230a0fe6..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import java.io.IOException; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; - -/** - * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. - * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. - */ -public class Rfc2822DateTimeDeserializer extends JsonDeserializer { - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - String raw = parser.getValueAsString(); - return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java b/seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java deleted file mode 100644 index ff26f71f1d98..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/AuditInfo.java +++ /dev/null @@ -1,196 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.time.OffsetDateTime; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = AuditInfo.Builder.class -) -public final class AuditInfo implements IAuditInfo { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private AuditInfo(Optional createdBy, Optional createdDateTime, - Optional modifiedBy, Optional modifiedDateTime) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - @java.lang.Override - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - @java.lang.Override - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - @java.lang.Override - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - @java.lang.Override - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AuditInfo && equalTo((AuditInfo) other); - } - - private boolean equalTo(AuditInfo other) { - return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder { - private Optional createdBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - private Builder() { - } - - public Builder from(AuditInfo other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - return this; - } - - /** - *

The user who created this resource.

- */ - @JsonSetter( - value = "createdBy", - nulls = Nulls.SKIP - ) - public Builder createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - public Builder createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

When this resource was created.

- */ - @JsonSetter( - value = "createdDateTime", - nulls = Nulls.SKIP - ) - public Builder createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - public Builder createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @JsonSetter( - value = "modifiedBy", - nulls = Nulls.SKIP - ) - public Builder modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - public Builder modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

When this resource was last modified.

- */ - @JsonSetter( - value = "modifiedDateTime", - nulls = Nulls.SKIP - ) - public Builder modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - public AuditInfo build() { - return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java b/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java deleted file mode 100644 index 00535bf9e0c9..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrg.java +++ /dev/null @@ -1,127 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = BaseOrg.Builder.class -) -public final class BaseOrg { - private final String id; - - private final Optional metadata; - - private BaseOrg(String id, Optional metadata) { - this.id = id; - this.metadata = metadata; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrg && equalTo((BaseOrg) other); - } - - private boolean equalTo(BaseOrg other) { - return id.equals(other.id) && metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - _FinalStage id(String id); - - Builder from(BaseOrg other); - } - - public interface _FinalStage { - BaseOrg build(); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(BaseOrgMetadata metadata); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional metadata = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(BaseOrg other) { - id(other.getId()); - metadata(other.getMetadata()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(BaseOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "metadata", - nulls = Nulls.SKIP - ) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public BaseOrg build() { - return new BaseOrg(id, metadata); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java b/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java deleted file mode 100644 index 6ccd08740868..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/BaseOrgMetadata.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = BaseOrgMetadata.Builder.class -) -public final class BaseOrgMetadata { - private final String region; - - private final Optional tier; - - private BaseOrgMetadata(String region, Optional tier) { - this.region = region; - this.tier = tier; - } - - /** - * @return Deployment region from BaseOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Subscription tier. - */ - @JsonProperty("tier") - public Optional getTier() { - return tier; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); - } - - private boolean equalTo(BaseOrgMetadata other) { - return region.equals(other.region) && tier.equals(other.tier); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.tier); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from BaseOrg.

- */ - _FinalStage region(String region); - - Builder from(BaseOrgMetadata other); - } - - public interface _FinalStage { - BaseOrgMetadata build(); - - /** - *

Subscription tier.

- */ - _FinalStage tier(Optional tier); - - _FinalStage tier(String tier); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional tier = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(BaseOrgMetadata other) { - region(other.getRegion()); - tier(other.getTier()); - return this; - } - - /** - *

Deployment region from BaseOrg.

- *

Deployment region from BaseOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Subscription tier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage tier(String tier) { - this.tier = Optional.ofNullable(tier); - return this; - } - - /** - *

Subscription tier.

- */ - @java.lang.Override - @JsonSetter( - value = "tier", - nulls = Nulls.SKIP - ) - public _FinalStage tier(Optional tier) { - this.tier = tier; - return this; - } - - @java.lang.Override - public BaseOrgMetadata build() { - return new BaseOrgMetadata(region, tier); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java b/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java deleted file mode 100644 index 20ce10be75f4..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntity.java +++ /dev/null @@ -1,218 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = CombinedEntity.Builder.class -) -public final class CombinedEntity { - private final CombinedEntityStatus status; - - private final String id; - - private final Optional name; - - private final Optional summary; - - private CombinedEntity(CombinedEntityStatus status, String id, Optional name, - Optional summary) { - this.status = status; - this.id = id; - this.name = name; - this.summary = summary; - } - - @JsonProperty("status") - public CombinedEntityStatus getStatus() { - return status; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Identifiable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof CombinedEntity && equalTo((CombinedEntity) other); - } - - private boolean equalTo(CombinedEntity other) { - return status.equals(other.status) && id.equals(other.id) && name.equals(other.name) && summary.equals(other.summary); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.status, this.id, this.name, this.summary); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static StatusStage builder() { - return new Builder(); - } - - public interface StatusStage { - IdStage status(CombinedEntityStatus status); - - Builder from(CombinedEntity other); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - _FinalStage id(String id); - } - - public interface _FinalStage { - CombinedEntity build(); - - /** - *

Display name from Identifiable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - - /** - *

A short summary.

- */ - _FinalStage summary(Optional summary); - - _FinalStage summary(String summary); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements StatusStage, IdStage, _FinalStage { - private CombinedEntityStatus status; - - private String id; - - private Optional summary = Optional.empty(); - - private Optional name = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(CombinedEntity other) { - status(other.getStatus()); - id(other.getId()); - name(other.getName()); - summary(other.getSummary()); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public IdStage status(CombinedEntityStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - /** - *

A short summary.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - /** - *

A short summary.

- */ - @java.lang.Override - @JsonSetter( - value = "summary", - nulls = Nulls.SKIP - ) - public _FinalStage summary(Optional summary) { - this.summary = summary; - return this; - } - - /** - *

Display name from Identifiable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Identifiable.

- */ - @java.lang.Override - @JsonSetter( - value = "name", - nulls = Nulls.SKIP - ) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public CombinedEntity build() { - return new CombinedEntity(status, id, name, summary); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java b/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java deleted file mode 100644 index 12b6e20c62fa..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/CombinedEntityStatus.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonValue; -import java.lang.String; - -public enum CombinedEntityStatus { - ACTIVE("active"), - - ARCHIVED("archived"); - - private final String value; - - CombinedEntityStatus(String value) { - this.value = value; - } - - @JsonValue - @java.lang.Override - public String toString() { - return this.value; - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java b/seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java deleted file mode 100644 index 47749f51c511..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/Describable.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = Describable.Builder.class -) -public final class Describable { - private final Optional name; - - private final Optional summary; - - private Describable(Optional name, Optional summary) { - this.name = name; - this.summary = summary; - } - - /** - * @return Display name from Describable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Describable && equalTo((Describable) other); - } - - private boolean equalTo(Describable other) { - return name.equals(other.name) && summary.equals(other.summary); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.summary); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder { - private Optional name = Optional.empty(); - - private Optional summary = Optional.empty(); - - private Builder() { - } - - public Builder from(Describable other) { - name(other.getName()); - summary(other.getSummary()); - return this; - } - - /** - *

Display name from Describable.

- */ - @JsonSetter( - value = "name", - nulls = Nulls.SKIP - ) - public Builder name(Optional name) { - this.name = name; - return this; - } - - public Builder name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

A short summary.

- */ - @JsonSetter( - value = "summary", - nulls = Nulls.SKIP - ) - public Builder summary(Optional summary) { - this.summary = summary; - return this; - } - - public Builder summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - public Describable build() { - return new Describable(name, summary); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java b/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java deleted file mode 100644 index 2673e956d206..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrg.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = DetailedOrg.Builder.class -) -public final class DetailedOrg { - private final Optional metadata; - - private DetailedOrg(Optional metadata) { - this.metadata = metadata; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrg && equalTo((DetailedOrg) other); - } - - private boolean equalTo(DetailedOrg other) { - return metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder { - private Optional metadata = Optional.empty(); - - private Builder() { - } - - public Builder from(DetailedOrg other) { - metadata(other.getMetadata()); - return this; - } - - @JsonSetter( - value = "metadata", - nulls = Nulls.SKIP - ) - public Builder metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - public Builder metadata(DetailedOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - public DetailedOrg build() { - return new DetailedOrg(metadata); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java b/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java deleted file mode 100644 index db3256745178..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/DetailedOrgMetadata.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = DetailedOrgMetadata.Builder.class -) -public final class DetailedOrgMetadata { - private final String region; - - private final Optional domain; - - private DetailedOrgMetadata(String region, Optional domain) { - this.region = region; - this.domain = domain; - } - - /** - * @return Deployment region from DetailedOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Custom domain name. - */ - @JsonProperty("domain") - public Optional getDomain() { - return domain; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); - } - - private boolean equalTo(DetailedOrgMetadata other) { - return region.equals(other.region) && domain.equals(other.domain); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.domain); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from DetailedOrg.

- */ - _FinalStage region(String region); - - Builder from(DetailedOrgMetadata other); - } - - public interface _FinalStage { - DetailedOrgMetadata build(); - - /** - *

Custom domain name.

- */ - _FinalStage domain(Optional domain); - - _FinalStage domain(String domain); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional domain = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(DetailedOrgMetadata other) { - region(other.getRegion()); - domain(other.getDomain()); - return this; - } - - /** - *

Deployment region from DetailedOrg.

- *

Deployment region from DetailedOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Custom domain name.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage domain(String domain) { - this.domain = Optional.ofNullable(domain); - return this; - } - - /** - *

Custom domain name.

- */ - @java.lang.Override - @JsonSetter( - value = "domain", - nulls = Nulls.SKIP - ) - public _FinalStage domain(Optional domain) { - this.domain = domain; - return this; - } - - @java.lang.Override - public DetailedOrgMetadata build() { - return new DetailedOrgMetadata(region, domain); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java b/seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java deleted file mode 100644 index 381064953ef7..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/IAuditInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import java.lang.String; -import java.time.OffsetDateTime; -import java.util.Optional; - -public interface IAuditInfo { - Optional getCreatedBy(); - - Optional getCreatedDateTime(); - - Optional getModifiedBy(); - - Optional getModifiedDateTime(); -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java b/seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java deleted file mode 100644 index c9dfcb3179ad..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/Identifiable.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = Identifiable.Builder.class -) -public final class Identifiable { - private final String id; - - private final Optional name; - - private Identifiable(String id, Optional name) { - this.id = id; - this.name = name; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Identifiable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Identifiable && equalTo((Identifiable) other); - } - - private boolean equalTo(Identifiable other) { - return id.equals(other.id) && name.equals(other.name); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - _FinalStage id(String id); - - Builder from(Identifiable other); - } - - public interface _FinalStage { - Identifiable build(); - - /** - *

Display name from Identifiable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional name = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(Identifiable other) { - id(other.getId()); - name(other.getName()); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - /** - *

Display name from Identifiable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Identifiable.

- */ - @java.lang.Override - @JsonSetter( - value = "name", - nulls = Nulls.SKIP - ) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public Identifiable build() { - return new Identifiable(id, name); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java b/seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java deleted file mode 100644 index afcaed6668f1..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/Organization.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = Organization.Builder.class -) -public final class Organization { - private final String name; - - private final String id; - - private final Optional metadata; - - private Organization(String name, String id, Optional metadata) { - this.name = name; - this.id = id; - this.metadata = metadata; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Organization && equalTo((Organization) other); - } - - private boolean equalTo(Organization other) { - return name.equals(other.name) && id.equals(other.id) && metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.id, this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NameStage builder() { - return new Builder(); - } - - public interface NameStage { - IdStage name(String name); - - Builder from(Organization other); - } - - public interface IdStage { - _FinalStage id(String id); - } - - public interface _FinalStage { - Organization build(); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(BaseOrgMetadata metadata); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements NameStage, IdStage, _FinalStage { - private String name; - - private String id; - - private Optional metadata = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(Organization other) { - name(other.getName()); - id(other.getId()); - metadata(other.getMetadata()); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public IdStage name(String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(BaseOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "metadata", - nulls = Nulls.SKIP - ) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public Organization build() { - return new Organization(name, id, metadata); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java b/seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java deleted file mode 100644 index f01c8cb01a89..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/PaginatedResult.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = PaginatedResult.Builder.class -) -public final class PaginatedResult { - private final PagingCursors paging; - - private final List results; - - private PaginatedResult(PagingCursors paging, List results) { - this.paging = paging; - this.results = results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public List getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PaginatedResult && equalTo((PaginatedResult) other); - } - - private boolean equalTo(PaginatedResult other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(PagingCursors paging); - - Builder from(PaginatedResult other); - } - - public interface _FinalStage { - PaginatedResult build(); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(List results); - - _FinalStage addResults(Object results); - - _FinalStage addAllResults(List results); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private List results = new ArrayList<>(); - - private Builder() { - } - - @java.lang.Override - public Builder from(PaginatedResult other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addAllResults(List results) { - if (results != null) { - this.results.addAll(results); - } - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addResults(Object results) { - this.results.add(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter( - value = "results", - nulls = Nulls.SKIP - ) - public _FinalStage results(List results) { - this.results.clear(); - if (results != null) { - this.results.addAll(results); - } - return this; - } - - @java.lang.Override - public PaginatedResult build() { - return new PaginatedResult(paging, results); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java b/seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java deleted file mode 100644 index 0d89da26698b..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/PagingCursors.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = PagingCursors.Builder.class -) -public final class PagingCursors { - private final String next; - - private final Optional previous; - - private PagingCursors(String next, Optional previous) { - this.next = next; - this.previous = previous; - } - - /** - * @return Cursor for the next page of results. - */ - @JsonProperty("next") - public String getNext() { - return next; - } - - /** - * @return Cursor for the previous page of results. - */ - @JsonProperty("previous") - public Optional getPrevious() { - return previous; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PagingCursors && equalTo((PagingCursors) other); - } - - private boolean equalTo(PagingCursors other) { - return next.equals(other.next) && previous.equals(other.previous); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.next, this.previous); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NextStage builder() { - return new Builder(); - } - - public interface NextStage { - /** - *

Cursor for the next page of results.

- */ - _FinalStage next(String next); - - Builder from(PagingCursors other); - } - - public interface _FinalStage { - PagingCursors build(); - - /** - *

Cursor for the previous page of results.

- */ - _FinalStage previous(Optional previous); - - _FinalStage previous(String previous); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements NextStage, _FinalStage { - private String next; - - private Optional previous = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(PagingCursors other) { - next(other.getNext()); - previous(other.getPrevious()); - return this; - } - - /** - *

Cursor for the next page of results.

- *

Cursor for the next page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("next") - public _FinalStage next(String next) { - this.next = Objects.requireNonNull(next, "next must not be null"); - return this; - } - - /** - *

Cursor for the previous page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage previous(String previous) { - this.previous = Optional.ofNullable(previous); - return this; - } - - /** - *

Cursor for the previous page of results.

- */ - @java.lang.Override - @JsonSetter( - value = "previous", - nulls = Nulls.SKIP - ) - public _FinalStage previous(Optional previous) { - this.previous = previous; - return this; - } - - @java.lang.Override - public PagingCursors build() { - return new PagingCursors(next, previous); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java deleted file mode 100644 index 93327278e6e3..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleExecutionContext.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonValue; -import java.lang.String; - -public enum RuleExecutionContext { - PROD("prod"), - - STAGING("staging"), - - DEV("dev"); - - private final String value; - - RuleExecutionContext(String value) { - this.value = value; - } - - @JsonValue - @java.lang.Override - public String toString() { - return this.value; - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java deleted file mode 100644 index 20b4bae74875..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponse.java +++ /dev/null @@ -1,354 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.time.OffsetDateTime; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = RuleResponse.Builder.class -) -public final class RuleResponse implements IAuditInfo { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private final String id; - - private final String name; - - private final RuleResponseStatus status; - - private final Optional executionContext; - - private RuleResponse(Optional createdBy, Optional createdDateTime, - Optional modifiedBy, Optional modifiedDateTime, String id, - String name, RuleResponseStatus status, Optional executionContext) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - this.id = id; - this.name = name; - this.status = status; - this.executionContext = executionContext; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - @java.lang.Override - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - @java.lang.Override - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - @java.lang.Override - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - @java.lang.Override - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("status") - public RuleResponseStatus getStatus() { - return status; - } - - @JsonProperty("executionContext") - public Optional getExecutionContext() { - return executionContext; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleResponse && equalTo((RuleResponse) other); - } - - private boolean equalTo(RuleResponse other) { - return createdBy.equals(other.createdBy) && createdDateTime.equals(other.createdDateTime) && modifiedBy.equals(other.modifiedBy) && modifiedDateTime.equals(other.modifiedDateTime) && id.equals(other.id) && name.equals(other.name) && status.equals(other.status) && executionContext.equals(other.executionContext); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime, this.id, this.name, this.status, this.executionContext); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(String id); - - Builder from(RuleResponse other); - } - - public interface NameStage { - StatusStage name(String name); - } - - public interface StatusStage { - _FinalStage status(RuleResponseStatus status); - } - - public interface _FinalStage { - RuleResponse build(); - - /** - *

The user who created this resource.

- */ - _FinalStage createdBy(Optional createdBy); - - _FinalStage createdBy(String createdBy); - - /** - *

When this resource was created.

- */ - _FinalStage createdDateTime(Optional createdDateTime); - - _FinalStage createdDateTime(OffsetDateTime createdDateTime); - - /** - *

The user who last modified this resource.

- */ - _FinalStage modifiedBy(Optional modifiedBy); - - _FinalStage modifiedBy(String modifiedBy); - - /** - *

When this resource was last modified.

- */ - _FinalStage modifiedDateTime(Optional modifiedDateTime); - - _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); - - _FinalStage executionContext(Optional executionContext); - - _FinalStage executionContext(RuleExecutionContext executionContext); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { - private String id; - - private String name; - - private RuleResponseStatus status; - - private Optional executionContext = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional createdBy = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(RuleResponse other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - id(other.getId()); - name(other.getName()); - status(other.getStatus()); - executionContext(other.getExecutionContext()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public StatusStage name(String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public _FinalStage status(RuleResponseStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage executionContext(RuleExecutionContext executionContext) { - this.executionContext = Optional.ofNullable(executionContext); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "executionContext", - nulls = Nulls.SKIP - ) - public _FinalStage executionContext(Optional executionContext) { - this.executionContext = executionContext; - return this; - } - - /** - *

When this resource was last modified.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - /** - *

When this resource was last modified.

- */ - @java.lang.Override - @JsonSetter( - value = "modifiedDateTime", - nulls = Nulls.SKIP - ) - public _FinalStage modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - /** - *

The user who last modified this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @java.lang.Override - @JsonSetter( - value = "modifiedBy", - nulls = Nulls.SKIP - ) - public _FinalStage modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - /** - *

When this resource was created.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

When this resource was created.

- */ - @java.lang.Override - @JsonSetter( - value = "createdDateTime", - nulls = Nulls.SKIP - ) - public _FinalStage createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - /** - *

The user who created this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

The user who created this resource.

- */ - @java.lang.Override - @JsonSetter( - value = "createdBy", - nulls = Nulls.SKIP - ) - public _FinalStage createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - @java.lang.Override - public RuleResponse build() { - return new RuleResponse(createdBy, createdDateTime, modifiedBy, modifiedDateTime, id, name, status, executionContext); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java deleted file mode 100644 index 302cf6780280..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleResponseStatus.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonValue; -import java.lang.String; - -public enum RuleResponseStatus { - ACTIVE("active"), - - INACTIVE("inactive"), - - DRAFT("draft"); - - private final String value; - - RuleResponseStatus(String value) { - this.value = value; - } - - @JsonValue - @java.lang.Override - public String toString() { - return this.value; - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java deleted file mode 100644 index daf82b139790..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleType.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = RuleType.Builder.class -) -public final class RuleType { - private final String id; - - private final String name; - - private final Optional description; - - private RuleType(String id, String name, Optional description) { - this.id = id; - this.name = name; - this.description = description; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("description") - public Optional getDescription() { - return description; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleType && equalTo((RuleType) other); - } - - private boolean equalTo(RuleType other) { - return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.description); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(String id); - - Builder from(RuleType other); - } - - public interface NameStage { - _FinalStage name(String name); - } - - public interface _FinalStage { - RuleType build(); - - _FinalStage description(Optional description); - - _FinalStage description(String description); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, NameStage, _FinalStage { - private String id; - - private String name; - - private Optional description = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(RuleType other) { - id(other.getId()); - name(other.getName()); - description(other.getDescription()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public _FinalStage name(String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage description(String description) { - this.description = Optional.ofNullable(description); - return this; - } - - @java.lang.Override - @JsonSetter( - value = "description", - nulls = Nulls.SKIP - ) - public _FinalStage description(Optional description) { - this.description = description; - return this; - } - - @java.lang.Override - public RuleType build() { - return new RuleType(id, name, description); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java b/seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java deleted file mode 100644 index dc70bd9a6364..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/RuleTypeSearchResponse.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = RuleTypeSearchResponse.Builder.class -) -public final class RuleTypeSearchResponse { - private final Optional> results; - - private final PagingCursors paging; - - private RuleTypeSearchResponse(Optional> results, PagingCursors paging) { - this.results = results; - this.paging = paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); - } - - private boolean equalTo(RuleTypeSearchResponse other) { - return results.equals(other.results) && paging.equals(other.paging); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.results, this.paging); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(PagingCursors paging); - - Builder from(RuleTypeSearchResponse other); - } - - public interface _FinalStage { - RuleTypeSearchResponse build(); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(RuleTypeSearchResponse other) { - results(other.getResults()); - paging(other.getPaging()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter( - value = "results", - nulls = Nulls.SKIP - ) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public RuleTypeSearchResponse build() { - return new RuleTypeSearchResponse(results, paging); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/User.java b/seed/java-model/allof/src/main/java/com/seed/api/model/User.java deleted file mode 100644 index 12f5b0483346..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/User.java +++ /dev/null @@ -1,116 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.Objects; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = User.Builder.class -) -public final class User { - private final String id; - - private final String email; - - private User(String id, String email) { - this.id = id; - this.email = email; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("email") - public String getEmail() { - return email; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof User && equalTo((User) other); - } - - private boolean equalTo(User other) { - return id.equals(other.id) && email.equals(other.email); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.email); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - EmailStage id(String id); - - Builder from(User other); - } - - public interface EmailStage { - _FinalStage email(String email); - } - - public interface _FinalStage { - User build(); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements IdStage, EmailStage, _FinalStage { - private String id; - - private String email; - - private Builder() { - } - - @java.lang.Override - public Builder from(User other) { - id(other.getId()); - email(other.getEmail()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public EmailStage id(String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("email") - public _FinalStage email(String email) { - this.email = Objects.requireNonNull(email, "email must not be null"); - return this; - } - - @java.lang.Override - public User build() { - return new User(id, email); - } - } -} diff --git a/seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java b/seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java deleted file mode 100644 index 0bd6c146a72f..000000000000 --- a/seed/java-model/allof/src/main/java/com/seed/api/model/UserSearchResponse.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package com.seed.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.lang.Object; -import java.lang.String; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize( - builder = UserSearchResponse.Builder.class -) -public final class UserSearchResponse { - private final Optional> results; - - private final PagingCursors paging; - - private UserSearchResponse(Optional> results, PagingCursors paging) { - this.results = results; - this.paging = paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); - } - - private boolean equalTo(UserSearchResponse other) { - return results.equals(other.results) && paging.equals(other.paging); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.results, this.paging); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(PagingCursors paging); - - Builder from(UserSearchResponse other); - } - - public interface _FinalStage { - UserSearchResponse build(); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties( - ignoreUnknown = true - ) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - private Builder() { - } - - @java.lang.Override - public Builder from(UserSearchResponse other) { - results(other.getResults()); - paging(other.getPaging()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter( - value = "results", - nulls = Nulls.SKIP - ) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public UserSearchResponse build() { - return new UserSearchResponse(results, paging); - } - } -} diff --git a/seed/java-sdk/allof-inline/.fern/metadata.json b/seed/java-sdk/allof-inline/.fern/metadata.json deleted file mode 100644 index 762fcfc814db..000000000000 --- a/seed/java-sdk/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/.github/workflows/ci.yml b/seed/java-sdk/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 09c8c666ad73..000000000000 --- a/seed/java-sdk/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Compile - run: ./gradlew compileJava - - test: - needs: [ compile ] - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Test - run: ./gradlew test - publish: - needs: [ compile, test ] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Publish to maven - run: | - ./gradlew publish - env: - MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/allof-inline/.gitignore b/seed/java-sdk/allof-inline/.gitignore deleted file mode 100644 index d4199abc2cd4..000000000000 --- a/seed/java-sdk/allof-inline/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -*.class -.project -.gradle -? -.classpath -.checkstyle -.settings -.node -build - -# IntelliJ -*.iml -*.ipr -*.iws -.idea/ -out/ - -# Eclipse/IntelliJ APT -generated_src/ -generated_testSrc/ -generated/ - -bin -build \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/README.md b/seed/java-sdk/allof-inline/README.md deleted file mode 100644 index de880cbffb2d..000000000000 --- a/seed/java-sdk/allof-inline/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# Seed Java Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FJava) -[![Maven Central](https://img.shields.io/maven-central/v/com.fern/allof-inline)](https://central.sonatype.com/artifact/com.fern/allof-inline) - -The Seed Java library provides convenient access to the Seed APIs from Java. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Base Url](#base-url) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Custom Client](#custom-client) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Custom Headers](#custom-headers) - - [Access Raw Response Data](#access-raw-response-data) -- [Contributing](#contributing) - -## Installation - -### Gradle - -Add the dependency in your `build.gradle` file: - -```groovy -dependencies { - implementation 'com.fern:allof-inline:0.0.1' -} -``` - -### Maven - -Add the dependency in your `pom.xml` file: - -```xml - - com.fern - allof-inline - 0.0.1 - -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```java -package com.example.usage; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.types.RuleExecutionContext; - -public class Example { - public static void main(String[] args) { - SeedApiClient client = SeedApiClient - .builder() - .build(); - - client.createRule( - RuleCreateRequest - .builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build() - ); - } -} -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```java -import com.seed.api.SeedApiClient; -import com.seed.api.core.Environment; - -SeedApiClient client = SeedApiClient - .builder() - .environment(Environment.Default) - .build(); -``` - -## Base Url - -You can set a custom base URL when constructing the client. - -```java -import com.seed.api.SeedApiClient; - -SeedApiClient client = SeedApiClient - .builder() - .url("https://example.com") - .build(); -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), an API exception will be thrown. - -```java -import com.seed.api.core.SeedApiApiException; - -try{ - client.createRule(...); -} catch (SeedApiApiException e){ - // Do something with the API exception... -} -``` - -## Advanced - -### Custom Client - -This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one. -However, you can pass your own client like so: - -```java -import com.seed.api.SeedApiClient; -import okhttp3.OkHttpClient; - -OkHttpClient customClient = ...; - -SeedApiClient client = SeedApiClient - .builder() - .httpClient(customClient) - .build(); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). Before defaulting to exponential backoff, the SDK will first attempt to respect -the `Retry-After` header (as either in seconds or as an HTTP date), and then the `X-RateLimit-Reset` header -(as a Unix timestamp in epoch seconds); failing both of those, it will fall back to exponential backoff. - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` client option to configure this behavior. - -```java -import com.seed.api.SeedApiClient; - -SeedApiClient client = SeedApiClient - .builder() - .maxRetries(1) - .build(); -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. -```java -import com.seed.api.SeedApiClient; -import com.seed.api.core.RequestOptions; - -// Client level -SeedApiClient client = SeedApiClient - .builder() - .timeout(60) - .build(); - -// Request level -client.createRule( - ..., - RequestOptions - .builder() - .timeout(60) - .build() -); -``` - -### Custom Headers - -The SDK allows you to add custom headers to requests. You can configure headers at the client level or at the request level. - -```java -import com.seed.api.SeedApiClient; -import com.seed.api.core.RequestOptions; - -// Client level -SeedApiClient client = SeedApiClient - .builder() - .addHeader("X-Custom-Header", "custom-value") - .addHeader("X-Request-Id", "abc-123") - .build(); -; - -// Request level -client.createRule( - ..., - RequestOptions - .builder() - .addHeader("X-Request-Header", "request-value") - .build() -); -``` - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `withRawResponse()` method. -The `withRawResponse()` method returns a raw client that wraps all responses with `body()` and `headers()` methods. -(A normal client's `response` is identical to a raw client's `response.body()`.) - -```java -SeedApiHttpResponse response = client.withRawResponse().createRule(...); - -System.out.println(response.body()); -System.out.println(response.headers().get("X-My-Header")); -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/java-sdk/allof-inline/build.gradle b/seed/java-sdk/allof-inline/build.gradle deleted file mode 100644 index 46dc64a52ca1..000000000000 --- a/seed/java-sdk/allof-inline/build.gradle +++ /dev/null @@ -1,102 +0,0 @@ -plugins { - id 'java-library' - id 'maven-publish' - id 'com.diffplug.spotless' version '6.11.0' -} - -repositories { - mavenCentral() - maven { - url 'https://s01.oss.sonatype.org/content/repositories/releases/' - } -} - -dependencies { - api 'com.squareup.okhttp3:okhttp:5.2.1' - api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' -} - - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -tasks.withType(Javadoc) { - failOnError false - options.addStringOption('Xdoclint:none', '-quiet') -} - -spotless { - java { - palantirJavaFormat() - } -} - - -java { - withSourcesJar() - withJavadocJar() -} - - -group = 'com.fern' - -version = '0.0.1' - -jar { - dependsOn(":generatePomFileForMavenPublication") - archiveBaseName = "allof-inline" -} - -sourcesJar { - archiveBaseName = "allof-inline" -} - -javadocJar { - archiveBaseName = "allof-inline" -} - -test { - useJUnitPlatform() - testLogging { - showStandardStreams = true - } -} - -publishing { - publications { - maven(MavenPublication) { - groupId = 'com.fern' - artifactId = 'allof-inline' - version = '0.0.1' - from components.java - pom { - licenses { - license { - name = 'The MIT License (MIT)' - url = 'https://mit-license.org/' - } - } - scm { - connection = 'scm:git:git://github.com/allof-inline/fern.git' - developerConnection = 'scm:git:git://github.com/allof-inline/fern.git' - url = 'https://github.com/allof-inline/fern' - } - } - } - } - repositories { - maven { - url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" - credentials { - username "$System.env.MAVEN_USERNAME" - password "$System.env.MAVEN_PASSWORD" - } - } - } -} - diff --git a/seed/java-sdk/allof-inline/reference.md b/seed/java-sdk/allof-inline/reference.md deleted file mode 100644 index c37cf3e44a84..000000000000 --- a/seed/java-sdk/allof-inline/reference.md +++ /dev/null @@ -1,174 +0,0 @@ -# Reference -
client.searchRuleTypes() -> RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.searchRuleTypes( - SearchRuleTypesRequest - .builder() - .build() -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `Optional` - -
-
-
-
- - -
-
-
- -
client.createRule(request) -> RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.createRule( - RuleCreateRequest - .builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build() -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `String` - -
-
- -
-
- -**executionContext:** `RuleExecutionContext` - -
-
-
-
- - -
-
-
- -
client.listUsers() -> UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.listUsers(); -``` -
-
-
-
- - -
-
-
- -
client.getEntity() -> CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.getEntity(); -``` -
-
-
-
- - -
-
-
- -
client.getOrganization() -> Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.getOrganization(); -``` -
-
-
-
- - -
-
-
- diff --git a/seed/java-sdk/allof-inline/sample-app/build.gradle b/seed/java-sdk/allof-inline/sample-app/build.gradle deleted file mode 100644 index 4ee8f227b7af..000000000000 --- a/seed/java-sdk/allof-inline/sample-app/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id 'java-library' -} - -repositories { - mavenCentral() - maven { - url 'https://s01.oss.sonatype.org/content/repositories/releases/' - } -} - -dependencies { - implementation rootProject -} - - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - diff --git a/seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java b/seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java deleted file mode 100644 index 8d293789008c..000000000000 --- a/seed/java-sdk/allof-inline/sample-app/src/main/java/sample/App.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package sample; - -import java.lang.String; - -public final class App { - public static void main(String[] args) { - // import com.seed.api.AsyncSeedApiClient - } -} diff --git a/seed/java-sdk/allof-inline/settings.gradle b/seed/java-sdk/allof-inline/settings.gradle deleted file mode 100644 index a22219aed8c4..000000000000 --- a/seed/java-sdk/allof-inline/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name = 'allof-inline' - -include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/snippet.json b/seed/java-sdk/allof-inline/snippet.json deleted file mode 100644 index 92723c195d93..000000000000 --- a/seed/java-sdk/allof-inline/snippet.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "endpoints": [ - { - "example_identifier": "b6434d4c", - "id": { - "method": "GET", - "path": "/rule-types", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "bfaefd0c", - "id": { - "method": "GET", - "path": "/rule-types", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "c1cf878e", - "id": { - "method": "POST", - "path": "/rules", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "af661062", - "id": { - "method": "POST", - "path": "/rules", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "55942cbc", - "id": { - "method": "GET", - "path": "/users", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" - } - }, - { - "example_identifier": "2bf3205", - "id": { - "method": "GET", - "path": "/users", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" - } - }, - { - "example_identifier": "b2b07150", - "id": { - "method": "GET", - "path": "/entities", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" - } - }, - { - "example_identifier": "a351d465", - "id": { - "method": "GET", - "path": "/entities", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" - } - }, - { - "example_identifier": "dfb0bc71", - "id": { - "method": "GET", - "path": "/organizations", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" - } - }, - { - "example_identifier": "c30ff6be", - "id": { - "method": "GET", - "path": "/organizations", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" - } - } - ], - "types": {} -} \ No newline at end of file diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java deleted file mode 100644 index 046deb483a7f..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncRawSeedApiClient.java +++ /dev/null @@ -1,323 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.seed.api.core.ClientOptions; -import com.seed.api.core.MediaTypes; -import com.seed.api.core.ObjectMappers; -import com.seed.api.core.QueryStringMapper; -import com.seed.api.core.RequestOptions; -import com.seed.api.core.SeedApiApiException; -import com.seed.api.core.SeedApiException; -import com.seed.api.core.SeedApiHttpResponse; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; -import java.io.IOException; -import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; - -public class AsyncRawSeedApiClient { - protected final ClientOptions clientOptions; - - public AsyncRawSeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - } - - public CompletableFuture> searchRuleTypes() { - return searchRuleTypes(SearchRuleTypesRequest.builder().build()); - } - - public CompletableFuture> searchRuleTypes( - RequestOptions requestOptions) { - return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); - } - - public CompletableFuture> searchRuleTypes( - SearchRuleTypesRequest request) { - return searchRuleTypes(request, null); - } - - public CompletableFuture> searchRuleTypes( - SearchRuleTypesRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rule-types"); - if (request.getQuery().isPresent()) { - QueryStringMapper.addQueryParameter( - httpUrl, "query", request.getQuery().get(), false); - } - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request.Builder _requestBuilder = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json"); - Request okhttpRequest = _requestBuilder.build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), - response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> createRule(RuleCreateRequest request) { - return createRule(request, null); - } - - public CompletableFuture> createRule( - RuleCreateRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rules"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - RequestBody body; - try { - body = RequestBody.create( - ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); - } catch (JsonProcessingException e) { - throw new SeedApiException("Failed to serialize request", e); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("POST", body) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> listUsers() { - return listUsers(null); - } - - public CompletableFuture> listUsers(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("users"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), - response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> getEntity() { - return getEntity(null); - } - - public CompletableFuture> getEntity(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("entities"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), - response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> getOrganization() { - return getOrganization(null); - } - - public CompletableFuture> getOrganization(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("organizations"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java deleted file mode 100644 index 45b759db04ed..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClient.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.RequestOptions; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; -import java.util.concurrent.CompletableFuture; - -public class AsyncSeedApiClient { - protected final ClientOptions clientOptions; - - private final AsyncRawSeedApiClient rawClient; - - public AsyncSeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.rawClient = new AsyncRawSeedApiClient(clientOptions); - } - - /** - * Get responses with HTTP metadata like headers - */ - public AsyncRawSeedApiClient withRawResponse() { - return this.rawClient; - } - - public CompletableFuture searchRuleTypes() { - return this.rawClient.searchRuleTypes().thenApply(response -> response.body()); - } - - public CompletableFuture searchRuleTypes(RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture searchRuleTypes(SearchRuleTypesRequest request) { - return this.rawClient.searchRuleTypes(request).thenApply(response -> response.body()); - } - - public CompletableFuture searchRuleTypes( - SearchRuleTypesRequest request, RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(request, requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture createRule(RuleCreateRequest request) { - return this.rawClient.createRule(request).thenApply(response -> response.body()); - } - - public CompletableFuture createRule(RuleCreateRequest request, RequestOptions requestOptions) { - return this.rawClient.createRule(request, requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture listUsers() { - return this.rawClient.listUsers().thenApply(response -> response.body()); - } - - public CompletableFuture listUsers(RequestOptions requestOptions) { - return this.rawClient.listUsers(requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture getEntity() { - return this.rawClient.getEntity().thenApply(response -> response.body()); - } - - public CompletableFuture getEntity(RequestOptions requestOptions) { - return this.rawClient.getEntity(requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture getOrganization() { - return this.rawClient.getOrganization().thenApply(response -> response.body()); - } - - public CompletableFuture getOrganization(RequestOptions requestOptions) { - return this.rawClient.getOrganization(requestOptions).thenApply(response -> response.body()); - } - - public static AsyncSeedApiClientBuilder builder() { - return new AsyncSeedApiClientBuilder(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java deleted file mode 100644 index bc66923d39bc..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.Environment; -import com.seed.api.core.LogConfig; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import okhttp3.OkHttpClient; - -public class AsyncSeedApiClientBuilder { - private Optional timeout = Optional.empty(); - - private Optional maxRetries = Optional.empty(); - - private final Map customHeaders = new HashMap<>(); - - private Environment environment = Environment.DEFAULT; - - private OkHttpClient httpClient; - - private Optional logging = Optional.empty(); - - public AsyncSeedApiClientBuilder environment(Environment environment) { - this.environment = environment; - return this; - } - - public AsyncSeedApiClientBuilder url(String url) { - this.environment = Environment.custom(url); - return this; - } - - /** - * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. - */ - public AsyncSeedApiClientBuilder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Sets the maximum number of retries for the client. Defaults to 2 retries. - */ - public AsyncSeedApiClientBuilder maxRetries(int maxRetries) { - this.maxRetries = Optional.of(maxRetries); - return this; - } - - /** - * Sets the underlying OkHttp client - */ - public AsyncSeedApiClientBuilder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - /** - * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. - */ - public AsyncSeedApiClientBuilder logging(LogConfig logging) { - this.logging = Optional.of(logging); - return this; - } - - /** - * Add a custom header to be sent with all requests. - * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. - * - * @param name The header name - * @param value The header value - * @return This builder for method chaining - */ - public AsyncSeedApiClientBuilder addHeader(String name, String value) { - this.customHeaders.put(name, value); - return this; - } - - protected ClientOptions buildClientOptions() { - ClientOptions.Builder builder = ClientOptions.builder(); - setEnvironment(builder); - setHttpClient(builder); - setTimeouts(builder); - setRetries(builder); - setLogging(builder); - for (Map.Entry header : this.customHeaders.entrySet()) { - builder.addHeader(header.getKey(), header.getValue()); - } - setAdditional(builder); - return builder.build(); - } - - /** - * Sets the environment configuration for the client. - * Override this method to modify URLs or add environment-specific logic. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setEnvironment(ClientOptions.Builder builder) { - builder.environment(this.environment); - } - - /** - * Sets the request timeout configuration. - * Override this method to customize timeout behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setTimeouts(ClientOptions.Builder builder) { - if (this.timeout.isPresent()) { - builder.timeout(this.timeout.get()); - } - } - - /** - * Sets the retry configuration for failed requests. - * Override this method to implement custom retry strategies. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setRetries(ClientOptions.Builder builder) { - if (this.maxRetries.isPresent()) { - builder.maxRetries(this.maxRetries.get()); - } - } - - /** - * Sets the OkHttp client configuration. - * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setHttpClient(ClientOptions.Builder builder) { - if (this.httpClient != null) { - builder.httpClient(this.httpClient); - } - } - - /** - * Sets the logging configuration for the SDK. - * Override this method to customize logging behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setLogging(ClientOptions.Builder builder) { - if (this.logging.isPresent()) { - builder.logging(this.logging.get()); - } - } - - /** - * Override this method to add any additional configuration to the client. - * This method is called at the end of the configuration chain, allowing you to add - * custom headers, modify settings, or perform any other client customization. - * - * @param builder The ClientOptions.Builder to configure - * - * Example: - *
{@code
-     * @Override
-     * protected void setAdditional(ClientOptions.Builder builder) {
-     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
-     *     builder.addHeader("X-Client-Version", "1.0.0");
-     * }
-     * }
- */ - protected void setAdditional(ClientOptions.Builder builder) {} - - /** - * Override this method to add custom validation logic before the client is built. - * This method is called at the beginning of the build() method to ensure the configuration is valid. - * Throw an exception to prevent client creation if validation fails. - * - * Example: - *
{@code
-     * @Override
-     * protected void validateConfiguration() {
-     *     super.validateConfiguration(); // Run parent validations
-     *     if (tenantId == null || tenantId.isEmpty()) {
-     *         throw new IllegalStateException("tenantId is required");
-     *     }
-     * }
-     * }
- */ - protected void validateConfiguration() {} - - public AsyncSeedApiClient build() { - validateConfiguration(); - return new AsyncSeedApiClient(buildClientOptions()); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java deleted file mode 100644 index f9a763a744c4..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/RawSeedApiClient.java +++ /dev/null @@ -1,249 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.seed.api.core.ClientOptions; -import com.seed.api.core.MediaTypes; -import com.seed.api.core.ObjectMappers; -import com.seed.api.core.QueryStringMapper; -import com.seed.api.core.RequestOptions; -import com.seed.api.core.SeedApiApiException; -import com.seed.api.core.SeedApiException; -import com.seed.api.core.SeedApiHttpResponse; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; -import java.io.IOException; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; - -public class RawSeedApiClient { - protected final ClientOptions clientOptions; - - public RawSeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - } - - public SeedApiHttpResponse searchRuleTypes() { - return searchRuleTypes(SearchRuleTypesRequest.builder().build()); - } - - public SeedApiHttpResponse searchRuleTypes(RequestOptions requestOptions) { - return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); - } - - public SeedApiHttpResponse searchRuleTypes(SearchRuleTypesRequest request) { - return searchRuleTypes(request, null); - } - - public SeedApiHttpResponse searchRuleTypes( - SearchRuleTypesRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rule-types"); - if (request.getQuery().isPresent()) { - QueryStringMapper.addQueryParameter( - httpUrl, "query", request.getQuery().get(), false); - } - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request.Builder _requestBuilder = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json"); - Request okhttpRequest = _requestBuilder.build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), - response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse createRule(RuleCreateRequest request) { - return createRule(request, null); - } - - public SeedApiHttpResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rules"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - RequestBody body; - try { - body = RequestBody.create( - ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); - } catch (JsonProcessingException e) { - throw new SeedApiException("Failed to serialize request", e); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("POST", body) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse listUsers() { - return listUsers(null); - } - - public SeedApiHttpResponse listUsers(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("users"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse getEntity() { - return getEntity(null); - } - - public SeedApiHttpResponse getEntity(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("entities"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse getOrganization() { - return getOrganization(null); - } - - public SeedApiHttpResponse getOrganization(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("organizations"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java deleted file mode 100644 index 73a73c0a87a2..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClient.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.RequestOptions; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; - -public class SeedApiClient { - protected final ClientOptions clientOptions; - - private final RawSeedApiClient rawClient; - - public SeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.rawClient = new RawSeedApiClient(clientOptions); - } - - /** - * Get responses with HTTP metadata like headers - */ - public RawSeedApiClient withRawResponse() { - return this.rawClient; - } - - public RuleTypeSearchResponse searchRuleTypes() { - return this.rawClient.searchRuleTypes().body(); - } - - public RuleTypeSearchResponse searchRuleTypes(RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(requestOptions).body(); - } - - public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request) { - return this.rawClient.searchRuleTypes(request).body(); - } - - public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request, RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(request, requestOptions).body(); - } - - public RuleResponse createRule(RuleCreateRequest request) { - return this.rawClient.createRule(request).body(); - } - - public RuleResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { - return this.rawClient.createRule(request, requestOptions).body(); - } - - public UserSearchResponse listUsers() { - return this.rawClient.listUsers().body(); - } - - public UserSearchResponse listUsers(RequestOptions requestOptions) { - return this.rawClient.listUsers(requestOptions).body(); - } - - public CombinedEntity getEntity() { - return this.rawClient.getEntity().body(); - } - - public CombinedEntity getEntity(RequestOptions requestOptions) { - return this.rawClient.getEntity(requestOptions).body(); - } - - public Organization getOrganization() { - return this.rawClient.getOrganization().body(); - } - - public Organization getOrganization(RequestOptions requestOptions) { - return this.rawClient.getOrganization(requestOptions).body(); - } - - public static SeedApiClientBuilder builder() { - return new SeedApiClientBuilder(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java deleted file mode 100644 index 5f458e5b960a..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/SeedApiClientBuilder.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.Environment; -import com.seed.api.core.LogConfig; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import okhttp3.OkHttpClient; - -public class SeedApiClientBuilder { - private Optional timeout = Optional.empty(); - - private Optional maxRetries = Optional.empty(); - - private final Map customHeaders = new HashMap<>(); - - private Environment environment = Environment.DEFAULT; - - private OkHttpClient httpClient; - - private Optional logging = Optional.empty(); - - public SeedApiClientBuilder environment(Environment environment) { - this.environment = environment; - return this; - } - - public SeedApiClientBuilder url(String url) { - this.environment = Environment.custom(url); - return this; - } - - /** - * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. - */ - public SeedApiClientBuilder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Sets the maximum number of retries for the client. Defaults to 2 retries. - */ - public SeedApiClientBuilder maxRetries(int maxRetries) { - this.maxRetries = Optional.of(maxRetries); - return this; - } - - /** - * Sets the underlying OkHttp client - */ - public SeedApiClientBuilder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - /** - * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. - */ - public SeedApiClientBuilder logging(LogConfig logging) { - this.logging = Optional.of(logging); - return this; - } - - /** - * Add a custom header to be sent with all requests. - * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. - * - * @param name The header name - * @param value The header value - * @return This builder for method chaining - */ - public SeedApiClientBuilder addHeader(String name, String value) { - this.customHeaders.put(name, value); - return this; - } - - protected ClientOptions buildClientOptions() { - ClientOptions.Builder builder = ClientOptions.builder(); - setEnvironment(builder); - setHttpClient(builder); - setTimeouts(builder); - setRetries(builder); - setLogging(builder); - for (Map.Entry header : this.customHeaders.entrySet()) { - builder.addHeader(header.getKey(), header.getValue()); - } - setAdditional(builder); - return builder.build(); - } - - /** - * Sets the environment configuration for the client. - * Override this method to modify URLs or add environment-specific logic. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setEnvironment(ClientOptions.Builder builder) { - builder.environment(this.environment); - } - - /** - * Sets the request timeout configuration. - * Override this method to customize timeout behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setTimeouts(ClientOptions.Builder builder) { - if (this.timeout.isPresent()) { - builder.timeout(this.timeout.get()); - } - } - - /** - * Sets the retry configuration for failed requests. - * Override this method to implement custom retry strategies. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setRetries(ClientOptions.Builder builder) { - if (this.maxRetries.isPresent()) { - builder.maxRetries(this.maxRetries.get()); - } - } - - /** - * Sets the OkHttp client configuration. - * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setHttpClient(ClientOptions.Builder builder) { - if (this.httpClient != null) { - builder.httpClient(this.httpClient); - } - } - - /** - * Sets the logging configuration for the SDK. - * Override this method to customize logging behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setLogging(ClientOptions.Builder builder) { - if (this.logging.isPresent()) { - builder.logging(this.logging.get()); - } - } - - /** - * Override this method to add any additional configuration to the client. - * This method is called at the end of the configuration chain, allowing you to add - * custom headers, modify settings, or perform any other client customization. - * - * @param builder The ClientOptions.Builder to configure - * - * Example: - *
{@code
-     * @Override
-     * protected void setAdditional(ClientOptions.Builder builder) {
-     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
-     *     builder.addHeader("X-Client-Version", "1.0.0");
-     * }
-     * }
- */ - protected void setAdditional(ClientOptions.Builder builder) {} - - /** - * Override this method to add custom validation logic before the client is built. - * This method is called at the beginning of the build() method to ensure the configuration is valid. - * Throw an exception to prevent client creation if validation fails. - * - * Example: - *
{@code
-     * @Override
-     * protected void validateConfiguration() {
-     *     super.validateConfiguration(); // Run parent validations
-     *     if (tenantId == null || tenantId.isEmpty()) {
-     *         throw new IllegalStateException("tenantId is required");
-     *     }
-     * }
-     * }
- */ - protected void validateConfiguration() {} - - public SeedApiClient build() { - validateConfiguration(); - return new SeedApiClient(buildClientOptions()); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java deleted file mode 100644 index 185b8126621c..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ClientOptions.java +++ /dev/null @@ -1,221 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import okhttp3.OkHttpClient; - -public final class ClientOptions { - private final Environment environment; - - private final Map headers; - - private final Map> headerSuppliers; - - private final OkHttpClient httpClient; - - private final int timeout; - - private final int maxRetries; - - private final Optional logging; - - private ClientOptions( - Environment environment, - Map headers, - Map> headerSuppliers, - OkHttpClient httpClient, - int timeout, - int maxRetries, - Optional logging) { - this.environment = environment; - this.headers = new HashMap<>(); - this.headers.putAll(headers); - this.headers.putAll(new HashMap() { - { - put("User-Agent", "com.fern:allof-inline/0.0.1"); - put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.seed.fern:api-sdk"); - } - }); - this.headerSuppliers = headerSuppliers; - this.httpClient = httpClient; - this.timeout = timeout; - this.maxRetries = maxRetries; - this.logging = logging; - } - - public Environment environment() { - return this.environment; - } - - public Map headers(RequestOptions requestOptions) { - Map values = new HashMap<>(this.headers); - headerSuppliers.forEach((key, supplier) -> { - values.put(key, supplier.get()); - }); - if (requestOptions != null) { - values.putAll(requestOptions.getHeaders()); - } - return values; - } - - public int timeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.timeout; - } - return requestOptions.getTimeout().orElse(this.timeout); - } - - public OkHttpClient httpClient() { - return this.httpClient; - } - - public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.httpClient; - } - return this.httpClient - .newBuilder() - .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS) - .build(); - } - - public int maxRetries() { - return this.maxRetries; - } - - public Optional logging() { - return this.logging; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Environment environment; - - private final Map headers = new HashMap<>(); - - private final Map> headerSuppliers = new HashMap<>(); - - private int maxRetries = 2; - - private Optional timeout = Optional.empty(); - - private OkHttpClient httpClient = null; - - private Optional logging = Optional.empty(); - - public Builder environment(Environment environment) { - this.environment = environment; - return this; - } - - public Builder addHeader(String key, String value) { - this.headers.put(key, value); - return this; - } - - public Builder addHeader(String key, Supplier value) { - this.headerSuppliers.put(key, value); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(Optional timeout) { - this.timeout = timeout; - return this; - } - - /** - * Override the maximum number of retries. Defaults to 2 retries. - */ - public Builder maxRetries(int maxRetries) { - this.maxRetries = maxRetries; - return this; - } - - public Builder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - /** - * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. - */ - public Builder logging(LogConfig logging) { - this.logging = Optional.of(logging); - return this; - } - - public ClientOptions build() { - OkHttpClient.Builder httpClientBuilder = - this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); - - if (this.httpClient != null) { - timeout.ifPresent(timeout -> httpClientBuilder - .callTimeout(timeout, TimeUnit.SECONDS) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS)); - } else { - httpClientBuilder - .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS) - .addInterceptor(new RetryInterceptor(this.maxRetries)); - } - - Logger logger = Logger.from(this.logging); - httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); - - this.httpClient = httpClientBuilder.build(); - this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); - - return new ClientOptions( - environment, - headers, - headerSuppliers, - httpClient, - this.timeout.get(), - this.maxRetries, - this.logging); - } - - /** - * Create a new Builder initialized with values from an existing ClientOptions - */ - public static Builder from(ClientOptions clientOptions) { - Builder builder = new Builder(); - builder.environment = clientOptions.environment(); - builder.timeout = Optional.of(clientOptions.timeout(null)); - builder.httpClient = clientOptions.httpClient(); - builder.headers.putAll(clientOptions.headers); - builder.headerSuppliers.putAll(clientOptions.headerSuppliers); - builder.maxRetries = clientOptions.maxRetries(); - builder.logging = clientOptions.logging(); - return builder; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java deleted file mode 100644 index 0deabc0b79f7..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ConsoleLogger.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.logging.Level; - -/** - * Default logger implementation that writes to the console using {@link java.util.logging.Logger}. - * - *

Uses the "fern" logger name with a simple format of "LEVEL - message". - */ -public final class ConsoleLogger implements ILogger { - - private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger("fern"); - - static { - if (logger.getHandlers().length == 0) { - java.util.logging.ConsoleHandler handler = new java.util.logging.ConsoleHandler(); - handler.setFormatter(new java.util.logging.SimpleFormatter() { - @Override - public String format(java.util.logging.LogRecord record) { - return record.getLevel() + " - " + record.getMessage() + System.lineSeparator(); - } - }); - logger.addHandler(handler); - logger.setUseParentHandlers(false); - logger.setLevel(Level.ALL); - } - } - - @Override - public void debug(String message) { - logger.log(Level.FINE, message); - } - - @Override - public void info(String message) { - logger.log(Level.INFO, message); - } - - @Override - public void warn(String message) { - logger.log(Level.WARNING, message); - } - - @Override - public void error(String message) { - logger.log(Level.SEVERE, message); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java deleted file mode 100644 index eac7d50c71ae..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DateTimeDeserializer.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalQueries; - -/** - * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. - */ -class DateTimeDeserializer extends JsonDeserializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); - } - - /** - * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - JsonToken token = parser.currentToken(); - if (token == JsonToken.VALUE_NUMBER_INT) { - return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); - } else { - TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( - parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); - - if (temporal.query(TemporalQueries.offset()) == null) { - return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); - } else { - return OffsetDateTime.from(temporal); - } - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java deleted file mode 100644 index 2390bfbe5690..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/DoubleSerializer.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; - -/** - * Custom serializer that writes integer-valued doubles without a decimal point. - * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. - * Non-integer values like {@code 3.14} are serialized normally. - */ -class DoubleSerializer extends JsonSerializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule() - .addSerializer(Double.class, new DoubleSerializer()) - .addSerializer(double.class, new DoubleSerializer()); - } - - /** - * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { - gen.writeNumber(value.longValue()); - } else { - gen.writeNumber(value); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java deleted file mode 100644 index c612dbc31093..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Environment.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -public final class Environment { - public static final Environment DEFAULT = new Environment("https://api.example.com"); - - private final String url; - - private Environment(String url) { - this.url = url; - } - - public String getUrl() { - return this.url; - } - - public static Environment custom(String url) { - return new Environment(url); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java deleted file mode 100644 index a71e79469869..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/FileStream.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.InputStream; -import java.util.Objects; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import org.jetbrains.annotations.Nullable; - -/** - * Represents a file stream with associated metadata for file uploads. - */ -public class FileStream { - private final InputStream inputStream; - private final String fileName; - private final MediaType contentType; - - /** - * Constructs a FileStream with the given input stream and optional metadata. - * - * @param inputStream The input stream of the file content. Must not be null. - * @param fileName The name of the file, or null if unknown. - * @param contentType The MIME type of the file content, or null if unknown. - * @throws NullPointerException if inputStream is null - */ - public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { - this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); - this.fileName = fileName; - this.contentType = contentType; - } - - public FileStream(InputStream inputStream) { - this(inputStream, null, null); - } - - public InputStream getInputStream() { - return inputStream; - } - - @Nullable - public String getFileName() { - return fileName; - } - - @Nullable - public MediaType getContentType() { - return contentType; - } - - /** - * Creates a RequestBody suitable for use with OkHttp client. - * - * @return A RequestBody instance representing this file stream. - */ - public RequestBody toRequestBody() { - return new InputStreamRequestBody(contentType, inputStream); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java deleted file mode 100644 index 866d4814dd5c..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ILogger.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * Interface for custom logger implementations. - * - *

Implement this interface to provide a custom logging backend for the SDK. - * The SDK will call the appropriate method based on the log level. - * - *

Example: - *

{@code
- * public class MyCustomLogger implements ILogger {
- *     public void debug(String message) {
- *         System.out.println("[DBG] " + message);
- *     }
- *     public void info(String message) {
- *         System.out.println("[INF] " + message);
- *     }
- *     public void warn(String message) {
- *         System.out.println("[WRN] " + message);
- *     }
- *     public void error(String message) {
- *         System.out.println("[ERR] " + message);
- *     }
- * }
- * }
- */ -public interface ILogger { - void debug(String message); - - void info(String message); - - void warn(String message); - - void error(String message); -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java deleted file mode 100644 index a1e136889aaa..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/InputStreamRequestBody.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Objects; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import okio.BufferedSink; -import okio.Okio; -import okio.Source; -import org.jetbrains.annotations.Nullable; - -/** - * A custom implementation of OkHttp's RequestBody that wraps an InputStream. - * This class allows streaming of data from an InputStream directly to an HTTP request body, - * which is useful for file uploads or sending large amounts of data without loading it all into memory. - */ -public class InputStreamRequestBody extends RequestBody { - private final InputStream inputStream; - private final MediaType contentType; - - /** - * Constructs an InputStreamRequestBody with the specified content type and input stream. - * - * @param contentType the MediaType of the content, or null if not known - * @param inputStream the InputStream containing the data to be sent - * @throws NullPointerException if inputStream is null - */ - public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { - this.contentType = contentType; - this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); - } - - /** - * Returns the content type of this request body. - * - * @return the MediaType of the content, or null if not specified - */ - @Nullable - @Override - public MediaType contentType() { - return contentType; - } - - /** - * Returns the content length of this request body, if known. - * This method attempts to determine the length using the InputStream's available() method, - * which may not always accurately reflect the total length of the stream. - * - * @return the content length, or -1 if the length is unknown - * @throws IOException if an I/O error occurs - */ - @Override - public long contentLength() throws IOException { - return inputStream.available() == 0 ? -1 : inputStream.available(); - } - - /** - * Writes the content of the InputStream to the given BufferedSink. - * This method is responsible for transferring the data from the InputStream to the network request. - * - * @param sink the BufferedSink to write the content to - * @throws IOException if an I/O error occurs during writing - */ - @Override - public void writeTo(BufferedSink sink) throws IOException { - try (Source source = Okio.source(inputStream)) { - sink.writeAll(source); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java deleted file mode 100644 index dd31bc7a6545..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogConfig.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * Configuration for SDK logging. - * - *

Use the builder to configure logging behavior: - *

{@code
- * LogConfig config = LogConfig.builder()
- *     .level(LogLevel.DEBUG)
- *     .silent(false)
- *     .build();
- * }
- * - *

Or with a custom logger: - *

{@code
- * LogConfig config = LogConfig.builder()
- *     .level(LogLevel.DEBUG)
- *     .logger(new MyCustomLogger())
- *     .silent(false)
- *     .build();
- * }
- * - *

Defaults: - *

    - *
  • {@code level} — {@link LogLevel#INFO}
  • - *
  • {@code logger} — {@link ConsoleLogger} (writes to stderr via java.util.logging)
  • - *
  • {@code silent} — {@code true} (no output unless explicitly enabled)
  • - *
- */ -public final class LogConfig { - - private final LogLevel level; - private final ILogger logger; - private final boolean silent; - - private LogConfig(LogLevel level, ILogger logger, boolean silent) { - this.level = level; - this.logger = logger; - this.silent = silent; - } - - public LogLevel level() { - return level; - } - - public ILogger logger() { - return logger; - } - - public boolean silent() { - return silent; - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private LogLevel level = LogLevel.INFO; - private ILogger logger = new ConsoleLogger(); - private boolean silent = true; - - private Builder() {} - - /** - * Set the minimum log level. Only messages at this level or above will be logged. - * Defaults to {@link LogLevel#INFO}. - */ - public Builder level(LogLevel level) { - this.level = level; - return this; - } - - /** - * Set a custom logger implementation. Defaults to {@link ConsoleLogger}. - */ - public Builder logger(ILogger logger) { - this.logger = logger; - return this; - } - - /** - * Set whether logging is silent (disabled). Defaults to {@code true}. - * Set to {@code false} to enable log output. - */ - public Builder silent(boolean silent) { - this.silent = silent; - return this; - } - - public LogConfig build() { - return new LogConfig(level, logger, silent); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java deleted file mode 100644 index 2ef88f853eae..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LogLevel.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * Log levels for SDK logging configuration. - * Silent by default — no log output unless explicitly configured. - */ -public enum LogLevel { - DEBUG(1), - INFO(2), - WARN(3), - ERROR(4); - - private final int value; - - LogLevel(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - /** - * Parse a log level from a string (case-insensitive). - * - * @param level the level string (debug, info, warn, error) - * @return the corresponding LogLevel - * @throws IllegalArgumentException if the string does not match any level - */ - public static LogLevel fromString(String level) { - return LogLevel.valueOf(level.toUpperCase()); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java deleted file mode 100644 index 353f696fcf3f..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Logger.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * SDK logger that filters messages based on level and silent mode. - * - *

Silent by default — no log output unless explicitly configured. - * Create via {@link LogConfig} or directly: - *

{@code
- * Logger logger = new Logger(LogLevel.DEBUG, new ConsoleLogger(), false);
- * logger.debug("request sent");
- * }
- */ -public final class Logger { - - private static final Logger DEFAULT = new Logger(LogLevel.INFO, new ConsoleLogger(), true); - - private final LogLevel level; - private final ILogger logger; - private final boolean silent; - - public Logger(LogLevel level, ILogger logger, boolean silent) { - this.level = level; - this.logger = logger; - this.silent = silent; - } - - /** - * Returns a default silent logger (no output). - */ - public static Logger getDefault() { - return DEFAULT; - } - - /** - * Creates a Logger from a {@link LogConfig}. If config is {@code null}, returns the default silent logger. - */ - public static Logger from(LogConfig config) { - if (config == null) { - return DEFAULT; - } - return new Logger(config.level(), config.logger(), config.silent()); - } - - /** - * Creates a Logger from an {@code Optional}. If empty, returns the default silent logger. - */ - public static Logger from(java.util.Optional config) { - return config.map(Logger::from).orElse(DEFAULT); - } - - private boolean shouldLog(LogLevel messageLevel) { - return !silent && level.getValue() <= messageLevel.getValue(); - } - - public boolean isDebug() { - return shouldLog(LogLevel.DEBUG); - } - - public boolean isInfo() { - return shouldLog(LogLevel.INFO); - } - - public boolean isWarn() { - return shouldLog(LogLevel.WARN); - } - - public boolean isError() { - return shouldLog(LogLevel.ERROR); - } - - public void debug(String message) { - if (isDebug()) { - logger.debug(message); - } - } - - public void info(String message) { - if (isInfo()) { - logger.info(message); - } - } - - public void warn(String message) { - if (isWarn()) { - logger.warn(message); - } - } - - public void error(String message) { - if (isError()) { - logger.error(message); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java deleted file mode 100644 index 39a8eadeb905..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/LoggingInterceptor.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -/** - * OkHttp interceptor that logs HTTP requests and responses. - * - *

Logs request method, URL, and headers (with sensitive values redacted) at debug level. - * Logs response status at debug level, and 4xx/5xx responses at error level. - * Does nothing if the logger is silent. - */ -public final class LoggingInterceptor implements Interceptor { - - private static final Set SENSITIVE_HEADERS = new HashSet<>(Arrays.asList( - "authorization", - "www-authenticate", - "x-api-key", - "api-key", - "apikey", - "x-api-token", - "x-auth-token", - "auth-token", - "proxy-authenticate", - "proxy-authorization", - "cookie", - "set-cookie", - "x-csrf-token", - "x-xsrf-token", - "x-session-token", - "x-access-token")); - - private final Logger logger; - - public LoggingInterceptor(Logger logger) { - this.logger = logger; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - - if (logger.isDebug()) { - StringBuilder sb = new StringBuilder(); - sb.append("HTTP Request: ").append(request.method()).append(" ").append(request.url()); - sb.append(" headers={"); - boolean first = true; - for (String name : request.headers().names()) { - if (!first) { - sb.append(", "); - } - sb.append(name).append("="); - if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { - sb.append("[REDACTED]"); - } else { - sb.append(request.header(name)); - } - first = false; - } - sb.append("}"); - sb.append(" has_body=").append(request.body() != null); - logger.debug(sb.toString()); - } - - Response response = chain.proceed(request); - - if (logger.isDebug()) { - StringBuilder sb = new StringBuilder(); - sb.append("HTTP Response: status=").append(response.code()); - sb.append(" url=").append(response.request().url()); - sb.append(" headers={"); - boolean first = true; - for (String name : response.headers().names()) { - if (!first) { - sb.append(", "); - } - sb.append(name).append("="); - if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { - sb.append("[REDACTED]"); - } else { - sb.append(response.header(name)); - } - first = false; - } - sb.append("}"); - logger.debug(sb.toString()); - } - - if (response.code() >= 400 && logger.isError()) { - logger.error("HTTP Error: status=" + response.code() + " url=" - + response.request().url()); - } - - return response; - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java deleted file mode 100644 index 4a8d1cf301d4..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/MediaTypes.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import okhttp3.MediaType; - -public final class MediaTypes { - - public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); - - private MediaTypes() {} -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java deleted file mode 100644 index ca50b2d8d50a..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Nullable.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.Optional; -import java.util.function.Function; - -public final class Nullable { - - private final Either, Null> value; - - private Nullable() { - this.value = Either.left(Optional.empty()); - } - - private Nullable(T value) { - if (value == null) { - this.value = Either.right(Null.INSTANCE); - } else { - this.value = Either.left(Optional.of(value)); - } - } - - public static Nullable ofNull() { - return new Nullable<>(null); - } - - public static Nullable of(T value) { - return new Nullable<>(value); - } - - public static Nullable empty() { - return new Nullable<>(); - } - - public static Nullable ofOptional(Optional value) { - if (value.isPresent()) { - return of(value.get()); - } else { - return empty(); - } - } - - public boolean isNull() { - return this.value.isRight(); - } - - public boolean isEmpty() { - return this.value.isLeft() && !this.value.getLeft().isPresent(); - } - - public T get() { - if (this.isNull()) { - return null; - } - - return this.value.getLeft().get(); - } - - public Nullable map(Function mapper) { - if (this.isNull()) { - return Nullable.ofNull(); - } - - return Nullable.ofOptional(this.value.getLeft().map(mapper)); - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Nullable)) { - return false; - } - - if (((Nullable) other).isNull() && this.isNull()) { - return true; - } - - return this.value.getLeft().equals(((Nullable) other).value.getLeft()); - } - - private static final class Either { - private L left = null; - private R right = null; - - private Either(L left, R right) { - if (left != null && right != null) { - throw new IllegalArgumentException("Left and right argument cannot both be non-null."); - } - - if (left == null && right == null) { - throw new IllegalArgumentException("Left and right argument cannot both be null."); - } - - if (left != null) { - this.left = left; - } - - if (right != null) { - this.right = right; - } - } - - public static Either left(L left) { - return new Either<>(left, null); - } - - public static Either right(R right) { - return new Either<>(null, right); - } - - public boolean isLeft() { - return this.left != null; - } - - public boolean isRight() { - return this.right != null; - } - - public L getLeft() { - if (!this.isLeft()) { - throw new IllegalArgumentException("Cannot get left from right Either."); - } - return this.left; - } - - public R getRight() { - if (!this.isRight()) { - throw new IllegalArgumentException("Cannot get right from left Either."); - } - return this.right; - } - } - - private static final class Null { - private static final Null INSTANCE = new Null(); - - private Null() {} - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java deleted file mode 100644 index dea5d181d48c..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/NullableNonemptyFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.Optional; - -public final class NullableNonemptyFilter { - @Override - public boolean equals(Object o) { - boolean isOptionalEmpty = isOptionalEmpty(o); - - return isOptionalEmpty; - } - - private boolean isOptionalEmpty(Object o) { - if (o instanceof Optional) { - return !((Optional) o).isPresent(); - } - return false; - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java deleted file mode 100644 index 2e443b07d910..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ObjectMappers.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.io.IOException; - -public final class ObjectMappers { - public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() - .addModule(new Jdk8Module()) - .addModule(new JavaTimeModule()) - .addModule(DateTimeDeserializer.getModule()) - .addModule(DoubleSerializer.getModule()) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .build(); - - private ObjectMappers() {} - - public static String stringify(Object o) { - try { - return JSON_MAPPER - .setSerializationInclusion(JsonInclude.Include.ALWAYS) - .writerWithDefaultPrettyPrinter() - .writeValueAsString(o); - } catch (IOException e) { - return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); - } - } - - public static Object parseErrorBody(String responseBodyString) { - try { - return JSON_MAPPER.readValue(responseBodyString, Object.class); - } catch (JsonProcessingException ignored) { - return responseBodyString; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java deleted file mode 100644 index 3e364e6f3d5f..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/QueryStringMapper.java +++ /dev/null @@ -1,142 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import okhttp3.HttpUrl; -import okhttp3.MultipartBody; - -public class QueryStringMapper { - - private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; - - public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { - JsonNode valueNode = MAPPER.valueToTree(value); - - List> flat; - if (valueNode.isObject()) { - flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); - } else if (valueNode.isArray()) { - flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); - } else { - if (valueNode.isTextual()) { - httpUrl.addQueryParameter(key, valueNode.textValue()); - } else { - httpUrl.addQueryParameter(key, valueNode.toString()); - } - return; - } - - for (Map.Entry field : flat) { - if (field.getValue().isTextual()) { - httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); - } else { - httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); - } - } - } - - public static void addFormDataPart( - MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { - JsonNode valueNode = MAPPER.valueToTree(value); - - List> flat; - if (valueNode.isObject()) { - flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); - } else if (valueNode.isArray()) { - flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); - } else { - if (valueNode.isTextual()) { - multipartBody.addFormDataPart(key, valueNode.textValue()); - } else { - multipartBody.addFormDataPart(key, valueNode.toString()); - } - return; - } - - for (Map.Entry field : flat) { - if (field.getValue().isTextual()) { - multipartBody.addFormDataPart( - key + field.getKey(), field.getValue().textValue()); - } else { - multipartBody.addFormDataPart( - key + field.getKey(), field.getValue().toString()); - } - } - } - - public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { - List> flat = new ArrayList<>(); - - Iterator> fields = object.fields(); - while (fields.hasNext()) { - Map.Entry field = fields.next(); - - String key = "[" + field.getKey() + "]"; - - if (field.getValue().isObject()) { - List> flatField = - flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); - addAll(flat, flatField, key); - } else if (field.getValue().isArray()) { - List> flatField = - flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); - addAll(flat, flatField, ""); - } else { - flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); - } - } - - return flat; - } - - private static List> flattenArray( - ArrayNode array, String key, boolean arraysAsRepeats) { - List> flat = new ArrayList<>(); - - Iterator elements = array.elements(); - - int index = 0; - while (elements.hasNext()) { - JsonNode element = elements.next(); - - String indexKey = key + "[" + index + "]"; - - if (arraysAsRepeats) { - indexKey = key; - } - - if (element.isObject()) { - List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); - addAll(flat, flatField, indexKey); - } else if (element.isArray()) { - List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); - addAll(flat, flatField, indexKey); - } else { - flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); - } - - index++; - } - - return flat; - } - - private static void addAll( - List> target, List> source, String prefix) { - for (Map.Entry entry : source) { - Map.Entry entryToAdd = - new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); - target.add(entryToAdd); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java deleted file mode 100644 index cd95a27201b2..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RequestOptions.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -public final class RequestOptions { - private final Optional timeout; - - private final TimeUnit timeoutTimeUnit; - - private final Map headers; - - private final Map> headerSuppliers; - - private final Map queryParameters; - - private final Map> queryParameterSuppliers; - - private RequestOptions( - Optional timeout, - TimeUnit timeoutTimeUnit, - Map headers, - Map> headerSuppliers, - Map queryParameters, - Map> queryParameterSuppliers) { - this.timeout = timeout; - this.timeoutTimeUnit = timeoutTimeUnit; - this.headers = headers; - this.headerSuppliers = headerSuppliers; - this.queryParameters = queryParameters; - this.queryParameterSuppliers = queryParameterSuppliers; - } - - public Optional getTimeout() { - return timeout; - } - - public TimeUnit getTimeoutTimeUnit() { - return timeoutTimeUnit; - } - - public Map getHeaders() { - Map headers = new HashMap<>(); - headers.putAll(this.headers); - this.headerSuppliers.forEach((key, supplier) -> { - headers.put(key, supplier.get()); - }); - return headers; - } - - public Map getQueryParameters() { - Map queryParameters = new HashMap<>(this.queryParameters); - this.queryParameterSuppliers.forEach((key, supplier) -> { - queryParameters.put(key, supplier.get()); - }); - return queryParameters; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Optional timeout = Optional.empty(); - - private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; - - private final Map headers = new HashMap<>(); - - private final Map> headerSuppliers = new HashMap<>(); - - private final Map queryParameters = new HashMap<>(); - - private final Map> queryParameterSuppliers = new HashMap<>(); - - public Builder timeout(Integer timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { - this.timeout = Optional.of(timeout); - this.timeoutTimeUnit = timeoutTimeUnit; - return this; - } - - public Builder addHeader(String key, String value) { - this.headers.put(key, value); - return this; - } - - public Builder addHeader(String key, Supplier value) { - this.headerSuppliers.put(key, value); - return this; - } - - public Builder addQueryParameter(String key, String value) { - this.queryParameters.put(key, value); - return this; - } - - public Builder addQueryParameter(String key, Supplier value) { - this.queryParameterSuppliers.put(key, value); - return this; - } - - public RequestOptions build() { - return new RequestOptions( - timeout, timeoutTimeUnit, headers, headerSuppliers, queryParameters, queryParameterSuppliers); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java deleted file mode 100644 index db05d538255d..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyInputStream.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.FilterInputStream; -import java.io.IOException; -import okhttp3.Response; - -/** - * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the - * OkHttp Response object is properly closed when the stream is closed. - * - * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. - * It retrieves the InputStream from the Response and overrides the close method to close - * both the InputStream and the Response object, ensuring proper resource management and preventing - * premature closure of the underlying HTTP connection. - */ -public class ResponseBodyInputStream extends FilterInputStream { - private final Response response; - - /** - * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp - * Response object. - * - * @param response the OkHttp Response object from which the InputStream is retrieved - * @throws IOException if an I/O error occurs while retrieving the InputStream - */ - public ResponseBodyInputStream(Response response) throws IOException { - super(response.body().byteStream()); - this.response = response; - } - - /** - * Closes the InputStream and the associated OkHttp Response object. This ensures that the - * underlying HTTP connection is properly closed after the stream is no longer needed. - * - * @throws IOException if an I/O error occurs - */ - @Override - public void close() throws IOException { - super.close(); - response.close(); // Ensure the response is closed when the stream is closed - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java deleted file mode 100644 index 97fcf7a0efbd..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/ResponseBodyReader.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.FilterReader; -import java.io.IOException; -import okhttp3.Response; - -/** - * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the - * OkHttp Response object is properly closed when the reader is closed. - * - * This class extends FilterReader and takes an OkHttp Response object as a parameter. - * It retrieves the Reader from the Response and overrides the close method to close - * both the Reader and the Response object, ensuring proper resource management and preventing - * premature closure of the underlying HTTP connection. - */ -public class ResponseBodyReader extends FilterReader { - private final Response response; - - /** - * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. - * - * @param response the OkHttp Response object from which the Reader is retrieved - * @throws IOException if an I/O error occurs while retrieving the Reader - */ - public ResponseBodyReader(Response response) throws IOException { - super(response.body().charStream()); - this.response = response; - } - - /** - * Closes the Reader and the associated OkHttp Response object. This ensures that the - * underlying HTTP connection is properly closed after the reader is no longer needed. - * - * @throws IOException if an I/O error occurs - */ - @Override - public void close() throws IOException { - super.close(); - response.close(); // Ensure the response is closed when the reader is closed - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java deleted file mode 100644 index 0d331c152477..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/RetryInterceptor.java +++ /dev/null @@ -1,181 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.IOException; -import java.time.Duration; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.Optional; -import java.util.Random; -import okhttp3.Interceptor; -import okhttp3.Response; - -public class RetryInterceptor implements Interceptor { - - private static final Duration INITIAL_RETRY_DELAY = Duration.ofMillis(1000); - private static final Duration MAX_RETRY_DELAY = Duration.ofMillis(60000); - private static final double JITTER_FACTOR = 0.2; - - private final int maxRetries; - private final Random random = new Random(); - - public RetryInterceptor(int maxRetries) { - this.maxRetries = maxRetries; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Response response = chain.proceed(chain.request()); - - if (shouldRetry(response.code())) { - return retryChain(response, chain); - } - - return response; - } - - private Response retryChain(Response response, Chain chain) throws IOException { - ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries); - Optional nextBackoff = backoff.nextBackoff(response); - while (nextBackoff.isPresent()) { - try { - Thread.sleep(nextBackoff.get().toMillis()); - } catch (InterruptedException e) { - throw new IOException("Interrupted while trying request", e); - } - response.close(); - response = chain.proceed(chain.request()); - if (shouldRetry(response.code())) { - nextBackoff = backoff.nextBackoff(response); - } else { - return response; - } - } - - return response; - } - - /** - * Calculates the retry delay from response headers, with fallback to exponential backoff. - * Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff - */ - private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) { - // Check for Retry-After header first (RFC 7231), with no jitter - String retryAfter = response.header("Retry-After"); - if (retryAfter != null) { - // Parse as number of seconds... - Optional secondsDelay = tryParseLong(retryAfter) - .map(seconds -> seconds * 1000) - .filter(delayMs -> delayMs > 0) - .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) - .map(Duration::ofMillis); - if (secondsDelay.isPresent()) { - return secondsDelay.get(); - } - - // ...or as an HTTP date; both are valid - Optional dateDelay = tryParseHttpDate(retryAfter) - .map(resetTime -> resetTime.toInstant().toEpochMilli() - System.currentTimeMillis()) - .filter(delayMs -> delayMs > 0) - .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) - .map(Duration::ofMillis); - if (dateDelay.isPresent()) { - return dateDelay.get(); - } - } - - // Then check for industry-standard X-RateLimit-Reset header, with positive jitter - String rateLimitReset = response.header("X-RateLimit-Reset"); - if (rateLimitReset != null) { - // Assume Unix timestamp in epoch seconds - Optional rateLimitDelay = tryParseLong(rateLimitReset) - .map(resetTimeSeconds -> (resetTimeSeconds * 1000) - System.currentTimeMillis()) - .filter(delayMs -> delayMs > 0) - .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) - .map(this::addPositiveJitter) - .map(Duration::ofMillis); - if (rateLimitDelay.isPresent()) { - return rateLimitDelay.get(); - } - } - - // Fall back to exponential backoff, with symmetric jitter - long baseDelay = INITIAL_RETRY_DELAY.toMillis() * (1L << retryAttempt); // 2^retryAttempt - long cappedDelay = Math.min(baseDelay, MAX_RETRY_DELAY.toMillis()); - return Duration.ofMillis(addSymmetricJitter(cappedDelay)); - } - - /** - * Attempts to parse a string as a long, returning empty Optional on failure. - */ - private Optional tryParseLong(String value) { - if (value == null) { - return Optional.empty(); - } - try { - return Optional.of(Long.parseLong(value)); - } catch (NumberFormatException e) { - return Optional.empty(); - } - } - - /** - * Attempts to parse a string as an HTTP date (RFC 1123), returning empty Optional on failure. - */ - private Optional tryParseHttpDate(String value) { - if (value == null) { - return Optional.empty(); - } - try { - return Optional.of(ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME)); - } catch (DateTimeParseException e) { - return Optional.empty(); - } - } - - /** - * Adds positive jitter (100-120% of original value) to prevent thundering herd. - * Used for X-RateLimit-Reset header delays. - */ - private long addPositiveJitter(long delayMs) { - double jitterMultiplier = 1.0 + (random.nextDouble() * JITTER_FACTOR); - return (long) (delayMs * jitterMultiplier); - } - - /** - * Adds symmetric jitter (90-110% of original value) to prevent thundering herd. - * Used for exponential backoff delays. - */ - private long addSymmetricJitter(long delayMs) { - double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * JITTER_FACTOR); - return (long) (delayMs * jitterMultiplier); - } - - private static boolean shouldRetry(int statusCode) { - return statusCode == 408 || statusCode == 429 || statusCode >= 500; - } - - private final class ExponentialBackoff { - - private final int maxNumRetries; - - private int retryNumber = 0; - - ExponentialBackoff(int maxNumRetries) { - this.maxNumRetries = maxNumRetries; - } - - public Optional nextBackoff(Response response) { - if (retryNumber >= maxNumRetries) { - return Optional.empty(); - } - - Duration delay = getRetryDelayFromHeaders(response, retryNumber); - retryNumber += 1; - return Optional.of(delay); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java deleted file mode 100644 index 268edd8c55be..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import java.io.IOException; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; - -/** - * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. - * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. - */ -public class Rfc2822DateTimeDeserializer extends JsonDeserializer { - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - String raw = parser.getValueAsString(); - return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java deleted file mode 100644 index ab4985c15f96..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiApiException.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import okhttp3.Response; - -/** - * This exception type will be thrown for any non-2XX API responses. - */ -public class SeedApiApiException extends SeedApiException { - /** - * The error code of the response that triggered the exception. - */ - private final int statusCode; - - /** - * The body of the response that triggered the exception. - */ - private final Object body; - - private final Map> headers; - - public SeedApiApiException(String message, int statusCode, Object body) { - super(message); - this.statusCode = statusCode; - this.body = body; - this.headers = new HashMap<>(); - } - - public SeedApiApiException(String message, int statusCode, Object body, Response rawResponse) { - super(message); - this.statusCode = statusCode; - this.body = body; - this.headers = new HashMap<>(); - rawResponse.headers().forEach(header -> { - String key = header.component1(); - String value = header.component2(); - this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); - }); - } - - /** - * @return the statusCode - */ - public int statusCode() { - return this.statusCode; - } - - /** - * @return the body - */ - public Object body() { - return this.body; - } - - /** - * @return the headers - */ - public Map> headers() { - return this.headers; - } - - @Override - public String toString() { - return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " - + ObjectMappers.stringify(body) + "}"; - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java deleted file mode 100644 index cf8236066674..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiException.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * This class serves as the base exception for all errors in the SDK. - */ -public class SeedApiException extends RuntimeException { - public SeedApiException(String message) { - super(message); - } - - public SeedApiException(String message, Exception e) { - super(message, e); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java deleted file mode 100644 index 814561a79e08..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SeedApiHttpResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import okhttp3.Response; - -public final class SeedApiHttpResponse { - - private final T body; - - private final Map> headers; - - public SeedApiHttpResponse(T body, Response rawResponse) { - this.body = body; - - Map> headers = new HashMap<>(); - rawResponse.headers().forEach(header -> { - String key = header.component1(); - String value = header.component2(); - headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); - }); - this.headers = headers; - } - - public T body() { - return this.body; - } - - public Map> headers() { - return headers; - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java deleted file mode 100644 index 9775f2110865..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEvent.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Objects; -import java.util.Optional; - -/** - * Represents a Server-Sent Event with all standard fields. - * Used for event-level discrimination where the discriminator is at the SSE envelope level. - * - * @param The type of the data field - */ -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonIgnoreProperties(ignoreUnknown = true) -public final class SseEvent { - private final String event; - private final T data; - private final String id; - private final Long retry; - - private SseEvent(String event, T data, String id, Long retry) { - this.event = event; - this.data = data; - this.id = id; - this.retry = retry; - } - - @JsonProperty("event") - public Optional getEvent() { - return Optional.ofNullable(event); - } - - @JsonProperty("data") - public T getData() { - return data; - } - - @JsonProperty("id") - public Optional getId() { - return Optional.ofNullable(id); - } - - @JsonProperty("retry") - public Optional getRetry() { - return Optional.ofNullable(retry); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SseEvent sseEvent = (SseEvent) o; - return Objects.equals(event, sseEvent.event) - && Objects.equals(data, sseEvent.data) - && Objects.equals(id, sseEvent.id) - && Objects.equals(retry, sseEvent.retry); - } - - @Override - public int hashCode() { - return Objects.hash(event, data, id, retry); - } - - @Override - public String toString() { - return "SseEvent{" + "event='" - + event + '\'' + ", data=" - + data + ", id='" - + id + '\'' + ", retry=" - + retry + '}'; - } - - public static Builder builder() { - return new Builder<>(); - } - - public static final class Builder { - private String event; - private T data; - private String id; - private Long retry; - - private Builder() {} - - public Builder event(String event) { - this.event = event; - return this; - } - - public Builder data(T data) { - this.data = data; - return this; - } - - public Builder id(String id) { - this.id = id; - return this; - } - - public Builder retry(Long retry) { - this.retry = retry; - return this; - } - - public SseEvent build() { - return new SseEvent<>(event, data, id, retry); - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java deleted file mode 100644 index b8a888d0dda0..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/SseEventParser.java +++ /dev/null @@ -1,228 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.core.type.TypeReference; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -/** - * Utility class for parsing Server-Sent Events with support for discriminated unions. - *

- * Handles two discrimination patterns: - *

    - *
  1. Data-level discrimination: The discriminator (e.g., 'type') is inside the JSON data payload. - * Jackson's polymorphic deserialization handles this automatically.
  2. - *
  3. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE envelope level. - * This requires constructing the full SSE envelope for Jackson to process.
  4. - *
- */ -public final class SseEventParser { - - private static final Set SSE_ENVELOPE_FIELDS = new HashSet<>(Arrays.asList("event", "data", "id", "retry")); - - private SseEventParser() { - // Utility class - } - - /** - * Parse an SSE event using event-level discrimination. - *

- * Constructs the full SSE envelope object with event, data, id, and retry fields, - * then deserializes it to the target union type. - * - * @param eventType The SSE event type (from event: field) - * @param data The SSE data content (from data: field) - * @param id The SSE event ID (from id: field), may be null - * @param retry The SSE retry value (from retry: field), may be null - * @param unionClass The target union class - * @param discriminatorProperty The property name used for discrimination (e.g., "event") - * @param The target type - * @return The deserialized object - */ - public static T parseEventLevelUnion( - String eventType, String data, String id, Long retry, Class unionClass, String discriminatorProperty) { - try { - // Determine if data should be parsed as JSON based on the variant's expected type - Object parsedData = parseDataForVariant(eventType, data, unionClass, discriminatorProperty); - - // Construct the SSE envelope object - Map envelope = new HashMap<>(); - envelope.put(discriminatorProperty, eventType); - envelope.put("data", parsedData); - if (id != null) { - envelope.put("id", id); - } - if (retry != null) { - envelope.put("retry", retry); - } - - // Serialize to JSON and deserialize to target type - String envelopeJson = ObjectMappers.JSON_MAPPER.writeValueAsString(envelope); - return ObjectMappers.JSON_MAPPER.readValue(envelopeJson, unionClass); - } catch (Exception e) { - throw new RuntimeException("Failed to parse SSE event with event-level discrimination", e); - } - } - - /** - * Parse an SSE event using data-level discrimination. - *

- * Simply parses the data field as JSON and deserializes it to the target type. - * Jackson's polymorphic deserialization handles the discrimination automatically. - * - * @param data The SSE data content (from data: field) - * @param valueType The target type - * @param The target type - * @return The deserialized object - */ - public static T parseDataLevelUnion(String data, Class valueType) { - try { - return ObjectMappers.JSON_MAPPER.readValue(data, valueType); - } catch (Exception e) { - throw new RuntimeException("Failed to parse SSE data with data-level discrimination", e); - } - } - - /** - * Determines if the given discriminator property indicates event-level discrimination. - * Event-level discrimination occurs when the discriminator is an SSE envelope field. - * - * @param discriminatorProperty The discriminator property name - * @return true if event-level discrimination, false otherwise - */ - public static boolean isEventLevelDiscrimination(String discriminatorProperty) { - return SSE_ENVELOPE_FIELDS.contains(discriminatorProperty); - } - - /** - * Attempts to find the discriminator property from the union class's Jackson annotations. - * - * @param unionClass The union class to inspect - * @return The discriminator property name, or empty if not found - */ - public static Optional findDiscriminatorProperty(Class unionClass) { - try { - // Look for JsonTypeInfo on the class itself - JsonTypeInfo typeInfo = unionClass.getAnnotation(JsonTypeInfo.class); - if (typeInfo != null && !typeInfo.property().isEmpty()) { - return Optional.of(typeInfo.property()); - } - - // Look for inner Value interface with JsonTypeInfo - for (Class innerClass : unionClass.getDeclaredClasses()) { - typeInfo = innerClass.getAnnotation(JsonTypeInfo.class); - if (typeInfo != null && !typeInfo.property().isEmpty()) { - return Optional.of(typeInfo.property()); - } - } - } catch (Exception e) { - // Ignore reflection errors - } - return Optional.empty(); - } - - /** - * Parse the data field based on what the matching variant expects. - * If the variant expects a String for its data field, returns the raw string. - * Otherwise, parses the data as JSON. - */ - private static Object parseDataForVariant( - String eventType, String data, Class unionClass, String discriminatorProperty) { - if (data == null || data.isEmpty()) { - return data; - } - - try { - // Try to find the variant class that matches this event type - Class variantClass = findVariantClass(unionClass, eventType, discriminatorProperty); - if (variantClass != null) { - // Check if the variant expects a String for the data field - Field dataField = findField(variantClass, "data"); - if (dataField != null && String.class.equals(dataField.getType())) { - // Variant expects String - return raw data - return data; - } - } - - // Try to parse as JSON - return ObjectMappers.JSON_MAPPER.readValue(data, new TypeReference>() {}); - } catch (Exception e) { - // If JSON parsing fails, return as string - return data; - } - } - - /** - * Find the variant class that matches the given discriminator value. - */ - private static Class findVariantClass( - Class unionClass, String discriminatorValue, String discriminatorProperty) { - try { - // Look for JsonSubTypes annotation - JsonSubTypes subTypes = findJsonSubTypes(unionClass); - if (subTypes == null) { - return null; - } - - for (JsonSubTypes.Type subType : subTypes.value()) { - JsonTypeName typeName = subType.value().getAnnotation(JsonTypeName.class); - if (typeName != null && typeName.value().equals(discriminatorValue)) { - return subType.value(); - } - // Also check the name attribute of @JsonSubTypes.Type - if (subType.name().equals(discriminatorValue)) { - return subType.value(); - } - } - } catch (Exception e) { - // Ignore reflection errors - } - return null; - } - - /** - * Find JsonSubTypes annotation on the class or its inner classes. - */ - private static JsonSubTypes findJsonSubTypes(Class unionClass) { - // Check the class itself - JsonSubTypes subTypes = unionClass.getAnnotation(JsonSubTypes.class); - if (subTypes != null) { - return subTypes; - } - - // Check inner classes (for Fern-style unions with inner Value interface) - for (Class innerClass : unionClass.getDeclaredClasses()) { - subTypes = innerClass.getAnnotation(JsonSubTypes.class); - if (subTypes != null) { - return subTypes; - } - } - return null; - } - - /** - * Find a field by name in a class, including private fields. - */ - private static Field findField(Class clazz, String fieldName) { - try { - return clazz.getDeclaredField(fieldName); - } catch (NoSuchFieldException e) { - // Check superclass - Class superClass = clazz.getSuperclass(); - if (superClass != null && superClass != Object.class) { - return findField(superClass, fieldName); - } - return null; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java deleted file mode 100644 index b4350ef9a699..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Stream.java +++ /dev/null @@ -1,513 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.Closeable; -import java.io.IOException; -import java.io.Reader; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Scanner; - -/** - * The {@code Stream} class implements {@link Iterable} to provide a simple mechanism for reading and parsing - * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. - *

- * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a - * {@code Scanner} to block during iteration if the next object is not available. - * Iterable stream for parsing JSON and Server-Sent Events (SSE) data. - * Supports both newline-delimited JSON and SSE with optional stream termination. - * - * @param The type of objects in the stream. - */ -public final class Stream implements Iterable, Closeable { - - private static final String NEWLINE = "\n"; - private static final String DATA_PREFIX = "data:"; - - public enum StreamType { - JSON, - SSE, - SSE_EVENT_DISCRIMINATED - } - - private final Class valueType; - private final Scanner scanner; - private final StreamType streamType; - private final String messageTerminator; - private final String streamTerminator; - private final Reader sseReader; - private final String discriminatorProperty; - private boolean isClosed = false; - - /** - * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. - * - * @param valueType The class of the objects in the stream. - * @param reader The reader that provides the streamed data. - * @param delimiter The delimiter used to separate elements in the stream. - */ - public Stream(Class valueType, Reader reader, String delimiter) { - this.valueType = valueType; - this.scanner = new Scanner(reader).useDelimiter(delimiter); - this.streamType = StreamType.JSON; - this.messageTerminator = delimiter; - this.streamTerminator = null; - this.sseReader = null; - this.discriminatorProperty = null; - } - - private Stream(Class valueType, StreamType type, Reader reader, String terminator) { - this(valueType, type, reader, terminator, null); - } - - private Stream( - Class valueType, StreamType type, Reader reader, String terminator, String discriminatorProperty) { - this.valueType = valueType; - this.streamType = type; - this.discriminatorProperty = discriminatorProperty; - if (type == StreamType.JSON) { - this.scanner = new Scanner(reader).useDelimiter(terminator); - this.messageTerminator = terminator; - this.streamTerminator = null; - this.sseReader = null; - } else { - this.scanner = null; - this.messageTerminator = NEWLINE; - this.streamTerminator = terminator; - this.sseReader = reader; - } - } - - public static Stream fromJson(Class valueType, Reader reader, String delimiter) { - return new Stream<>(valueType, reader, delimiter); - } - - public static Stream fromJson(Class valueType, Reader reader) { - return new Stream<>(valueType, reader, NEWLINE); - } - - public static Stream fromSse(Class valueType, Reader sseReader) { - return new Stream<>(valueType, StreamType.SSE, sseReader, null); - } - - public static Stream fromSse(Class valueType, Reader sseReader, String streamTerminator) { - return new Stream<>(valueType, StreamType.SSE, sseReader, streamTerminator); - } - - /** - * Creates a stream from SSE data with event-level discrimination support. - * Use this when the SSE payload is a discriminated union where the discriminator - * is an SSE envelope field (e.g., 'event'). - * - * @param valueType The class of the objects in the stream. - * @param sseReader The reader that provides the SSE data. - * @param discriminatorProperty The property name used for discrimination (e.g., "event"). - * @param The type of objects in the stream. - * @return A new Stream instance configured for SSE with event-level discrimination. - */ - public static Stream fromSseWithEventDiscrimination( - Class valueType, Reader sseReader, String discriminatorProperty) { - return new Stream<>(valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, null, discriminatorProperty); - } - - /** - * Creates a stream from SSE data with event-level discrimination support and a stream terminator. - * - * @param valueType The class of the objects in the stream. - * @param sseReader The reader that provides the SSE data. - * @param discriminatorProperty The property name used for discrimination (e.g., "event"). - * @param streamTerminator The terminator string that signals end of stream (e.g., "[DONE]"). - * @param The type of objects in the stream. - * @return A new Stream instance configured for SSE with event-level discrimination. - */ - public static Stream fromSseWithEventDiscrimination( - Class valueType, Reader sseReader, String discriminatorProperty, String streamTerminator) { - return new Stream<>( - valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, streamTerminator, discriminatorProperty); - } - - @Override - public void close() throws IOException { - if (!isClosed) { - isClosed = true; - if (scanner != null) { - scanner.close(); - } - if (sseReader != null) { - sseReader.close(); - } - } - } - - private boolean isStreamClosed() { - return isClosed; - } - - /** - * Returns an iterator over the elements in this stream that blocks during iteration when the next object is - * not yet available. - * - * @return An iterator that can be used to traverse the elements in the stream. - */ - @Override - public Iterator iterator() { - switch (streamType) { - case SSE: - return new SSEIterator(); - case SSE_EVENT_DISCRIMINATED: - return new SSEEventDiscriminatedIterator(); - case JSON: - default: - return new JsonIterator(); - } - } - - private final class JsonIterator implements Iterator { - - /** - * Returns {@code true} if there are more elements in the stream. - *

- * Will block and wait for input if the stream has not ended and the next object is not yet available. - * - * @return {@code true} if there are more elements, {@code false} otherwise. - */ - @Override - public boolean hasNext() { - if (isStreamClosed()) { - return false; - } - return scanner.hasNext(); - } - - /** - * Returns the next element in the stream. - *

- * Will block and wait for input if the stream has not ended and the next object is not yet available. - * - * @return The next element in the stream. - * @throws NoSuchElementException If there are no more elements in the stream. - */ - @Override - public T next() { - if (isStreamClosed()) { - throw new NoSuchElementException("Stream is closed"); - } - - if (!scanner.hasNext()) { - throw new NoSuchElementException(); - } else { - try { - T parsedResponse = - ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); - return parsedResponse; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - private final class SSEIterator implements Iterator { - private Scanner sseScanner; - private T nextItem; - private boolean hasNextItem = false; - private boolean endOfStream = false; - private StringBuilder eventDataBuffer = new StringBuilder(); - private String currentEventType = null; - - private SSEIterator() { - if (sseReader != null && !isStreamClosed()) { - this.sseScanner = new Scanner(sseReader); - } else { - this.endOfStream = true; - } - } - - @Override - public boolean hasNext() { - if (isStreamClosed() || endOfStream) { - return false; - } - - if (hasNextItem) { - return true; - } - - return readNextMessage(); - } - - @Override - public T next() { - if (!hasNext()) { - throw new NoSuchElementException("No more elements in stream"); - } - - T result = nextItem; - nextItem = null; - hasNextItem = false; - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - private boolean readNextMessage() { - if (sseScanner == null || isStreamClosed()) { - endOfStream = true; - return false; - } - - try { - while (sseScanner.hasNextLine()) { - String line = sseScanner.nextLine(); - - if (line.trim().isEmpty()) { - if (eventDataBuffer.length() > 0) { - try { - nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); - hasNextItem = true; - eventDataBuffer.setLength(0); - currentEventType = null; - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); - eventDataBuffer.setLength(0); - currentEventType = null; - continue; - } - } - continue; - } - - if (line.startsWith(DATA_PREFIX)) { - String dataContent = line.substring(DATA_PREFIX.length()); - if (dataContent.startsWith(" ")) { - dataContent = dataContent.substring(1); - } - - if (eventDataBuffer.length() == 0 - && streamTerminator != null - && dataContent.trim().equals(streamTerminator)) { - endOfStream = true; - return false; - } - - if (eventDataBuffer.length() > 0) { - eventDataBuffer.append('\n'); - } - eventDataBuffer.append(dataContent); - } else if (line.startsWith("event:")) { - String eventValue = line.length() > 6 ? line.substring(6) : ""; - if (eventValue.startsWith(" ")) { - eventValue = eventValue.substring(1); - } - currentEventType = eventValue; - } else if (line.startsWith("id:")) { - // Event ID field (ignored) - } else if (line.startsWith("retry:")) { - // Retry field (ignored) - } else if (line.startsWith(":")) { - // Comment line (ignored) - } - } - - if (eventDataBuffer.length() > 0) { - try { - nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); - hasNextItem = true; - eventDataBuffer.setLength(0); - currentEventType = null; - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); - eventDataBuffer.setLength(0); - currentEventType = null; - } - } - - endOfStream = true; - return false; - - } catch (Exception e) { - System.err.println("Failed to parse SSE stream: " + e.getMessage()); - endOfStream = true; - return false; - } - } - } - - /** - * Iterator for SSE streams with event-level discrimination. - * Uses SseEventParser to construct the full SSE envelope for Jackson deserialization. - */ - private final class SSEEventDiscriminatedIterator implements Iterator { - private Scanner sseScanner; - private T nextItem; - private boolean hasNextItem = false; - private boolean endOfStream = false; - private StringBuilder eventDataBuffer = new StringBuilder(); - private String currentEventType = null; - private String currentEventId = null; - private Long currentRetry = null; - - private SSEEventDiscriminatedIterator() { - if (sseReader != null && !isStreamClosed()) { - this.sseScanner = new Scanner(sseReader); - } else { - this.endOfStream = true; - } - } - - @Override - public boolean hasNext() { - if (isStreamClosed() || endOfStream) { - return false; - } - - if (hasNextItem) { - return true; - } - - return readNextMessage(); - } - - @Override - public T next() { - if (!hasNext()) { - throw new NoSuchElementException("No more elements in stream"); - } - - T result = nextItem; - nextItem = null; - hasNextItem = false; - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - private boolean readNextMessage() { - if (sseScanner == null || isStreamClosed()) { - endOfStream = true; - return false; - } - - try { - while (sseScanner.hasNextLine()) { - String line = sseScanner.nextLine(); - - if (line.trim().isEmpty()) { - if (eventDataBuffer.length() > 0 || currentEventType != null) { - try { - // Use SseEventParser for event-level discrimination - nextItem = SseEventParser.parseEventLevelUnion( - currentEventType, - eventDataBuffer.toString(), - currentEventId, - currentRetry, - valueType, - discriminatorProperty); - hasNextItem = true; - resetEventState(); - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); - resetEventState(); - continue; - } - } - continue; - } - - if (line.startsWith(DATA_PREFIX)) { - String dataContent = line.substring(DATA_PREFIX.length()); - if (dataContent.startsWith(" ")) { - dataContent = dataContent.substring(1); - } - - if (eventDataBuffer.length() == 0 - && streamTerminator != null - && dataContent.trim().equals(streamTerminator)) { - endOfStream = true; - return false; - } - - if (eventDataBuffer.length() > 0) { - eventDataBuffer.append('\n'); - } - eventDataBuffer.append(dataContent); - } else if (line.startsWith("event:")) { - String eventValue = line.length() > 6 ? line.substring(6) : ""; - if (eventValue.startsWith(" ")) { - eventValue = eventValue.substring(1); - } - currentEventType = eventValue; - } else if (line.startsWith("id:")) { - String idValue = line.length() > 3 ? line.substring(3) : ""; - if (idValue.startsWith(" ")) { - idValue = idValue.substring(1); - } - currentEventId = idValue; - } else if (line.startsWith("retry:")) { - String retryValue = line.length() > 6 ? line.substring(6) : ""; - if (retryValue.startsWith(" ")) { - retryValue = retryValue.substring(1); - } - try { - currentRetry = Long.parseLong(retryValue.trim()); - } catch (NumberFormatException e) { - // Ignore invalid retry values - } - } else if (line.startsWith(":")) { - // Comment line (ignored) - } - } - - // Handle any remaining buffered data at end of stream - if (eventDataBuffer.length() > 0 || currentEventType != null) { - try { - nextItem = SseEventParser.parseEventLevelUnion( - currentEventType, - eventDataBuffer.toString(), - currentEventId, - currentRetry, - valueType, - discriminatorProperty); - hasNextItem = true; - resetEventState(); - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); - resetEventState(); - } - } - - endOfStream = true; - return false; - - } catch (Exception e) { - System.err.println("Failed to parse SSE stream: " + e.getMessage()); - endOfStream = true; - return false; - } - } - - private void resetEventState() { - eventDataBuffer.setLength(0); - currentEventType = null; - currentEventId = null; - currentRetry = null; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java deleted file mode 100644 index a3c24e968576..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/core/Suppliers.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -public final class Suppliers { - private Suppliers() {} - - public static Supplier memoize(Supplier delegate) { - AtomicReference value = new AtomicReference<>(); - return () -> { - T val = value.get(); - if (val == null) { - val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); - } - return val; - }; - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java deleted file mode 100644 index 0f39ad2d3cf4..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/RuleCreateRequest.java +++ /dev/null @@ -1,142 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.requests; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import com.seed.api.types.RuleExecutionContext; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleCreateRequest.Builder.class) -public final class RuleCreateRequest { - private final String name; - - private final RuleExecutionContext executionContext; - - private final Map additionalProperties; - - private RuleCreateRequest( - String name, RuleExecutionContext executionContext, Map additionalProperties) { - this.name = name; - this.executionContext = executionContext; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("executionContext") - public RuleExecutionContext getExecutionContext() { - return executionContext; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleCreateRequest && equalTo((RuleCreateRequest) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleCreateRequest other) { - return name.equals(other.name) && executionContext.equals(other.executionContext); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.executionContext); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NameStage builder() { - return new Builder(); - } - - public interface NameStage { - ExecutionContextStage name(@NotNull String name); - - Builder from(RuleCreateRequest other); - } - - public interface ExecutionContextStage { - _FinalStage executionContext(@NotNull RuleExecutionContext executionContext); - } - - public interface _FinalStage { - RuleCreateRequest build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements NameStage, ExecutionContextStage, _FinalStage { - private String name; - - private RuleExecutionContext executionContext; - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleCreateRequest other) { - name(other.getName()); - executionContext(other.getExecutionContext()); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public ExecutionContextStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("executionContext") - public _FinalStage executionContext(@NotNull RuleExecutionContext executionContext) { - this.executionContext = Objects.requireNonNull(executionContext, "executionContext must not be null"); - return this; - } - - @java.lang.Override - public RuleCreateRequest build() { - return new RuleCreateRequest(name, executionContext, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java deleted file mode 100644 index 80659000defc..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.requests; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SearchRuleTypesRequest.Builder.class) -public final class SearchRuleTypesRequest { - private final Optional query; - - private final Map additionalProperties; - - private SearchRuleTypesRequest(Optional query, Map additionalProperties) { - this.query = query; - this.additionalProperties = additionalProperties; - } - - @JsonIgnore - public Optional getQuery() { - return query; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SearchRuleTypesRequest && equalTo((SearchRuleTypesRequest) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SearchRuleTypesRequest other) { - return query.equals(other.query); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.query); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional query = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SearchRuleTypesRequest other) { - query(other.getQuery()); - return this; - } - - @JsonSetter(value = "query", nulls = Nulls.SKIP) - public Builder query(Optional query) { - this.query = query; - return this; - } - - public Builder query(String query) { - this.query = Optional.ofNullable(query); - return this; - } - - public SearchRuleTypesRequest build() { - return new SearchRuleTypesRequest(query, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java deleted file mode 100644 index 472220038b70..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/AuditInfo.java +++ /dev/null @@ -1,204 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.time.OffsetDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AuditInfo.Builder.class) -public final class AuditInfo { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private final Map additionalProperties; - - private AuditInfo( - Optional createdBy, - Optional createdDateTime, - Optional modifiedBy, - Optional modifiedDateTime, - Map additionalProperties) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - this.additionalProperties = additionalProperties; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AuditInfo && equalTo((AuditInfo) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(AuditInfo other) { - return createdBy.equals(other.createdBy) - && createdDateTime.equals(other.createdDateTime) - && modifiedBy.equals(other.modifiedBy) - && modifiedDateTime.equals(other.modifiedDateTime); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional createdBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AuditInfo other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - return this; - } - - /** - *

The user who created this resource.

- */ - @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) - public Builder createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - public Builder createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

When this resource was created.

- */ - @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) - public Builder createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - public Builder createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) - public Builder modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - public Builder modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

When this resource was last modified.

- */ - @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) - public Builder modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - public AuditInfo build() { - return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java deleted file mode 100644 index 2ff2e67bb3b4..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrg.java +++ /dev/null @@ -1,148 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = BaseOrg.Builder.class) -public final class BaseOrg { - private final String id; - - private final Optional metadata; - - private final Map additionalProperties; - - private BaseOrg(String id, Optional metadata, Map additionalProperties) { - this.id = id; - this.metadata = metadata; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrg && equalTo((BaseOrg) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(BaseOrg other) { - return id.equals(other.id) && metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - _FinalStage id(@NotNull String id); - - Builder from(BaseOrg other); - } - - public interface _FinalStage { - BaseOrg build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(BaseOrgMetadata metadata); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional metadata = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(BaseOrg other) { - id(other.getId()); - metadata(other.getMetadata()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(BaseOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter(value = "metadata", nulls = Nulls.SKIP) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public BaseOrg build() { - return new BaseOrg(id, metadata, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java deleted file mode 100644 index a444b87ccc24..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/BaseOrgMetadata.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = BaseOrgMetadata.Builder.class) -public final class BaseOrgMetadata { - private final String region; - - private final Optional tier; - - private final Map additionalProperties; - - private BaseOrgMetadata(String region, Optional tier, Map additionalProperties) { - this.region = region; - this.tier = tier; - this.additionalProperties = additionalProperties; - } - - /** - * @return Deployment region from BaseOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Subscription tier. - */ - @JsonProperty("tier") - public Optional getTier() { - return tier; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(BaseOrgMetadata other) { - return region.equals(other.region) && tier.equals(other.tier); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.tier); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from BaseOrg.

- */ - _FinalStage region(@NotNull String region); - - Builder from(BaseOrgMetadata other); - } - - public interface _FinalStage { - BaseOrgMetadata build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Subscription tier.

- */ - _FinalStage tier(Optional tier); - - _FinalStage tier(String tier); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional tier = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(BaseOrgMetadata other) { - region(other.getRegion()); - tier(other.getTier()); - return this; - } - - /** - *

Deployment region from BaseOrg.

- *

Deployment region from BaseOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(@NotNull String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Subscription tier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage tier(String tier) { - this.tier = Optional.ofNullable(tier); - return this; - } - - /** - *

Subscription tier.

- */ - @java.lang.Override - @JsonSetter(value = "tier", nulls = Nulls.SKIP) - public _FinalStage tier(Optional tier) { - this.tier = tier; - return this; - } - - @java.lang.Override - public BaseOrgMetadata build() { - return new BaseOrgMetadata(region, tier, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java deleted file mode 100644 index c10996083da5..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntity.java +++ /dev/null @@ -1,243 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = CombinedEntity.Builder.class) -public final class CombinedEntity { - private final String id; - - private final Optional name; - - private final Optional summary; - - private final CombinedEntityStatus status; - - private final Map additionalProperties; - - private CombinedEntity( - String id, - Optional name, - Optional summary, - CombinedEntityStatus status, - Map additionalProperties) { - this.id = id; - this.name = name; - this.summary = summary; - this.status = status; - this.additionalProperties = additionalProperties; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Describable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @JsonProperty("status") - public CombinedEntityStatus getStatus() { - return status; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof CombinedEntity && equalTo((CombinedEntity) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(CombinedEntity other) { - return id.equals(other.id) - && name.equals(other.name) - && summary.equals(other.summary) - && status.equals(other.status); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.summary, this.status); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - StatusStage id(@NotNull String id); - - Builder from(CombinedEntity other); - } - - public interface StatusStage { - _FinalStage status(@NotNull CombinedEntityStatus status); - } - - public interface _FinalStage { - CombinedEntity build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Display name from Describable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - - /** - *

A short summary.

- */ - _FinalStage summary(Optional summary); - - _FinalStage summary(String summary); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, StatusStage, _FinalStage { - private String id; - - private CombinedEntityStatus status; - - private Optional summary = Optional.empty(); - - private Optional name = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(CombinedEntity other) { - id(other.getId()); - name(other.getName()); - summary(other.getSummary()); - status(other.getStatus()); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public StatusStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public _FinalStage status(@NotNull CombinedEntityStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - /** - *

A short summary.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - /** - *

A short summary.

- */ - @java.lang.Override - @JsonSetter(value = "summary", nulls = Nulls.SKIP) - public _FinalStage summary(Optional summary) { - this.summary = summary; - return this; - } - - /** - *

Display name from Describable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Describable.

- */ - @java.lang.Override - @JsonSetter(value = "name", nulls = Nulls.SKIP) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public CombinedEntity build() { - return new CombinedEntity(id, name, summary, status, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java deleted file mode 100644 index 86e82fb63f85..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/CombinedEntityStatus.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class CombinedEntityStatus { - public static final CombinedEntityStatus ARCHIVED = new CombinedEntityStatus(Value.ARCHIVED, "archived"); - - public static final CombinedEntityStatus ACTIVE = new CombinedEntityStatus(Value.ACTIVE, "active"); - - private final Value value; - - private final String string; - - CombinedEntityStatus(Value value, String string) { - this.value = value; - this.string = string; - } - - public Value getEnumValue() { - return value; - } - - @java.lang.Override - @JsonValue - public String toString() { - return this.string; - } - - @java.lang.Override - public boolean equals(Object other) { - return (this == other) - || (other instanceof CombinedEntityStatus && this.string.equals(((CombinedEntityStatus) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case ARCHIVED: - return visitor.visitArchived(); - case ACTIVE: - return visitor.visitActive(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static CombinedEntityStatus valueOf(String value) { - switch (value) { - case "archived": - return ARCHIVED; - case "active": - return ACTIVE; - default: - return new CombinedEntityStatus(Value.UNKNOWN, value); - } - } - - public enum Value { - ACTIVE, - - ARCHIVED, - - UNKNOWN - } - - public interface Visitor { - T visitActive(); - - T visitArchived(); - - T visitUnknown(String unknownType); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java deleted file mode 100644 index b16e91712b0e..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Describable.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = Describable.Builder.class) -public final class Describable { - private final Optional name; - - private final Optional summary; - - private final Map additionalProperties; - - private Describable(Optional name, Optional summary, Map additionalProperties) { - this.name = name; - this.summary = summary; - this.additionalProperties = additionalProperties; - } - - /** - * @return Display name from Describable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Describable && equalTo((Describable) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(Describable other) { - return name.equals(other.name) && summary.equals(other.summary); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.summary); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional name = Optional.empty(); - - private Optional summary = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(Describable other) { - name(other.getName()); - summary(other.getSummary()); - return this; - } - - /** - *

Display name from Describable.

- */ - @JsonSetter(value = "name", nulls = Nulls.SKIP) - public Builder name(Optional name) { - this.name = name; - return this; - } - - public Builder name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

A short summary.

- */ - @JsonSetter(value = "summary", nulls = Nulls.SKIP) - public Builder summary(Optional summary) { - this.summary = summary; - return this; - } - - public Builder summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - public Describable build() { - return new Describable(name, summary, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java deleted file mode 100644 index ef605e7427da..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrg.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = DetailedOrg.Builder.class) -public final class DetailedOrg { - private final Optional metadata; - - private final Map additionalProperties; - - private DetailedOrg(Optional metadata, Map additionalProperties) { - this.metadata = metadata; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrg && equalTo((DetailedOrg) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(DetailedOrg other) { - return metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional metadata = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(DetailedOrg other) { - metadata(other.getMetadata()); - return this; - } - - @JsonSetter(value = "metadata", nulls = Nulls.SKIP) - public Builder metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - public Builder metadata(DetailedOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - public DetailedOrg build() { - return new DetailedOrg(metadata, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java deleted file mode 100644 index e65ef4975e85..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/DetailedOrgMetadata.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = DetailedOrgMetadata.Builder.class) -public final class DetailedOrgMetadata { - private final String region; - - private final Optional domain; - - private final Map additionalProperties; - - private DetailedOrgMetadata(String region, Optional domain, Map additionalProperties) { - this.region = region; - this.domain = domain; - this.additionalProperties = additionalProperties; - } - - /** - * @return Deployment region from DetailedOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Custom domain name. - */ - @JsonProperty("domain") - public Optional getDomain() { - return domain; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(DetailedOrgMetadata other) { - return region.equals(other.region) && domain.equals(other.domain); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.domain); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from DetailedOrg.

- */ - _FinalStage region(@NotNull String region); - - Builder from(DetailedOrgMetadata other); - } - - public interface _FinalStage { - DetailedOrgMetadata build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Custom domain name.

- */ - _FinalStage domain(Optional domain); - - _FinalStage domain(String domain); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional domain = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(DetailedOrgMetadata other) { - region(other.getRegion()); - domain(other.getDomain()); - return this; - } - - /** - *

Deployment region from DetailedOrg.

- *

Deployment region from DetailedOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(@NotNull String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Custom domain name.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage domain(String domain) { - this.domain = Optional.ofNullable(domain); - return this; - } - - /** - *

Custom domain name.

- */ - @java.lang.Override - @JsonSetter(value = "domain", nulls = Nulls.SKIP) - public _FinalStage domain(Optional domain) { - this.domain = domain; - return this; - } - - @java.lang.Override - public DetailedOrgMetadata build() { - return new DetailedOrgMetadata(region, domain, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java deleted file mode 100644 index 8a35f505a9bf..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Identifiable.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = Identifiable.Builder.class) -public final class Identifiable { - private final String id; - - private final Optional name; - - private final Map additionalProperties; - - private Identifiable(String id, Optional name, Map additionalProperties) { - this.id = id; - this.name = name; - this.additionalProperties = additionalProperties; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Identifiable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Identifiable && equalTo((Identifiable) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(Identifiable other) { - return id.equals(other.id) && name.equals(other.name); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - _FinalStage id(@NotNull String id); - - Builder from(Identifiable other); - } - - public interface _FinalStage { - Identifiable build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Display name from Identifiable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional name = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(Identifiable other) { - id(other.getId()); - name(other.getName()); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - /** - *

Display name from Identifiable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Identifiable.

- */ - @java.lang.Override - @JsonSetter(value = "name", nulls = Nulls.SKIP) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public Identifiable build() { - return new Identifiable(id, name, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java deleted file mode 100644 index 2d890a4bdb53..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/Organization.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = Organization.Builder.class) -public final class Organization { - private final String id; - - private final Optional metadata; - - private final String name; - - private final Map additionalProperties; - - private Organization( - String id, Optional metadata, String name, Map additionalProperties) { - this.id = id; - this.metadata = metadata; - this.name = name; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Organization && equalTo((Organization) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(Organization other) { - return id.equals(other.id) && metadata.equals(other.metadata) && name.equals(other.name); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.metadata, this.name); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(@NotNull String id); - - Builder from(Organization other); - } - - public interface NameStage { - _FinalStage name(@NotNull String name); - } - - public interface _FinalStage { - Organization build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(OrganizationMetadata metadata); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, NameStage, _FinalStage { - private String id; - - private String name; - - private Optional metadata = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(Organization other) { - id(other.getId()); - metadata(other.getMetadata()); - name(other.getName()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public _FinalStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(OrganizationMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter(value = "metadata", nulls = Nulls.SKIP) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public Organization build() { - return new Organization(id, metadata, name, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java deleted file mode 100644 index c1665b373e03..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/OrganizationMetadata.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = OrganizationMetadata.Builder.class) -public final class OrganizationMetadata { - private final String region; - - private final Optional domain; - - private final Map additionalProperties; - - private OrganizationMetadata(String region, Optional domain, Map additionalProperties) { - this.region = region; - this.domain = domain; - this.additionalProperties = additionalProperties; - } - - /** - * @return Deployment region from DetailedOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Custom domain name. - */ - @JsonProperty("domain") - public Optional getDomain() { - return domain; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof OrganizationMetadata && equalTo((OrganizationMetadata) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(OrganizationMetadata other) { - return region.equals(other.region) && domain.equals(other.domain); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.domain); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from DetailedOrg.

- */ - _FinalStage region(@NotNull String region); - - Builder from(OrganizationMetadata other); - } - - public interface _FinalStage { - OrganizationMetadata build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Custom domain name.

- */ - _FinalStage domain(Optional domain); - - _FinalStage domain(String domain); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional domain = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(OrganizationMetadata other) { - region(other.getRegion()); - domain(other.getDomain()); - return this; - } - - /** - *

Deployment region from DetailedOrg.

- *

Deployment region from DetailedOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(@NotNull String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Custom domain name.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage domain(String domain) { - this.domain = Optional.ofNullable(domain); - return this; - } - - /** - *

Custom domain name.

- */ - @java.lang.Override - @JsonSetter(value = "domain", nulls = Nulls.SKIP) - public _FinalStage domain(Optional domain) { - this.domain = domain; - return this; - } - - @java.lang.Override - public OrganizationMetadata build() { - return new OrganizationMetadata(region, domain, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java deleted file mode 100644 index 2994a6d8c2fd..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PaginatedResult.java +++ /dev/null @@ -1,179 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = PaginatedResult.Builder.class) -public final class PaginatedResult { - private final PagingCursors paging; - - private final List results; - - private final Map additionalProperties; - - private PaginatedResult(PagingCursors paging, List results, Map additionalProperties) { - this.paging = paging; - this.results = results; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public List getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PaginatedResult && equalTo((PaginatedResult) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(PaginatedResult other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(@NotNull PagingCursors paging); - - Builder from(PaginatedResult other); - } - - public interface _FinalStage { - PaginatedResult build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(List results); - - _FinalStage addResults(Object results); - - _FinalStage addAllResults(List results); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private List results = new ArrayList<>(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(PaginatedResult other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(@NotNull PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addAllResults(List results) { - if (results != null) { - this.results.addAll(results); - } - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addResults(Object results) { - this.results.add(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public _FinalStage results(List results) { - this.results.clear(); - if (results != null) { - this.results.addAll(results); - } - return this; - } - - @java.lang.Override - public PaginatedResult build() { - return new PaginatedResult(paging, results, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java deleted file mode 100644 index 5453abc20ef3..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/PagingCursors.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = PagingCursors.Builder.class) -public final class PagingCursors { - private final String next; - - private final Optional previous; - - private final Map additionalProperties; - - private PagingCursors(String next, Optional previous, Map additionalProperties) { - this.next = next; - this.previous = previous; - this.additionalProperties = additionalProperties; - } - - /** - * @return Cursor for the next page of results. - */ - @JsonProperty("next") - public String getNext() { - return next; - } - - /** - * @return Cursor for the previous page of results. - */ - @JsonProperty("previous") - public Optional getPrevious() { - return previous; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PagingCursors && equalTo((PagingCursors) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(PagingCursors other) { - return next.equals(other.next) && previous.equals(other.previous); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.next, this.previous); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NextStage builder() { - return new Builder(); - } - - public interface NextStage { - /** - *

Cursor for the next page of results.

- */ - _FinalStage next(@NotNull String next); - - Builder from(PagingCursors other); - } - - public interface _FinalStage { - PagingCursors build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Cursor for the previous page of results.

- */ - _FinalStage previous(Optional previous); - - _FinalStage previous(String previous); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements NextStage, _FinalStage { - private String next; - - private Optional previous = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(PagingCursors other) { - next(other.getNext()); - previous(other.getPrevious()); - return this; - } - - /** - *

Cursor for the next page of results.

- *

Cursor for the next page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("next") - public _FinalStage next(@NotNull String next) { - this.next = Objects.requireNonNull(next, "next must not be null"); - return this; - } - - /** - *

Cursor for the previous page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage previous(String previous) { - this.previous = Optional.ofNullable(previous); - return this; - } - - /** - *

Cursor for the previous page of results.

- */ - @java.lang.Override - @JsonSetter(value = "previous", nulls = Nulls.SKIP) - public _FinalStage previous(Optional previous) { - this.previous = previous; - return this; - } - - @java.lang.Override - public PagingCursors build() { - return new PagingCursors(next, previous, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java deleted file mode 100644 index 46c429ed089e..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleExecutionContext.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class RuleExecutionContext { - public static final RuleExecutionContext PROD = new RuleExecutionContext(Value.PROD, "prod"); - - public static final RuleExecutionContext DEV = new RuleExecutionContext(Value.DEV, "dev"); - - public static final RuleExecutionContext STAGING = new RuleExecutionContext(Value.STAGING, "staging"); - - private final Value value; - - private final String string; - - RuleExecutionContext(Value value, String string) { - this.value = value; - this.string = string; - } - - public Value getEnumValue() { - return value; - } - - @java.lang.Override - @JsonValue - public String toString() { - return this.string; - } - - @java.lang.Override - public boolean equals(Object other) { - return (this == other) - || (other instanceof RuleExecutionContext && this.string.equals(((RuleExecutionContext) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case PROD: - return visitor.visitProd(); - case DEV: - return visitor.visitDev(); - case STAGING: - return visitor.visitStaging(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static RuleExecutionContext valueOf(String value) { - switch (value) { - case "prod": - return PROD; - case "dev": - return DEV; - case "staging": - return STAGING; - default: - return new RuleExecutionContext(Value.UNKNOWN, value); - } - } - - public enum Value { - PROD, - - STAGING, - - DEV, - - UNKNOWN - } - - public interface Visitor { - T visitProd(); - - T visitStaging(); - - T visitDev(); - - T visitUnknown(String unknownType); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java deleted file mode 100644 index 1cda0fe9805e..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponse.java +++ /dev/null @@ -1,390 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.time.OffsetDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleResponse.Builder.class) -public final class RuleResponse { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private final String id; - - private final String name; - - private final RuleResponseStatus status; - - private final Optional executionContext; - - private final Map additionalProperties; - - private RuleResponse( - Optional createdBy, - Optional createdDateTime, - Optional modifiedBy, - Optional modifiedDateTime, - String id, - String name, - RuleResponseStatus status, - Optional executionContext, - Map additionalProperties) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - this.id = id; - this.name = name; - this.status = status; - this.executionContext = executionContext; - this.additionalProperties = additionalProperties; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("status") - public RuleResponseStatus getStatus() { - return status; - } - - @JsonProperty("executionContext") - public Optional getExecutionContext() { - return executionContext; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleResponse && equalTo((RuleResponse) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleResponse other) { - return createdBy.equals(other.createdBy) - && createdDateTime.equals(other.createdDateTime) - && modifiedBy.equals(other.modifiedBy) - && modifiedDateTime.equals(other.modifiedDateTime) - && id.equals(other.id) - && name.equals(other.name) - && status.equals(other.status) - && executionContext.equals(other.executionContext); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash( - this.createdBy, - this.createdDateTime, - this.modifiedBy, - this.modifiedDateTime, - this.id, - this.name, - this.status, - this.executionContext); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(@NotNull String id); - - Builder from(RuleResponse other); - } - - public interface NameStage { - StatusStage name(@NotNull String name); - } - - public interface StatusStage { - _FinalStage status(@NotNull RuleResponseStatus status); - } - - public interface _FinalStage { - RuleResponse build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

The user who created this resource.

- */ - _FinalStage createdBy(Optional createdBy); - - _FinalStage createdBy(String createdBy); - - /** - *

When this resource was created.

- */ - _FinalStage createdDateTime(Optional createdDateTime); - - _FinalStage createdDateTime(OffsetDateTime createdDateTime); - - /** - *

The user who last modified this resource.

- */ - _FinalStage modifiedBy(Optional modifiedBy); - - _FinalStage modifiedBy(String modifiedBy); - - /** - *

When this resource was last modified.

- */ - _FinalStage modifiedDateTime(Optional modifiedDateTime); - - _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); - - _FinalStage executionContext(Optional executionContext); - - _FinalStage executionContext(RuleExecutionContext executionContext); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { - private String id; - - private String name; - - private RuleResponseStatus status; - - private Optional executionContext = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional createdBy = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleResponse other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - id(other.getId()); - name(other.getName()); - status(other.getStatus()); - executionContext(other.getExecutionContext()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public StatusStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public _FinalStage status(@NotNull RuleResponseStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage executionContext(RuleExecutionContext executionContext) { - this.executionContext = Optional.ofNullable(executionContext); - return this; - } - - @java.lang.Override - @JsonSetter(value = "executionContext", nulls = Nulls.SKIP) - public _FinalStage executionContext(Optional executionContext) { - this.executionContext = executionContext; - return this; - } - - /** - *

When this resource was last modified.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - /** - *

When this resource was last modified.

- */ - @java.lang.Override - @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) - public _FinalStage modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - /** - *

The user who last modified this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @java.lang.Override - @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) - public _FinalStage modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - /** - *

When this resource was created.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

When this resource was created.

- */ - @java.lang.Override - @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) - public _FinalStage createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - /** - *

The user who created this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

The user who created this resource.

- */ - @java.lang.Override - @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) - public _FinalStage createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - @java.lang.Override - public RuleResponse build() { - return new RuleResponse( - createdBy, - createdDateTime, - modifiedBy, - modifiedDateTime, - id, - name, - status, - executionContext, - additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java deleted file mode 100644 index b771bb61005f..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleResponseStatus.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class RuleResponseStatus { - public static final RuleResponseStatus INACTIVE = new RuleResponseStatus(Value.INACTIVE, "inactive"); - - public static final RuleResponseStatus ACTIVE = new RuleResponseStatus(Value.ACTIVE, "active"); - - public static final RuleResponseStatus DRAFT = new RuleResponseStatus(Value.DRAFT, "draft"); - - private final Value value; - - private final String string; - - RuleResponseStatus(Value value, String string) { - this.value = value; - this.string = string; - } - - public Value getEnumValue() { - return value; - } - - @java.lang.Override - @JsonValue - public String toString() { - return this.string; - } - - @java.lang.Override - public boolean equals(Object other) { - return (this == other) - || (other instanceof RuleResponseStatus && this.string.equals(((RuleResponseStatus) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case INACTIVE: - return visitor.visitInactive(); - case ACTIVE: - return visitor.visitActive(); - case DRAFT: - return visitor.visitDraft(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static RuleResponseStatus valueOf(String value) { - switch (value) { - case "inactive": - return INACTIVE; - case "active": - return ACTIVE; - case "draft": - return DRAFT; - default: - return new RuleResponseStatus(Value.UNKNOWN, value); - } - } - - public enum Value { - ACTIVE, - - INACTIVE, - - DRAFT, - - UNKNOWN - } - - public interface Visitor { - T visitActive(); - - T visitInactive(); - - T visitDraft(); - - T visitUnknown(String unknownType); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java deleted file mode 100644 index 18d016fde1bd..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleType.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleType.Builder.class) -public final class RuleType { - private final String id; - - private final String name; - - private final Optional description; - - private final Map additionalProperties; - - private RuleType(String id, String name, Optional description, Map additionalProperties) { - this.id = id; - this.name = name; - this.description = description; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("description") - public Optional getDescription() { - return description; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleType && equalTo((RuleType) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleType other) { - return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.description); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(@NotNull String id); - - Builder from(RuleType other); - } - - public interface NameStage { - _FinalStage name(@NotNull String name); - } - - public interface _FinalStage { - RuleType build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - _FinalStage description(Optional description); - - _FinalStage description(String description); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, NameStage, _FinalStage { - private String id; - - private String name; - - private Optional description = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleType other) { - id(other.getId()); - name(other.getName()); - description(other.getDescription()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public _FinalStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage description(String description) { - this.description = Optional.ofNullable(description); - return this; - } - - @java.lang.Override - @JsonSetter(value = "description", nulls = Nulls.SKIP) - public _FinalStage description(Optional description) { - this.description = description; - return this; - } - - @java.lang.Override - public RuleType build() { - return new RuleType(id, name, description, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java deleted file mode 100644 index fca3c6ec8009..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleTypeSearchResponse.Builder.class) -public final class RuleTypeSearchResponse { - private final PagingCursors paging; - - private final Optional> results; - - private final Map additionalProperties; - - private RuleTypeSearchResponse( - PagingCursors paging, Optional> results, Map additionalProperties) { - this.paging = paging; - this.results = results; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleTypeSearchResponse other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(@NotNull PagingCursors paging); - - Builder from(RuleTypeSearchResponse other); - } - - public interface _FinalStage { - RuleTypeSearchResponse build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleTypeSearchResponse other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(@NotNull PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public RuleTypeSearchResponse build() { - return new RuleTypeSearchResponse(paging, results, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java deleted file mode 100644 index cb2b9f73c34e..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/User.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = User.Builder.class) -public final class User { - private final String id; - - private final String email; - - private final Map additionalProperties; - - private User(String id, String email, Map additionalProperties) { - this.id = id; - this.email = email; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("email") - public String getEmail() { - return email; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof User && equalTo((User) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(User other) { - return id.equals(other.id) && email.equals(other.email); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.email); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - EmailStage id(@NotNull String id); - - Builder from(User other); - } - - public interface EmailStage { - _FinalStage email(@NotNull String email); - } - - public interface _FinalStage { - User build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, EmailStage, _FinalStage { - private String id; - - private String email; - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(User other) { - id(other.getId()); - email(other.getEmail()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public EmailStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("email") - public _FinalStage email(@NotNull String email) { - this.email = Objects.requireNonNull(email, "email must not be null"); - return this; - } - - @java.lang.Override - public User build() { - return new User(id, email, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java b/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java deleted file mode 100644 index 0fc2c8e793aa..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/seed/api/types/UserSearchResponse.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = UserSearchResponse.Builder.class) -public final class UserSearchResponse { - private final PagingCursors paging; - - private final Optional> results; - - private final Map additionalProperties; - - private UserSearchResponse( - PagingCursors paging, Optional> results, Map additionalProperties) { - this.paging = paging; - this.results = results; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(UserSearchResponse other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(@NotNull PagingCursors paging); - - Builder from(UserSearchResponse other); - } - - public interface _FinalStage { - UserSearchResponse build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(UserSearchResponse other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(@NotNull PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public UserSearchResponse build() { - return new UserSearchResponse(paging, results, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java deleted file mode 100644 index 22ad146af59c..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example0.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.SearchRuleTypesRequest; - -public class Example0 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.searchRuleTypes(SearchRuleTypesRequest.builder().build()); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java deleted file mode 100644 index 20488261da14..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example1.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.SearchRuleTypesRequest; - -public class Example1 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.searchRuleTypes(SearchRuleTypesRequest.builder().query("query").build()); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java deleted file mode 100644 index e0f6cdddaea0..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example2.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.types.RuleExecutionContext; - -public class Example2 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.createRule(RuleCreateRequest.builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build()); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java deleted file mode 100644 index 7f1d77d08358..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example3.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.types.RuleExecutionContext; - -public class Example3 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.createRule(RuleCreateRequest.builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build()); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java deleted file mode 100644 index e680b39ebabe..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example4.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example4 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.listUsers(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java deleted file mode 100644 index 83e4c14bbe78..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example5.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example5 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.listUsers(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java deleted file mode 100644 index e1757715676e..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example6.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example6 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getEntity(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java deleted file mode 100644 index e180edb8e8a3..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example7.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example7 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getEntity(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java deleted file mode 100644 index e80deb53f21a..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example8.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example8 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getOrganization(); - } -} diff --git a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java b/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java deleted file mode 100644 index f5ab1ab4544d..000000000000 --- a/seed/java-sdk/allof-inline/src/main/java/com/snippets/Example9.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example9 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getOrganization(); - } -} diff --git a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java deleted file mode 100644 index 9bf64b7a1cf5..000000000000 --- a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/StreamTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import static org.junit.jupiter.api.Assertions.*; - -import com.seed.api.core.ObjectMappers; -import com.seed.api.core.Stream; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.junit.jupiter.api.Test; - -public final class StreamTest { - @Test - public void testJsonStream() { - List> messages = - Arrays.asList(createMap("message", "hello"), createMap("message", "world")); - List jsonStrings = messages.stream().map(StreamTest::mapToJson).collect(Collectors.toList()); - String input = String.join("\n", jsonStrings); - StringReader jsonInput = new StringReader(input); - Stream jsonStream = Stream.fromJson(Map.class, jsonInput); - int expectedMessages = 2; - int actualMessages = 0; - for (Map jsonObject : jsonStream) { - actualMessages++; - assertTrue(jsonObject.containsKey("message")); - } - assertEquals(expectedMessages, actualMessages); - } - - @Test - public void testSseStream() { - List> events = Arrays.asList(createMap("event", "start"), createMap("event", "end")); - List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); - String input = String.join("\n" + "\n", sseStrings); - StringReader sseInput = new StringReader(input); - Stream sseStream = Stream.fromSse(Map.class, sseInput); - int expectedEvents = 2; - int actualEvents = 0; - for (Map eventData : sseStream) { - actualEvents++; - assertTrue(eventData.containsKey("event")); - } - assertEquals(expectedEvents, actualEvents); - } - - @Test - public void testSseStreamWithTerminator() { - List> events = Arrays.asList(createMap("message", "first"), createMap("message", "second")); - List sseStrings = - new ArrayList<>(events.stream().map(StreamTest::mapToSse).collect(Collectors.toList())); - sseStrings.add("data: [DONE]"); - String input = String.join("\n" + "\n", sseStrings); - StringReader sseInput = new StringReader(input); - Stream sseStream = Stream.fromSse(Map.class, sseInput, "[DONE]"); - int expectedEvents = 2; - int actualEvents = 0; - for (Map eventData : sseStream) { - actualEvents++; - assertTrue(eventData.containsKey("message")); - } - assertEquals(expectedEvents, actualEvents); - } - - @Test - public void testSseEventDiscriminatedStream() { - List sseStrings = Arrays.asList( - mapToSseWithEvent("start", createMap("status", "pending")), - mapToSseWithEvent("end", createMap("status", "complete"))); - String input = String.join("\n" + "\n", sseStrings); - StringReader sseInput = new StringReader(input); - Stream sseStream = Stream.fromSseWithEventDiscrimination(Map.class, sseInput, "event"); - int expectedEvents = 2; - int actualEvents = 0; - for (Map eventData : sseStream) { - actualEvents++; - // Event-level discrimination includes the event field in the parsed result - assertTrue(eventData.containsKey("event")); - assertTrue(eventData.containsKey("data")); - } - assertEquals(expectedEvents, actualEvents); - } - - @Test - public void testStreamResourceManagement() throws IOException { - StringReader testInput = new StringReader("{\"test\":\"data\"}"); - Stream testStream = Stream.fromJson(Map.class, testInput); - testStream.close(); - assertFalse(testStream.iterator().hasNext()); - } - - private static String mapToJson(Map map) { - try { - return ObjectMappers.JSON_MAPPER.writeValueAsString(map); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static String mapToSse(Map map) { - return "data: " + mapToJson(map); - } - - private static String mapToSseWithEvent(String eventType, Map data) { - return "event: " + eventType + "\n" + "data: " + mapToJson(data); - } - - private static Map createMap(String key, String value) { - Map map = new HashMap<>(); - map.put(key, value); - return map; - } -} diff --git a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java deleted file mode 100644 index 1686cfd803c1..000000000000 --- a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/TestClient.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -public final class TestClient { - public void test() { - // Add tests here and mark this file in .fernignore - assert true; - } -} diff --git a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java b/seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java deleted file mode 100644 index ead1a49af7a2..000000000000 --- a/seed/java-sdk/allof-inline/src/test/java/com/seed/api/core/QueryStringMapperTest.java +++ /dev/null @@ -1,339 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import okhttp3.HttpUrl; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public final class QueryStringMapperTest { - @Test - public void testObjectWithQuotedString_indexedArrays() { - Map map = new HashMap() { - { - put("hello", "\"world\""); - } - }; - - String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; - - String actualQueryString = queryString( - new HashMap() { - { - put("withquoted", map); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectWithQuotedString_arraysAsRepeats() { - Map map = new HashMap() { - { - put("hello", "\"world\""); - } - }; - - String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; - - String actualQueryString = queryString( - new HashMap() { - { - put("withquoted", map); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObject_indexedArrays() { - Map map = new HashMap() { - { - put("foo", "bar"); - put("baz", "qux"); - } - }; - - String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; - - String actualQueryString = queryString( - new HashMap() { - { - put("metadata", map); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObject_arraysAsRepeats() { - Map map = new HashMap() { - { - put("foo", "bar"); - put("baz", "qux"); - } - }; - - String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; - - String actualQueryString = queryString( - new HashMap() { - { - put("metadata", map); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testNestedObject_indexedArrays() { - Map> nestedMap = new HashMap>() { - { - put("mapkey1", new HashMap() { - { - put("mapkey1mapkey1", "mapkey1mapkey1value"); - put("mapkey1mapkey2", "mapkey1mapkey2value"); - } - }); - put("mapkey2", new HashMap() { - { - put("mapkey2mapkey1", "mapkey2mapkey1value"); - } - }); - } - }; - - String expectedQueryString = - "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" - + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; - - String actualQueryString = queryString( - new HashMap() { - { - put("nested", nestedMap); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testNestedObject_arraysAsRepeats() { - Map> nestedMap = new HashMap>() { - { - put("mapkey1", new HashMap() { - { - put("mapkey1mapkey1", "mapkey1mapkey1value"); - put("mapkey1mapkey2", "mapkey1mapkey2value"); - } - }); - put("mapkey2", new HashMap() { - { - put("mapkey2mapkey1", "mapkey2mapkey1value"); - } - }); - } - }; - - String expectedQueryString = - "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" - + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; - - String actualQueryString = queryString( - new HashMap() { - { - put("nested", nestedMap); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testDateTime_indexedArrays() { - OffsetDateTime dateTime = - OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); - - String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; - - String actualQueryString = queryString( - new HashMap() { - { - put("datetime", dateTime); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testDateTime_arraysAsRepeats() { - OffsetDateTime dateTime = - OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); - - String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; - - String actualQueryString = queryString( - new HashMap() { - { - put("datetime", dateTime); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectArray_indexedArrays() { - List> mapArray = new ArrayList>() { - { - add(new HashMap() { - { - put("key", "hello"); - put("value", "world"); - } - }); - add(new HashMap() { - { - put("key", "foo"); - put("value", "bar"); - } - }); - add(new HashMap<>()); - } - }; - - String expectedQueryString = "objects%5B0%5D%5Bvalue%5D=world&objects%5B0%5D%5Bkey%5D=hello&objects%5B1%5D" - + "%5Bvalue%5D=bar&objects%5B1%5D%5Bkey%5D=foo"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objects", mapArray); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectArray_arraysAsRepeats() { - List> mapArray = new ArrayList>() { - { - add(new HashMap() { - { - put("key", "hello"); - put("value", "world"); - } - }); - add(new HashMap() { - { - put("key", "foo"); - put("value", "bar"); - } - }); - add(new HashMap<>()); - } - }; - - String expectedQueryString = - "objects%5Bvalue%5D=world&objects%5Bkey%5D=hello&objects%5Bvalue" + "%5D=bar&objects%5Bkey%5D=foo"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objects", mapArray); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectWithArray_indexedArrays() { - Map objectWithArray = new HashMap() { - { - put("id", "abc123"); - put("contactIds", new ArrayList() { - { - add("id1"); - add("id2"); - add("id3"); - } - }); - } - }; - - String expectedQueryString = - "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds%5D%5B0%5D=id1&objectwitharray" - + "%5BcontactIds%5D%5B1%5D=id2&objectwitharray%5BcontactIds%5D%5B2%5D=id3"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objectwitharray", objectWithArray); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectWithArray_arraysAsRepeats() { - Map objectWithArray = new HashMap() { - { - put("id", "abc123"); - put("contactIds", new ArrayList() { - { - add("id1"); - add("id2"); - add("id3"); - } - }); - } - }; - - String expectedQueryString = "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds" - + "%5D=id1&objectwitharray%5BcontactIds%5D=id2&objectwitharray%5BcontactIds%5D=id3"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objectwitharray", objectWithArray); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - private static String queryString(Map params, boolean arraysAsRepeats) { - HttpUrl.Builder httpUrl = HttpUrl.parse("http://www.fakewebsite.com/").newBuilder(); - params.forEach((paramName, paramValue) -> - QueryStringMapper.addQueryParameter(httpUrl, paramName, paramValue, arraysAsRepeats)); - return httpUrl.build().encodedQuery(); - } -} diff --git a/seed/java-sdk/allof/.fern/metadata.json b/seed/java-sdk/allof/.fern/metadata.json deleted file mode 100644 index 762fcfc814db..000000000000 --- a/seed/java-sdk/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/java-sdk/allof/.github/workflows/ci.yml b/seed/java-sdk/allof/.github/workflows/ci.yml deleted file mode 100644 index 09c8c666ad73..000000000000 --- a/seed/java-sdk/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Compile - run: ./gradlew compileJava - - test: - needs: [ compile ] - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Test - run: ./gradlew test - publish: - needs: [ compile, test ] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Java - id: setup-jre - uses: actions/setup-java@v1 - with: - java-version: "11" - architecture: x64 - - - name: Publish to maven - run: | - ./gradlew publish - env: - MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/allof/.gitignore b/seed/java-sdk/allof/.gitignore deleted file mode 100644 index d4199abc2cd4..000000000000 --- a/seed/java-sdk/allof/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -*.class -.project -.gradle -? -.classpath -.checkstyle -.settings -.node -build - -# IntelliJ -*.iml -*.ipr -*.iws -.idea/ -out/ - -# Eclipse/IntelliJ APT -generated_src/ -generated_testSrc/ -generated/ - -bin -build \ No newline at end of file diff --git a/seed/java-sdk/allof/README.md b/seed/java-sdk/allof/README.md deleted file mode 100644 index c880c4c6d68d..000000000000 --- a/seed/java-sdk/allof/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# Seed Java Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FJava) -[![Maven Central](https://img.shields.io/maven-central/v/com.fern/allof)](https://central.sonatype.com/artifact/com.fern/allof) - -The Seed Java library provides convenient access to the Seed APIs from Java. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Base Url](#base-url) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Custom Client](#custom-client) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Custom Headers](#custom-headers) - - [Access Raw Response Data](#access-raw-response-data) -- [Contributing](#contributing) - -## Installation - -### Gradle - -Add the dependency in your `build.gradle` file: - -```groovy -dependencies { - implementation 'com.fern:allof:0.0.1' -} -``` - -### Maven - -Add the dependency in your `pom.xml` file: - -```xml - - com.fern - allof - 0.0.1 - -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```java -package com.example.usage; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.types.RuleExecutionContext; - -public class Example { - public static void main(String[] args) { - SeedApiClient client = SeedApiClient - .builder() - .build(); - - client.createRule( - RuleCreateRequest - .builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build() - ); - } -} -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```java -import com.seed.api.SeedApiClient; -import com.seed.api.core.Environment; - -SeedApiClient client = SeedApiClient - .builder() - .environment(Environment.Default) - .build(); -``` - -## Base Url - -You can set a custom base URL when constructing the client. - -```java -import com.seed.api.SeedApiClient; - -SeedApiClient client = SeedApiClient - .builder() - .url("https://example.com") - .build(); -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), an API exception will be thrown. - -```java -import com.seed.api.core.SeedApiApiException; - -try{ - client.createRule(...); -} catch (SeedApiApiException e){ - // Do something with the API exception... -} -``` - -## Advanced - -### Custom Client - -This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one. -However, you can pass your own client like so: - -```java -import com.seed.api.SeedApiClient; -import okhttp3.OkHttpClient; - -OkHttpClient customClient = ...; - -SeedApiClient client = SeedApiClient - .builder() - .httpClient(customClient) - .build(); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). Before defaulting to exponential backoff, the SDK will first attempt to respect -the `Retry-After` header (as either in seconds or as an HTTP date), and then the `X-RateLimit-Reset` header -(as a Unix timestamp in epoch seconds); failing both of those, it will fall back to exponential backoff. - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` client option to configure this behavior. - -```java -import com.seed.api.SeedApiClient; - -SeedApiClient client = SeedApiClient - .builder() - .maxRetries(1) - .build(); -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. -```java -import com.seed.api.SeedApiClient; -import com.seed.api.core.RequestOptions; - -// Client level -SeedApiClient client = SeedApiClient - .builder() - .timeout(60) - .build(); - -// Request level -client.createRule( - ..., - RequestOptions - .builder() - .timeout(60) - .build() -); -``` - -### Custom Headers - -The SDK allows you to add custom headers to requests. You can configure headers at the client level or at the request level. - -```java -import com.seed.api.SeedApiClient; -import com.seed.api.core.RequestOptions; - -// Client level -SeedApiClient client = SeedApiClient - .builder() - .addHeader("X-Custom-Header", "custom-value") - .addHeader("X-Request-Id", "abc-123") - .build(); -; - -// Request level -client.createRule( - ..., - RequestOptions - .builder() - .addHeader("X-Request-Header", "request-value") - .build() -); -``` - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `withRawResponse()` method. -The `withRawResponse()` method returns a raw client that wraps all responses with `body()` and `headers()` methods. -(A normal client's `response` is identical to a raw client's `response.body()`.) - -```java -SeedApiHttpResponse response = client.withRawResponse().createRule(...); - -System.out.println(response.body()); -System.out.println(response.headers().get("X-My-Header")); -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/java-sdk/allof/build.gradle b/seed/java-sdk/allof/build.gradle deleted file mode 100644 index 33626ee04bbe..000000000000 --- a/seed/java-sdk/allof/build.gradle +++ /dev/null @@ -1,102 +0,0 @@ -plugins { - id 'java-library' - id 'maven-publish' - id 'com.diffplug.spotless' version '6.11.0' -} - -repositories { - mavenCentral() - maven { - url 'https://s01.oss.sonatype.org/content/repositories/releases/' - } -} - -dependencies { - api 'com.squareup.okhttp3:okhttp:5.2.1' - api 'com.fasterxml.jackson.core:jackson-databind:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' -} - - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -tasks.withType(Javadoc) { - failOnError false - options.addStringOption('Xdoclint:none', '-quiet') -} - -spotless { - java { - palantirJavaFormat() - } -} - - -java { - withSourcesJar() - withJavadocJar() -} - - -group = 'com.fern' - -version = '0.0.1' - -jar { - dependsOn(":generatePomFileForMavenPublication") - archiveBaseName = "allof" -} - -sourcesJar { - archiveBaseName = "allof" -} - -javadocJar { - archiveBaseName = "allof" -} - -test { - useJUnitPlatform() - testLogging { - showStandardStreams = true - } -} - -publishing { - publications { - maven(MavenPublication) { - groupId = 'com.fern' - artifactId = 'allof' - version = '0.0.1' - from components.java - pom { - licenses { - license { - name = 'The MIT License (MIT)' - url = 'https://mit-license.org/' - } - } - scm { - connection = 'scm:git:git://github.com/allof/fern.git' - developerConnection = 'scm:git:git://github.com/allof/fern.git' - url = 'https://github.com/allof/fern' - } - } - } - } - repositories { - maven { - url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" - credentials { - username "$System.env.MAVEN_USERNAME" - password "$System.env.MAVEN_PASSWORD" - } - } - } -} - diff --git a/seed/java-sdk/allof/reference.md b/seed/java-sdk/allof/reference.md deleted file mode 100644 index c37cf3e44a84..000000000000 --- a/seed/java-sdk/allof/reference.md +++ /dev/null @@ -1,174 +0,0 @@ -# Reference -
client.searchRuleTypes() -> RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.searchRuleTypes( - SearchRuleTypesRequest - .builder() - .build() -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `Optional` - -
-
-
-
- - -
-
-
- -
client.createRule(request) -> RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.createRule( - RuleCreateRequest - .builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build() -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `String` - -
-
- -
-
- -**executionContext:** `RuleExecutionContext` - -
-
-
-
- - -
-
-
- -
client.listUsers() -> UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.listUsers(); -``` -
-
-
-
- - -
-
-
- -
client.getEntity() -> CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.getEntity(); -``` -
-
-
-
- - -
-
-
- -
client.getOrganization() -> Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```java -client.getOrganization(); -``` -
-
-
-
- - -
-
-
- diff --git a/seed/java-sdk/allof/sample-app/build.gradle b/seed/java-sdk/allof/sample-app/build.gradle deleted file mode 100644 index 4ee8f227b7af..000000000000 --- a/seed/java-sdk/allof/sample-app/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id 'java-library' -} - -repositories { - mavenCentral() - maven { - url 'https://s01.oss.sonatype.org/content/repositories/releases/' - } -} - -dependencies { - implementation rootProject -} - - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - diff --git a/seed/java-sdk/allof/sample-app/src/main/java/sample/App.java b/seed/java-sdk/allof/sample-app/src/main/java/sample/App.java deleted file mode 100644 index 8d293789008c..000000000000 --- a/seed/java-sdk/allof/sample-app/src/main/java/sample/App.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -package sample; - -import java.lang.String; - -public final class App { - public static void main(String[] args) { - // import com.seed.api.AsyncSeedApiClient - } -} diff --git a/seed/java-sdk/allof/settings.gradle b/seed/java-sdk/allof/settings.gradle deleted file mode 100644 index 841ce1f693b0..000000000000 --- a/seed/java-sdk/allof/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name = 'allof' - -include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/allof/snippet.json b/seed/java-sdk/allof/snippet.json deleted file mode 100644 index 4ed7337cd530..000000000000 --- a/seed/java-sdk/allof/snippet.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "endpoints": [ - { - "example_identifier": "b6434d4c", - "id": { - "method": "GET", - "path": "/rule-types", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "72136c9a", - "id": { - "method": "GET", - "path": "/rule-types", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.SearchRuleTypesRequest;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.searchRuleTypes(\n SearchRuleTypesRequest\n .builder()\n .query(\"query\")\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "c1cf878e", - "id": { - "method": "POST", - "path": "/rules", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "1581df40", - "id": { - "method": "POST", - "path": "/rules", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.RuleCreateRequest;\nimport com.seed.api.types.RuleExecutionContext;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.createRule(\n RuleCreateRequest\n .builder()\n .name(\"name\")\n .executionContext(RuleExecutionContext.PROD)\n .build()\n );\n }\n}\n" - } - }, - { - "example_identifier": "55942cbc", - "id": { - "method": "GET", - "path": "/users", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" - } - }, - { - "example_identifier": "d95b49df", - "id": { - "method": "GET", - "path": "/users", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.listUsers();\n }\n}\n" - } - }, - { - "example_identifier": "b2b07150", - "id": { - "method": "GET", - "path": "/entities", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" - } - }, - { - "example_identifier": "51ca69d", - "id": { - "method": "GET", - "path": "/entities", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getEntity();\n }\n}\n" - } - }, - { - "example_identifier": "fd2e2e41", - "id": { - "method": "GET", - "path": "/organizations", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" - } - }, - { - "example_identifier": "6b857a2c", - "id": { - "method": "GET", - "path": "/organizations", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.getOrganization();\n }\n}\n" - } - } - ], - "types": {} -} \ No newline at end of file diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java deleted file mode 100644 index 046deb483a7f..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncRawSeedApiClient.java +++ /dev/null @@ -1,323 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.seed.api.core.ClientOptions; -import com.seed.api.core.MediaTypes; -import com.seed.api.core.ObjectMappers; -import com.seed.api.core.QueryStringMapper; -import com.seed.api.core.RequestOptions; -import com.seed.api.core.SeedApiApiException; -import com.seed.api.core.SeedApiException; -import com.seed.api.core.SeedApiHttpResponse; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; -import java.io.IOException; -import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; - -public class AsyncRawSeedApiClient { - protected final ClientOptions clientOptions; - - public AsyncRawSeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - } - - public CompletableFuture> searchRuleTypes() { - return searchRuleTypes(SearchRuleTypesRequest.builder().build()); - } - - public CompletableFuture> searchRuleTypes( - RequestOptions requestOptions) { - return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); - } - - public CompletableFuture> searchRuleTypes( - SearchRuleTypesRequest request) { - return searchRuleTypes(request, null); - } - - public CompletableFuture> searchRuleTypes( - SearchRuleTypesRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rule-types"); - if (request.getQuery().isPresent()) { - QueryStringMapper.addQueryParameter( - httpUrl, "query", request.getQuery().get(), false); - } - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request.Builder _requestBuilder = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json"); - Request okhttpRequest = _requestBuilder.build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), - response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> createRule(RuleCreateRequest request) { - return createRule(request, null); - } - - public CompletableFuture> createRule( - RuleCreateRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rules"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - RequestBody body; - try { - body = RequestBody.create( - ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); - } catch (JsonProcessingException e) { - throw new SeedApiException("Failed to serialize request", e); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("POST", body) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> listUsers() { - return listUsers(null); - } - - public CompletableFuture> listUsers(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("users"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), - response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> getEntity() { - return getEntity(null); - } - - public CompletableFuture> getEntity(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("entities"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), - response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } - - public CompletableFuture> getOrganization() { - return getOrganization(null); - } - - public CompletableFuture> getOrganization(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("organizations"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - CompletableFuture> future = new CompletableFuture<>(); - client.newCall(okhttpRequest).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - future.complete(new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response)); - return; - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - future.completeExceptionally(new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response)); - return; - } catch (IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - } - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); - } - }); - return future; - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java deleted file mode 100644 index 45b759db04ed..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClient.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.RequestOptions; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; -import java.util.concurrent.CompletableFuture; - -public class AsyncSeedApiClient { - protected final ClientOptions clientOptions; - - private final AsyncRawSeedApiClient rawClient; - - public AsyncSeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.rawClient = new AsyncRawSeedApiClient(clientOptions); - } - - /** - * Get responses with HTTP metadata like headers - */ - public AsyncRawSeedApiClient withRawResponse() { - return this.rawClient; - } - - public CompletableFuture searchRuleTypes() { - return this.rawClient.searchRuleTypes().thenApply(response -> response.body()); - } - - public CompletableFuture searchRuleTypes(RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture searchRuleTypes(SearchRuleTypesRequest request) { - return this.rawClient.searchRuleTypes(request).thenApply(response -> response.body()); - } - - public CompletableFuture searchRuleTypes( - SearchRuleTypesRequest request, RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(request, requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture createRule(RuleCreateRequest request) { - return this.rawClient.createRule(request).thenApply(response -> response.body()); - } - - public CompletableFuture createRule(RuleCreateRequest request, RequestOptions requestOptions) { - return this.rawClient.createRule(request, requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture listUsers() { - return this.rawClient.listUsers().thenApply(response -> response.body()); - } - - public CompletableFuture listUsers(RequestOptions requestOptions) { - return this.rawClient.listUsers(requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture getEntity() { - return this.rawClient.getEntity().thenApply(response -> response.body()); - } - - public CompletableFuture getEntity(RequestOptions requestOptions) { - return this.rawClient.getEntity(requestOptions).thenApply(response -> response.body()); - } - - public CompletableFuture getOrganization() { - return this.rawClient.getOrganization().thenApply(response -> response.body()); - } - - public CompletableFuture getOrganization(RequestOptions requestOptions) { - return this.rawClient.getOrganization(requestOptions).thenApply(response -> response.body()); - } - - public static AsyncSeedApiClientBuilder builder() { - return new AsyncSeedApiClientBuilder(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java b/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java deleted file mode 100644 index bc66923d39bc..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.Environment; -import com.seed.api.core.LogConfig; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import okhttp3.OkHttpClient; - -public class AsyncSeedApiClientBuilder { - private Optional timeout = Optional.empty(); - - private Optional maxRetries = Optional.empty(); - - private final Map customHeaders = new HashMap<>(); - - private Environment environment = Environment.DEFAULT; - - private OkHttpClient httpClient; - - private Optional logging = Optional.empty(); - - public AsyncSeedApiClientBuilder environment(Environment environment) { - this.environment = environment; - return this; - } - - public AsyncSeedApiClientBuilder url(String url) { - this.environment = Environment.custom(url); - return this; - } - - /** - * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. - */ - public AsyncSeedApiClientBuilder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Sets the maximum number of retries for the client. Defaults to 2 retries. - */ - public AsyncSeedApiClientBuilder maxRetries(int maxRetries) { - this.maxRetries = Optional.of(maxRetries); - return this; - } - - /** - * Sets the underlying OkHttp client - */ - public AsyncSeedApiClientBuilder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - /** - * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. - */ - public AsyncSeedApiClientBuilder logging(LogConfig logging) { - this.logging = Optional.of(logging); - return this; - } - - /** - * Add a custom header to be sent with all requests. - * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. - * - * @param name The header name - * @param value The header value - * @return This builder for method chaining - */ - public AsyncSeedApiClientBuilder addHeader(String name, String value) { - this.customHeaders.put(name, value); - return this; - } - - protected ClientOptions buildClientOptions() { - ClientOptions.Builder builder = ClientOptions.builder(); - setEnvironment(builder); - setHttpClient(builder); - setTimeouts(builder); - setRetries(builder); - setLogging(builder); - for (Map.Entry header : this.customHeaders.entrySet()) { - builder.addHeader(header.getKey(), header.getValue()); - } - setAdditional(builder); - return builder.build(); - } - - /** - * Sets the environment configuration for the client. - * Override this method to modify URLs or add environment-specific logic. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setEnvironment(ClientOptions.Builder builder) { - builder.environment(this.environment); - } - - /** - * Sets the request timeout configuration. - * Override this method to customize timeout behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setTimeouts(ClientOptions.Builder builder) { - if (this.timeout.isPresent()) { - builder.timeout(this.timeout.get()); - } - } - - /** - * Sets the retry configuration for failed requests. - * Override this method to implement custom retry strategies. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setRetries(ClientOptions.Builder builder) { - if (this.maxRetries.isPresent()) { - builder.maxRetries(this.maxRetries.get()); - } - } - - /** - * Sets the OkHttp client configuration. - * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setHttpClient(ClientOptions.Builder builder) { - if (this.httpClient != null) { - builder.httpClient(this.httpClient); - } - } - - /** - * Sets the logging configuration for the SDK. - * Override this method to customize logging behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setLogging(ClientOptions.Builder builder) { - if (this.logging.isPresent()) { - builder.logging(this.logging.get()); - } - } - - /** - * Override this method to add any additional configuration to the client. - * This method is called at the end of the configuration chain, allowing you to add - * custom headers, modify settings, or perform any other client customization. - * - * @param builder The ClientOptions.Builder to configure - * - * Example: - *
{@code
-     * @Override
-     * protected void setAdditional(ClientOptions.Builder builder) {
-     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
-     *     builder.addHeader("X-Client-Version", "1.0.0");
-     * }
-     * }
- */ - protected void setAdditional(ClientOptions.Builder builder) {} - - /** - * Override this method to add custom validation logic before the client is built. - * This method is called at the beginning of the build() method to ensure the configuration is valid. - * Throw an exception to prevent client creation if validation fails. - * - * Example: - *
{@code
-     * @Override
-     * protected void validateConfiguration() {
-     *     super.validateConfiguration(); // Run parent validations
-     *     if (tenantId == null || tenantId.isEmpty()) {
-     *         throw new IllegalStateException("tenantId is required");
-     *     }
-     * }
-     * }
- */ - protected void validateConfiguration() {} - - public AsyncSeedApiClient build() { - validateConfiguration(); - return new AsyncSeedApiClient(buildClientOptions()); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java deleted file mode 100644 index f9a763a744c4..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/RawSeedApiClient.java +++ /dev/null @@ -1,249 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.seed.api.core.ClientOptions; -import com.seed.api.core.MediaTypes; -import com.seed.api.core.ObjectMappers; -import com.seed.api.core.QueryStringMapper; -import com.seed.api.core.RequestOptions; -import com.seed.api.core.SeedApiApiException; -import com.seed.api.core.SeedApiException; -import com.seed.api.core.SeedApiHttpResponse; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; -import java.io.IOException; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; - -public class RawSeedApiClient { - protected final ClientOptions clientOptions; - - public RawSeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - } - - public SeedApiHttpResponse searchRuleTypes() { - return searchRuleTypes(SearchRuleTypesRequest.builder().build()); - } - - public SeedApiHttpResponse searchRuleTypes(RequestOptions requestOptions) { - return searchRuleTypes(SearchRuleTypesRequest.builder().build(), requestOptions); - } - - public SeedApiHttpResponse searchRuleTypes(SearchRuleTypesRequest request) { - return searchRuleTypes(request, null); - } - - public SeedApiHttpResponse searchRuleTypes( - SearchRuleTypesRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rule-types"); - if (request.getQuery().isPresent()) { - QueryStringMapper.addQueryParameter( - httpUrl, "query", request.getQuery().get(), false); - } - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request.Builder _requestBuilder = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json"); - Request okhttpRequest = _requestBuilder.build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleTypeSearchResponse.class), - response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse createRule(RuleCreateRequest request) { - return createRule(request, null); - } - - public SeedApiHttpResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("rules"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - RequestBody body; - try { - body = RequestBody.create( - ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); - } catch (JsonProcessingException e) { - throw new SeedApiException("Failed to serialize request", e); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("POST", body) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, RuleResponse.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse listUsers() { - return listUsers(null); - } - - public SeedApiHttpResponse listUsers(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("users"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, UserSearchResponse.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse getEntity() { - return getEntity(null); - } - - public SeedApiHttpResponse getEntity(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("entities"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, CombinedEntity.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } - - public SeedApiHttpResponse getOrganization() { - return getOrganization(null); - } - - public SeedApiHttpResponse getOrganization(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) - .newBuilder() - .addPathSegments("organizations"); - if (requestOptions != null) { - requestOptions.getQueryParameters().forEach((_key, _value) -> { - httpUrl.addQueryParameter(_key, _value); - }); - } - Request okhttpRequest = new Request.Builder() - .url(httpUrl.build()) - .method("GET", null) - .headers(Headers.of(clientOptions.headers(requestOptions))) - .addHeader("Accept", "application/json") - .build(); - OkHttpClient client = clientOptions.httpClient(); - if (requestOptions != null && requestOptions.getTimeout().isPresent()) { - client = clientOptions.httpClientWithTimeout(requestOptions); - } - try (Response response = client.newCall(okhttpRequest).execute()) { - ResponseBody responseBody = response.body(); - String responseBodyString = responseBody != null ? responseBody.string() : "{}"; - if (response.isSuccessful()) { - return new SeedApiHttpResponse<>( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Organization.class), response); - } - Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); - throw new SeedApiApiException( - "Error with status code " + response.code(), response.code(), errorBody, response); - } catch (IOException e) { - throw new SeedApiException("Network error executing HTTP request", e); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java b/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java deleted file mode 100644 index 73a73c0a87a2..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClient.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.RequestOptions; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.requests.SearchRuleTypesRequest; -import com.seed.api.types.CombinedEntity; -import com.seed.api.types.Organization; -import com.seed.api.types.RuleResponse; -import com.seed.api.types.RuleTypeSearchResponse; -import com.seed.api.types.UserSearchResponse; - -public class SeedApiClient { - protected final ClientOptions clientOptions; - - private final RawSeedApiClient rawClient; - - public SeedApiClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.rawClient = new RawSeedApiClient(clientOptions); - } - - /** - * Get responses with HTTP metadata like headers - */ - public RawSeedApiClient withRawResponse() { - return this.rawClient; - } - - public RuleTypeSearchResponse searchRuleTypes() { - return this.rawClient.searchRuleTypes().body(); - } - - public RuleTypeSearchResponse searchRuleTypes(RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(requestOptions).body(); - } - - public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request) { - return this.rawClient.searchRuleTypes(request).body(); - } - - public RuleTypeSearchResponse searchRuleTypes(SearchRuleTypesRequest request, RequestOptions requestOptions) { - return this.rawClient.searchRuleTypes(request, requestOptions).body(); - } - - public RuleResponse createRule(RuleCreateRequest request) { - return this.rawClient.createRule(request).body(); - } - - public RuleResponse createRule(RuleCreateRequest request, RequestOptions requestOptions) { - return this.rawClient.createRule(request, requestOptions).body(); - } - - public UserSearchResponse listUsers() { - return this.rawClient.listUsers().body(); - } - - public UserSearchResponse listUsers(RequestOptions requestOptions) { - return this.rawClient.listUsers(requestOptions).body(); - } - - public CombinedEntity getEntity() { - return this.rawClient.getEntity().body(); - } - - public CombinedEntity getEntity(RequestOptions requestOptions) { - return this.rawClient.getEntity(requestOptions).body(); - } - - public Organization getOrganization() { - return this.rawClient.getOrganization().body(); - } - - public Organization getOrganization(RequestOptions requestOptions) { - return this.rawClient.getOrganization(requestOptions).body(); - } - - public static SeedApiClientBuilder builder() { - return new SeedApiClientBuilder(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java b/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java deleted file mode 100644 index 5f458e5b960a..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/SeedApiClientBuilder.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import com.seed.api.core.ClientOptions; -import com.seed.api.core.Environment; -import com.seed.api.core.LogConfig; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import okhttp3.OkHttpClient; - -public class SeedApiClientBuilder { - private Optional timeout = Optional.empty(); - - private Optional maxRetries = Optional.empty(); - - private final Map customHeaders = new HashMap<>(); - - private Environment environment = Environment.DEFAULT; - - private OkHttpClient httpClient; - - private Optional logging = Optional.empty(); - - public SeedApiClientBuilder environment(Environment environment) { - this.environment = environment; - return this; - } - - public SeedApiClientBuilder url(String url) { - this.environment = Environment.custom(url); - return this; - } - - /** - * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. - */ - public SeedApiClientBuilder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Sets the maximum number of retries for the client. Defaults to 2 retries. - */ - public SeedApiClientBuilder maxRetries(int maxRetries) { - this.maxRetries = Optional.of(maxRetries); - return this; - } - - /** - * Sets the underlying OkHttp client - */ - public SeedApiClientBuilder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - /** - * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. - */ - public SeedApiClientBuilder logging(LogConfig logging) { - this.logging = Optional.of(logging); - return this; - } - - /** - * Add a custom header to be sent with all requests. - * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. - * - * @param name The header name - * @param value The header value - * @return This builder for method chaining - */ - public SeedApiClientBuilder addHeader(String name, String value) { - this.customHeaders.put(name, value); - return this; - } - - protected ClientOptions buildClientOptions() { - ClientOptions.Builder builder = ClientOptions.builder(); - setEnvironment(builder); - setHttpClient(builder); - setTimeouts(builder); - setRetries(builder); - setLogging(builder); - for (Map.Entry header : this.customHeaders.entrySet()) { - builder.addHeader(header.getKey(), header.getValue()); - } - setAdditional(builder); - return builder.build(); - } - - /** - * Sets the environment configuration for the client. - * Override this method to modify URLs or add environment-specific logic. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setEnvironment(ClientOptions.Builder builder) { - builder.environment(this.environment); - } - - /** - * Sets the request timeout configuration. - * Override this method to customize timeout behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setTimeouts(ClientOptions.Builder builder) { - if (this.timeout.isPresent()) { - builder.timeout(this.timeout.get()); - } - } - - /** - * Sets the retry configuration for failed requests. - * Override this method to implement custom retry strategies. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setRetries(ClientOptions.Builder builder) { - if (this.maxRetries.isPresent()) { - builder.maxRetries(this.maxRetries.get()); - } - } - - /** - * Sets the OkHttp client configuration. - * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setHttpClient(ClientOptions.Builder builder) { - if (this.httpClient != null) { - builder.httpClient(this.httpClient); - } - } - - /** - * Sets the logging configuration for the SDK. - * Override this method to customize logging behavior. - * - * @param builder The ClientOptions.Builder to configure - */ - protected void setLogging(ClientOptions.Builder builder) { - if (this.logging.isPresent()) { - builder.logging(this.logging.get()); - } - } - - /** - * Override this method to add any additional configuration to the client. - * This method is called at the end of the configuration chain, allowing you to add - * custom headers, modify settings, or perform any other client customization. - * - * @param builder The ClientOptions.Builder to configure - * - * Example: - *
{@code
-     * @Override
-     * protected void setAdditional(ClientOptions.Builder builder) {
-     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
-     *     builder.addHeader("X-Client-Version", "1.0.0");
-     * }
-     * }
- */ - protected void setAdditional(ClientOptions.Builder builder) {} - - /** - * Override this method to add custom validation logic before the client is built. - * This method is called at the beginning of the build() method to ensure the configuration is valid. - * Throw an exception to prevent client creation if validation fails. - * - * Example: - *
{@code
-     * @Override
-     * protected void validateConfiguration() {
-     *     super.validateConfiguration(); // Run parent validations
-     *     if (tenantId == null || tenantId.isEmpty()) {
-     *         throw new IllegalStateException("tenantId is required");
-     *     }
-     * }
-     * }
- */ - protected void validateConfiguration() {} - - public SeedApiClient build() { - validateConfiguration(); - return new SeedApiClient(buildClientOptions()); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java deleted file mode 100644 index d5be33a0645f..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ClientOptions.java +++ /dev/null @@ -1,221 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import okhttp3.OkHttpClient; - -public final class ClientOptions { - private final Environment environment; - - private final Map headers; - - private final Map> headerSuppliers; - - private final OkHttpClient httpClient; - - private final int timeout; - - private final int maxRetries; - - private final Optional logging; - - private ClientOptions( - Environment environment, - Map headers, - Map> headerSuppliers, - OkHttpClient httpClient, - int timeout, - int maxRetries, - Optional logging) { - this.environment = environment; - this.headers = new HashMap<>(); - this.headers.putAll(headers); - this.headers.putAll(new HashMap() { - { - put("User-Agent", "com.fern:allof/0.0.1"); - put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.seed.fern:api-sdk"); - } - }); - this.headerSuppliers = headerSuppliers; - this.httpClient = httpClient; - this.timeout = timeout; - this.maxRetries = maxRetries; - this.logging = logging; - } - - public Environment environment() { - return this.environment; - } - - public Map headers(RequestOptions requestOptions) { - Map values = new HashMap<>(this.headers); - headerSuppliers.forEach((key, supplier) -> { - values.put(key, supplier.get()); - }); - if (requestOptions != null) { - values.putAll(requestOptions.getHeaders()); - } - return values; - } - - public int timeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.timeout; - } - return requestOptions.getTimeout().orElse(this.timeout); - } - - public OkHttpClient httpClient() { - return this.httpClient; - } - - public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.httpClient; - } - return this.httpClient - .newBuilder() - .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS) - .build(); - } - - public int maxRetries() { - return this.maxRetries; - } - - public Optional logging() { - return this.logging; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Environment environment; - - private final Map headers = new HashMap<>(); - - private final Map> headerSuppliers = new HashMap<>(); - - private int maxRetries = 2; - - private Optional timeout = Optional.empty(); - - private OkHttpClient httpClient = null; - - private Optional logging = Optional.empty(); - - public Builder environment(Environment environment) { - this.environment = environment; - return this; - } - - public Builder addHeader(String key, String value) { - this.headers.put(key, value); - return this; - } - - public Builder addHeader(String key, Supplier value) { - this.headerSuppliers.put(key, value); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(Optional timeout) { - this.timeout = timeout; - return this; - } - - /** - * Override the maximum number of retries. Defaults to 2 retries. - */ - public Builder maxRetries(int maxRetries) { - this.maxRetries = maxRetries; - return this; - } - - public Builder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - /** - * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. - */ - public Builder logging(LogConfig logging) { - this.logging = Optional.of(logging); - return this; - } - - public ClientOptions build() { - OkHttpClient.Builder httpClientBuilder = - this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); - - if (this.httpClient != null) { - timeout.ifPresent(timeout -> httpClientBuilder - .callTimeout(timeout, TimeUnit.SECONDS) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS)); - } else { - httpClientBuilder - .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS) - .addInterceptor(new RetryInterceptor(this.maxRetries)); - } - - Logger logger = Logger.from(this.logging); - httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); - - this.httpClient = httpClientBuilder.build(); - this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); - - return new ClientOptions( - environment, - headers, - headerSuppliers, - httpClient, - this.timeout.get(), - this.maxRetries, - this.logging); - } - - /** - * Create a new Builder initialized with values from an existing ClientOptions - */ - public static Builder from(ClientOptions clientOptions) { - Builder builder = new Builder(); - builder.environment = clientOptions.environment(); - builder.timeout = Optional.of(clientOptions.timeout(null)); - builder.httpClient = clientOptions.httpClient(); - builder.headers.putAll(clientOptions.headers); - builder.headerSuppliers.putAll(clientOptions.headerSuppliers); - builder.maxRetries = clientOptions.maxRetries(); - builder.logging = clientOptions.logging(); - return builder; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java deleted file mode 100644 index 0deabc0b79f7..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ConsoleLogger.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.logging.Level; - -/** - * Default logger implementation that writes to the console using {@link java.util.logging.Logger}. - * - *

Uses the "fern" logger name with a simple format of "LEVEL - message". - */ -public final class ConsoleLogger implements ILogger { - - private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger("fern"); - - static { - if (logger.getHandlers().length == 0) { - java.util.logging.ConsoleHandler handler = new java.util.logging.ConsoleHandler(); - handler.setFormatter(new java.util.logging.SimpleFormatter() { - @Override - public String format(java.util.logging.LogRecord record) { - return record.getLevel() + " - " + record.getMessage() + System.lineSeparator(); - } - }); - logger.addHandler(handler); - logger.setUseParentHandlers(false); - logger.setLevel(Level.ALL); - } - } - - @Override - public void debug(String message) { - logger.log(Level.FINE, message); - } - - @Override - public void info(String message) { - logger.log(Level.INFO, message); - } - - @Override - public void warn(String message) { - logger.log(Level.WARNING, message); - } - - @Override - public void error(String message) { - logger.log(Level.SEVERE, message); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java deleted file mode 100644 index eac7d50c71ae..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/DateTimeDeserializer.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalQueries; - -/** - * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. - */ -class DateTimeDeserializer extends JsonDeserializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); - } - - /** - * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - JsonToken token = parser.currentToken(); - if (token == JsonToken.VALUE_NUMBER_INT) { - return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); - } else { - TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( - parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); - - if (temporal.query(TemporalQueries.offset()) == null) { - return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); - } else { - return OffsetDateTime.from(temporal); - } - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java deleted file mode 100644 index 2390bfbe5690..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/DoubleSerializer.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; - -/** - * Custom serializer that writes integer-valued doubles without a decimal point. - * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. - * Non-integer values like {@code 3.14} are serialized normally. - */ -class DoubleSerializer extends JsonSerializer { - private static final SimpleModule MODULE; - - static { - MODULE = new SimpleModule() - .addSerializer(Double.class, new DoubleSerializer()) - .addSerializer(double.class, new DoubleSerializer()); - } - - /** - * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. - * - * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - return MODULE; - } - - @Override - public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { - gen.writeNumber(value.longValue()); - } else { - gen.writeNumber(value); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java deleted file mode 100644 index c612dbc31093..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Environment.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -public final class Environment { - public static final Environment DEFAULT = new Environment("https://api.example.com"); - - private final String url; - - private Environment(String url) { - this.url = url; - } - - public String getUrl() { - return this.url; - } - - public static Environment custom(String url) { - return new Environment(url); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java deleted file mode 100644 index a71e79469869..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/FileStream.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.InputStream; -import java.util.Objects; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import org.jetbrains.annotations.Nullable; - -/** - * Represents a file stream with associated metadata for file uploads. - */ -public class FileStream { - private final InputStream inputStream; - private final String fileName; - private final MediaType contentType; - - /** - * Constructs a FileStream with the given input stream and optional metadata. - * - * @param inputStream The input stream of the file content. Must not be null. - * @param fileName The name of the file, or null if unknown. - * @param contentType The MIME type of the file content, or null if unknown. - * @throws NullPointerException if inputStream is null - */ - public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { - this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); - this.fileName = fileName; - this.contentType = contentType; - } - - public FileStream(InputStream inputStream) { - this(inputStream, null, null); - } - - public InputStream getInputStream() { - return inputStream; - } - - @Nullable - public String getFileName() { - return fileName; - } - - @Nullable - public MediaType getContentType() { - return contentType; - } - - /** - * Creates a RequestBody suitable for use with OkHttp client. - * - * @return A RequestBody instance representing this file stream. - */ - public RequestBody toRequestBody() { - return new InputStreamRequestBody(contentType, inputStream); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java deleted file mode 100644 index 866d4814dd5c..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ILogger.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * Interface for custom logger implementations. - * - *

Implement this interface to provide a custom logging backend for the SDK. - * The SDK will call the appropriate method based on the log level. - * - *

Example: - *

{@code
- * public class MyCustomLogger implements ILogger {
- *     public void debug(String message) {
- *         System.out.println("[DBG] " + message);
- *     }
- *     public void info(String message) {
- *         System.out.println("[INF] " + message);
- *     }
- *     public void warn(String message) {
- *         System.out.println("[WRN] " + message);
- *     }
- *     public void error(String message) {
- *         System.out.println("[ERR] " + message);
- *     }
- * }
- * }
- */ -public interface ILogger { - void debug(String message); - - void info(String message); - - void warn(String message); - - void error(String message); -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java deleted file mode 100644 index a1e136889aaa..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/InputStreamRequestBody.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Objects; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import okio.BufferedSink; -import okio.Okio; -import okio.Source; -import org.jetbrains.annotations.Nullable; - -/** - * A custom implementation of OkHttp's RequestBody that wraps an InputStream. - * This class allows streaming of data from an InputStream directly to an HTTP request body, - * which is useful for file uploads or sending large amounts of data without loading it all into memory. - */ -public class InputStreamRequestBody extends RequestBody { - private final InputStream inputStream; - private final MediaType contentType; - - /** - * Constructs an InputStreamRequestBody with the specified content type and input stream. - * - * @param contentType the MediaType of the content, or null if not known - * @param inputStream the InputStream containing the data to be sent - * @throws NullPointerException if inputStream is null - */ - public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { - this.contentType = contentType; - this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); - } - - /** - * Returns the content type of this request body. - * - * @return the MediaType of the content, or null if not specified - */ - @Nullable - @Override - public MediaType contentType() { - return contentType; - } - - /** - * Returns the content length of this request body, if known. - * This method attempts to determine the length using the InputStream's available() method, - * which may not always accurately reflect the total length of the stream. - * - * @return the content length, or -1 if the length is unknown - * @throws IOException if an I/O error occurs - */ - @Override - public long contentLength() throws IOException { - return inputStream.available() == 0 ? -1 : inputStream.available(); - } - - /** - * Writes the content of the InputStream to the given BufferedSink. - * This method is responsible for transferring the data from the InputStream to the network request. - * - * @param sink the BufferedSink to write the content to - * @throws IOException if an I/O error occurs during writing - */ - @Override - public void writeTo(BufferedSink sink) throws IOException { - try (Source source = Okio.source(inputStream)) { - sink.writeAll(source); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java deleted file mode 100644 index dd31bc7a6545..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogConfig.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * Configuration for SDK logging. - * - *

Use the builder to configure logging behavior: - *

{@code
- * LogConfig config = LogConfig.builder()
- *     .level(LogLevel.DEBUG)
- *     .silent(false)
- *     .build();
- * }
- * - *

Or with a custom logger: - *

{@code
- * LogConfig config = LogConfig.builder()
- *     .level(LogLevel.DEBUG)
- *     .logger(new MyCustomLogger())
- *     .silent(false)
- *     .build();
- * }
- * - *

Defaults: - *

    - *
  • {@code level} — {@link LogLevel#INFO}
  • - *
  • {@code logger} — {@link ConsoleLogger} (writes to stderr via java.util.logging)
  • - *
  • {@code silent} — {@code true} (no output unless explicitly enabled)
  • - *
- */ -public final class LogConfig { - - private final LogLevel level; - private final ILogger logger; - private final boolean silent; - - private LogConfig(LogLevel level, ILogger logger, boolean silent) { - this.level = level; - this.logger = logger; - this.silent = silent; - } - - public LogLevel level() { - return level; - } - - public ILogger logger() { - return logger; - } - - public boolean silent() { - return silent; - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private LogLevel level = LogLevel.INFO; - private ILogger logger = new ConsoleLogger(); - private boolean silent = true; - - private Builder() {} - - /** - * Set the minimum log level. Only messages at this level or above will be logged. - * Defaults to {@link LogLevel#INFO}. - */ - public Builder level(LogLevel level) { - this.level = level; - return this; - } - - /** - * Set a custom logger implementation. Defaults to {@link ConsoleLogger}. - */ - public Builder logger(ILogger logger) { - this.logger = logger; - return this; - } - - /** - * Set whether logging is silent (disabled). Defaults to {@code true}. - * Set to {@code false} to enable log output. - */ - public Builder silent(boolean silent) { - this.silent = silent; - return this; - } - - public LogConfig build() { - return new LogConfig(level, logger, silent); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java deleted file mode 100644 index 2ef88f853eae..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LogLevel.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * Log levels for SDK logging configuration. - * Silent by default — no log output unless explicitly configured. - */ -public enum LogLevel { - DEBUG(1), - INFO(2), - WARN(3), - ERROR(4); - - private final int value; - - LogLevel(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - /** - * Parse a log level from a string (case-insensitive). - * - * @param level the level string (debug, info, warn, error) - * @return the corresponding LogLevel - * @throws IllegalArgumentException if the string does not match any level - */ - public static LogLevel fromString(String level) { - return LogLevel.valueOf(level.toUpperCase()); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java deleted file mode 100644 index 353f696fcf3f..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Logger.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * SDK logger that filters messages based on level and silent mode. - * - *

Silent by default — no log output unless explicitly configured. - * Create via {@link LogConfig} or directly: - *

{@code
- * Logger logger = new Logger(LogLevel.DEBUG, new ConsoleLogger(), false);
- * logger.debug("request sent");
- * }
- */ -public final class Logger { - - private static final Logger DEFAULT = new Logger(LogLevel.INFO, new ConsoleLogger(), true); - - private final LogLevel level; - private final ILogger logger; - private final boolean silent; - - public Logger(LogLevel level, ILogger logger, boolean silent) { - this.level = level; - this.logger = logger; - this.silent = silent; - } - - /** - * Returns a default silent logger (no output). - */ - public static Logger getDefault() { - return DEFAULT; - } - - /** - * Creates a Logger from a {@link LogConfig}. If config is {@code null}, returns the default silent logger. - */ - public static Logger from(LogConfig config) { - if (config == null) { - return DEFAULT; - } - return new Logger(config.level(), config.logger(), config.silent()); - } - - /** - * Creates a Logger from an {@code Optional}. If empty, returns the default silent logger. - */ - public static Logger from(java.util.Optional config) { - return config.map(Logger::from).orElse(DEFAULT); - } - - private boolean shouldLog(LogLevel messageLevel) { - return !silent && level.getValue() <= messageLevel.getValue(); - } - - public boolean isDebug() { - return shouldLog(LogLevel.DEBUG); - } - - public boolean isInfo() { - return shouldLog(LogLevel.INFO); - } - - public boolean isWarn() { - return shouldLog(LogLevel.WARN); - } - - public boolean isError() { - return shouldLog(LogLevel.ERROR); - } - - public void debug(String message) { - if (isDebug()) { - logger.debug(message); - } - } - - public void info(String message) { - if (isInfo()) { - logger.info(message); - } - } - - public void warn(String message) { - if (isWarn()) { - logger.warn(message); - } - } - - public void error(String message) { - if (isError()) { - logger.error(message); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java deleted file mode 100644 index 39a8eadeb905..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/LoggingInterceptor.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -/** - * OkHttp interceptor that logs HTTP requests and responses. - * - *

Logs request method, URL, and headers (with sensitive values redacted) at debug level. - * Logs response status at debug level, and 4xx/5xx responses at error level. - * Does nothing if the logger is silent. - */ -public final class LoggingInterceptor implements Interceptor { - - private static final Set SENSITIVE_HEADERS = new HashSet<>(Arrays.asList( - "authorization", - "www-authenticate", - "x-api-key", - "api-key", - "apikey", - "x-api-token", - "x-auth-token", - "auth-token", - "proxy-authenticate", - "proxy-authorization", - "cookie", - "set-cookie", - "x-csrf-token", - "x-xsrf-token", - "x-session-token", - "x-access-token")); - - private final Logger logger; - - public LoggingInterceptor(Logger logger) { - this.logger = logger; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - - if (logger.isDebug()) { - StringBuilder sb = new StringBuilder(); - sb.append("HTTP Request: ").append(request.method()).append(" ").append(request.url()); - sb.append(" headers={"); - boolean first = true; - for (String name : request.headers().names()) { - if (!first) { - sb.append(", "); - } - sb.append(name).append("="); - if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { - sb.append("[REDACTED]"); - } else { - sb.append(request.header(name)); - } - first = false; - } - sb.append("}"); - sb.append(" has_body=").append(request.body() != null); - logger.debug(sb.toString()); - } - - Response response = chain.proceed(request); - - if (logger.isDebug()) { - StringBuilder sb = new StringBuilder(); - sb.append("HTTP Response: status=").append(response.code()); - sb.append(" url=").append(response.request().url()); - sb.append(" headers={"); - boolean first = true; - for (String name : response.headers().names()) { - if (!first) { - sb.append(", "); - } - sb.append(name).append("="); - if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { - sb.append("[REDACTED]"); - } else { - sb.append(response.header(name)); - } - first = false; - } - sb.append("}"); - logger.debug(sb.toString()); - } - - if (response.code() >= 400 && logger.isError()) { - logger.error("HTTP Error: status=" + response.code() + " url=" - + response.request().url()); - } - - return response; - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java deleted file mode 100644 index 4a8d1cf301d4..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/MediaTypes.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import okhttp3.MediaType; - -public final class MediaTypes { - - public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); - - private MediaTypes() {} -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java deleted file mode 100644 index ca50b2d8d50a..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Nullable.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.Optional; -import java.util.function.Function; - -public final class Nullable { - - private final Either, Null> value; - - private Nullable() { - this.value = Either.left(Optional.empty()); - } - - private Nullable(T value) { - if (value == null) { - this.value = Either.right(Null.INSTANCE); - } else { - this.value = Either.left(Optional.of(value)); - } - } - - public static Nullable ofNull() { - return new Nullable<>(null); - } - - public static Nullable of(T value) { - return new Nullable<>(value); - } - - public static Nullable empty() { - return new Nullable<>(); - } - - public static Nullable ofOptional(Optional value) { - if (value.isPresent()) { - return of(value.get()); - } else { - return empty(); - } - } - - public boolean isNull() { - return this.value.isRight(); - } - - public boolean isEmpty() { - return this.value.isLeft() && !this.value.getLeft().isPresent(); - } - - public T get() { - if (this.isNull()) { - return null; - } - - return this.value.getLeft().get(); - } - - public Nullable map(Function mapper) { - if (this.isNull()) { - return Nullable.ofNull(); - } - - return Nullable.ofOptional(this.value.getLeft().map(mapper)); - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Nullable)) { - return false; - } - - if (((Nullable) other).isNull() && this.isNull()) { - return true; - } - - return this.value.getLeft().equals(((Nullable) other).value.getLeft()); - } - - private static final class Either { - private L left = null; - private R right = null; - - private Either(L left, R right) { - if (left != null && right != null) { - throw new IllegalArgumentException("Left and right argument cannot both be non-null."); - } - - if (left == null && right == null) { - throw new IllegalArgumentException("Left and right argument cannot both be null."); - } - - if (left != null) { - this.left = left; - } - - if (right != null) { - this.right = right; - } - } - - public static Either left(L left) { - return new Either<>(left, null); - } - - public static Either right(R right) { - return new Either<>(null, right); - } - - public boolean isLeft() { - return this.left != null; - } - - public boolean isRight() { - return this.right != null; - } - - public L getLeft() { - if (!this.isLeft()) { - throw new IllegalArgumentException("Cannot get left from right Either."); - } - return this.left; - } - - public R getRight() { - if (!this.isRight()) { - throw new IllegalArgumentException("Cannot get right from left Either."); - } - return this.right; - } - } - - private static final class Null { - private static final Null INSTANCE = new Null(); - - private Null() {} - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java deleted file mode 100644 index dea5d181d48c..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/NullableNonemptyFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.Optional; - -public final class NullableNonemptyFilter { - @Override - public boolean equals(Object o) { - boolean isOptionalEmpty = isOptionalEmpty(o); - - return isOptionalEmpty; - } - - private boolean isOptionalEmpty(Object o) { - if (o instanceof Optional) { - return !((Optional) o).isPresent(); - } - return false; - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java deleted file mode 100644 index 2e443b07d910..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ObjectMappers.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.io.IOException; - -public final class ObjectMappers { - public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() - .addModule(new Jdk8Module()) - .addModule(new JavaTimeModule()) - .addModule(DateTimeDeserializer.getModule()) - .addModule(DoubleSerializer.getModule()) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .build(); - - private ObjectMappers() {} - - public static String stringify(Object o) { - try { - return JSON_MAPPER - .setSerializationInclusion(JsonInclude.Include.ALWAYS) - .writerWithDefaultPrettyPrinter() - .writeValueAsString(o); - } catch (IOException e) { - return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); - } - } - - public static Object parseErrorBody(String responseBodyString) { - try { - return JSON_MAPPER.readValue(responseBodyString, Object.class); - } catch (JsonProcessingException ignored) { - return responseBodyString; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java deleted file mode 100644 index 3e364e6f3d5f..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/QueryStringMapper.java +++ /dev/null @@ -1,142 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import okhttp3.HttpUrl; -import okhttp3.MultipartBody; - -public class QueryStringMapper { - - private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; - - public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { - JsonNode valueNode = MAPPER.valueToTree(value); - - List> flat; - if (valueNode.isObject()) { - flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); - } else if (valueNode.isArray()) { - flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); - } else { - if (valueNode.isTextual()) { - httpUrl.addQueryParameter(key, valueNode.textValue()); - } else { - httpUrl.addQueryParameter(key, valueNode.toString()); - } - return; - } - - for (Map.Entry field : flat) { - if (field.getValue().isTextual()) { - httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); - } else { - httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); - } - } - } - - public static void addFormDataPart( - MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { - JsonNode valueNode = MAPPER.valueToTree(value); - - List> flat; - if (valueNode.isObject()) { - flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); - } else if (valueNode.isArray()) { - flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); - } else { - if (valueNode.isTextual()) { - multipartBody.addFormDataPart(key, valueNode.textValue()); - } else { - multipartBody.addFormDataPart(key, valueNode.toString()); - } - return; - } - - for (Map.Entry field : flat) { - if (field.getValue().isTextual()) { - multipartBody.addFormDataPart( - key + field.getKey(), field.getValue().textValue()); - } else { - multipartBody.addFormDataPart( - key + field.getKey(), field.getValue().toString()); - } - } - } - - public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { - List> flat = new ArrayList<>(); - - Iterator> fields = object.fields(); - while (fields.hasNext()) { - Map.Entry field = fields.next(); - - String key = "[" + field.getKey() + "]"; - - if (field.getValue().isObject()) { - List> flatField = - flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); - addAll(flat, flatField, key); - } else if (field.getValue().isArray()) { - List> flatField = - flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); - addAll(flat, flatField, ""); - } else { - flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); - } - } - - return flat; - } - - private static List> flattenArray( - ArrayNode array, String key, boolean arraysAsRepeats) { - List> flat = new ArrayList<>(); - - Iterator elements = array.elements(); - - int index = 0; - while (elements.hasNext()) { - JsonNode element = elements.next(); - - String indexKey = key + "[" + index + "]"; - - if (arraysAsRepeats) { - indexKey = key; - } - - if (element.isObject()) { - List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); - addAll(flat, flatField, indexKey); - } else if (element.isArray()) { - List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); - addAll(flat, flatField, indexKey); - } else { - flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); - } - - index++; - } - - return flat; - } - - private static void addAll( - List> target, List> source, String prefix) { - for (Map.Entry entry : source) { - Map.Entry entryToAdd = - new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); - target.add(entryToAdd); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java deleted file mode 100644 index cd95a27201b2..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/RequestOptions.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -public final class RequestOptions { - private final Optional timeout; - - private final TimeUnit timeoutTimeUnit; - - private final Map headers; - - private final Map> headerSuppliers; - - private final Map queryParameters; - - private final Map> queryParameterSuppliers; - - private RequestOptions( - Optional timeout, - TimeUnit timeoutTimeUnit, - Map headers, - Map> headerSuppliers, - Map queryParameters, - Map> queryParameterSuppliers) { - this.timeout = timeout; - this.timeoutTimeUnit = timeoutTimeUnit; - this.headers = headers; - this.headerSuppliers = headerSuppliers; - this.queryParameters = queryParameters; - this.queryParameterSuppliers = queryParameterSuppliers; - } - - public Optional getTimeout() { - return timeout; - } - - public TimeUnit getTimeoutTimeUnit() { - return timeoutTimeUnit; - } - - public Map getHeaders() { - Map headers = new HashMap<>(); - headers.putAll(this.headers); - this.headerSuppliers.forEach((key, supplier) -> { - headers.put(key, supplier.get()); - }); - return headers; - } - - public Map getQueryParameters() { - Map queryParameters = new HashMap<>(this.queryParameters); - this.queryParameterSuppliers.forEach((key, supplier) -> { - queryParameters.put(key, supplier.get()); - }); - return queryParameters; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Optional timeout = Optional.empty(); - - private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; - - private final Map headers = new HashMap<>(); - - private final Map> headerSuppliers = new HashMap<>(); - - private final Map queryParameters = new HashMap<>(); - - private final Map> queryParameterSuppliers = new HashMap<>(); - - public Builder timeout(Integer timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { - this.timeout = Optional.of(timeout); - this.timeoutTimeUnit = timeoutTimeUnit; - return this; - } - - public Builder addHeader(String key, String value) { - this.headers.put(key, value); - return this; - } - - public Builder addHeader(String key, Supplier value) { - this.headerSuppliers.put(key, value); - return this; - } - - public Builder addQueryParameter(String key, String value) { - this.queryParameters.put(key, value); - return this; - } - - public Builder addQueryParameter(String key, Supplier value) { - this.queryParameterSuppliers.put(key, value); - return this; - } - - public RequestOptions build() { - return new RequestOptions( - timeout, timeoutTimeUnit, headers, headerSuppliers, queryParameters, queryParameterSuppliers); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java deleted file mode 100644 index db05d538255d..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyInputStream.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.FilterInputStream; -import java.io.IOException; -import okhttp3.Response; - -/** - * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the - * OkHttp Response object is properly closed when the stream is closed. - * - * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. - * It retrieves the InputStream from the Response and overrides the close method to close - * both the InputStream and the Response object, ensuring proper resource management and preventing - * premature closure of the underlying HTTP connection. - */ -public class ResponseBodyInputStream extends FilterInputStream { - private final Response response; - - /** - * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp - * Response object. - * - * @param response the OkHttp Response object from which the InputStream is retrieved - * @throws IOException if an I/O error occurs while retrieving the InputStream - */ - public ResponseBodyInputStream(Response response) throws IOException { - super(response.body().byteStream()); - this.response = response; - } - - /** - * Closes the InputStream and the associated OkHttp Response object. This ensures that the - * underlying HTTP connection is properly closed after the stream is no longer needed. - * - * @throws IOException if an I/O error occurs - */ - @Override - public void close() throws IOException { - super.close(); - response.close(); // Ensure the response is closed when the stream is closed - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java deleted file mode 100644 index 97fcf7a0efbd..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/ResponseBodyReader.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.FilterReader; -import java.io.IOException; -import okhttp3.Response; - -/** - * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the - * OkHttp Response object is properly closed when the reader is closed. - * - * This class extends FilterReader and takes an OkHttp Response object as a parameter. - * It retrieves the Reader from the Response and overrides the close method to close - * both the Reader and the Response object, ensuring proper resource management and preventing - * premature closure of the underlying HTTP connection. - */ -public class ResponseBodyReader extends FilterReader { - private final Response response; - - /** - * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. - * - * @param response the OkHttp Response object from which the Reader is retrieved - * @throws IOException if an I/O error occurs while retrieving the Reader - */ - public ResponseBodyReader(Response response) throws IOException { - super(response.body().charStream()); - this.response = response; - } - - /** - * Closes the Reader and the associated OkHttp Response object. This ensures that the - * underlying HTTP connection is properly closed after the reader is no longer needed. - * - * @throws IOException if an I/O error occurs - */ - @Override - public void close() throws IOException { - super.close(); - response.close(); // Ensure the response is closed when the reader is closed - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java deleted file mode 100644 index 0d331c152477..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/RetryInterceptor.java +++ /dev/null @@ -1,181 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.IOException; -import java.time.Duration; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.Optional; -import java.util.Random; -import okhttp3.Interceptor; -import okhttp3.Response; - -public class RetryInterceptor implements Interceptor { - - private static final Duration INITIAL_RETRY_DELAY = Duration.ofMillis(1000); - private static final Duration MAX_RETRY_DELAY = Duration.ofMillis(60000); - private static final double JITTER_FACTOR = 0.2; - - private final int maxRetries; - private final Random random = new Random(); - - public RetryInterceptor(int maxRetries) { - this.maxRetries = maxRetries; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Response response = chain.proceed(chain.request()); - - if (shouldRetry(response.code())) { - return retryChain(response, chain); - } - - return response; - } - - private Response retryChain(Response response, Chain chain) throws IOException { - ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries); - Optional nextBackoff = backoff.nextBackoff(response); - while (nextBackoff.isPresent()) { - try { - Thread.sleep(nextBackoff.get().toMillis()); - } catch (InterruptedException e) { - throw new IOException("Interrupted while trying request", e); - } - response.close(); - response = chain.proceed(chain.request()); - if (shouldRetry(response.code())) { - nextBackoff = backoff.nextBackoff(response); - } else { - return response; - } - } - - return response; - } - - /** - * Calculates the retry delay from response headers, with fallback to exponential backoff. - * Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff - */ - private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) { - // Check for Retry-After header first (RFC 7231), with no jitter - String retryAfter = response.header("Retry-After"); - if (retryAfter != null) { - // Parse as number of seconds... - Optional secondsDelay = tryParseLong(retryAfter) - .map(seconds -> seconds * 1000) - .filter(delayMs -> delayMs > 0) - .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) - .map(Duration::ofMillis); - if (secondsDelay.isPresent()) { - return secondsDelay.get(); - } - - // ...or as an HTTP date; both are valid - Optional dateDelay = tryParseHttpDate(retryAfter) - .map(resetTime -> resetTime.toInstant().toEpochMilli() - System.currentTimeMillis()) - .filter(delayMs -> delayMs > 0) - .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) - .map(Duration::ofMillis); - if (dateDelay.isPresent()) { - return dateDelay.get(); - } - } - - // Then check for industry-standard X-RateLimit-Reset header, with positive jitter - String rateLimitReset = response.header("X-RateLimit-Reset"); - if (rateLimitReset != null) { - // Assume Unix timestamp in epoch seconds - Optional rateLimitDelay = tryParseLong(rateLimitReset) - .map(resetTimeSeconds -> (resetTimeSeconds * 1000) - System.currentTimeMillis()) - .filter(delayMs -> delayMs > 0) - .map(delayMs -> Math.min(delayMs, MAX_RETRY_DELAY.toMillis())) - .map(this::addPositiveJitter) - .map(Duration::ofMillis); - if (rateLimitDelay.isPresent()) { - return rateLimitDelay.get(); - } - } - - // Fall back to exponential backoff, with symmetric jitter - long baseDelay = INITIAL_RETRY_DELAY.toMillis() * (1L << retryAttempt); // 2^retryAttempt - long cappedDelay = Math.min(baseDelay, MAX_RETRY_DELAY.toMillis()); - return Duration.ofMillis(addSymmetricJitter(cappedDelay)); - } - - /** - * Attempts to parse a string as a long, returning empty Optional on failure. - */ - private Optional tryParseLong(String value) { - if (value == null) { - return Optional.empty(); - } - try { - return Optional.of(Long.parseLong(value)); - } catch (NumberFormatException e) { - return Optional.empty(); - } - } - - /** - * Attempts to parse a string as an HTTP date (RFC 1123), returning empty Optional on failure. - */ - private Optional tryParseHttpDate(String value) { - if (value == null) { - return Optional.empty(); - } - try { - return Optional.of(ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME)); - } catch (DateTimeParseException e) { - return Optional.empty(); - } - } - - /** - * Adds positive jitter (100-120% of original value) to prevent thundering herd. - * Used for X-RateLimit-Reset header delays. - */ - private long addPositiveJitter(long delayMs) { - double jitterMultiplier = 1.0 + (random.nextDouble() * JITTER_FACTOR); - return (long) (delayMs * jitterMultiplier); - } - - /** - * Adds symmetric jitter (90-110% of original value) to prevent thundering herd. - * Used for exponential backoff delays. - */ - private long addSymmetricJitter(long delayMs) { - double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * JITTER_FACTOR); - return (long) (delayMs * jitterMultiplier); - } - - private static boolean shouldRetry(int statusCode) { - return statusCode == 408 || statusCode == 429 || statusCode >= 500; - } - - private final class ExponentialBackoff { - - private final int maxNumRetries; - - private int retryNumber = 0; - - ExponentialBackoff(int maxNumRetries) { - this.maxNumRetries = maxNumRetries; - } - - public Optional nextBackoff(Response response) { - if (retryNumber >= maxNumRetries) { - return Optional.empty(); - } - - Duration delay = getRetryDelayFromHeaders(response, retryNumber); - retryNumber += 1; - return Optional.of(delay); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java deleted file mode 100644 index 268edd8c55be..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Rfc2822DateTimeDeserializer.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import java.io.IOException; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; - -/** - * Custom deserializer that handles converting RFC 2822 (RFC 1123) dates into {@link OffsetDateTime} objects. - * This is used for fields with format "date-time-rfc-2822", such as Twilio's dateCreated, dateSent, dateUpdated. - */ -public class Rfc2822DateTimeDeserializer extends JsonDeserializer { - - @Override - public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { - String raw = parser.getValueAsString(); - return ZonedDateTime.parse(raw, DateTimeFormatter.RFC_1123_DATE_TIME).toOffsetDateTime(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java deleted file mode 100644 index ab4985c15f96..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiApiException.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import okhttp3.Response; - -/** - * This exception type will be thrown for any non-2XX API responses. - */ -public class SeedApiApiException extends SeedApiException { - /** - * The error code of the response that triggered the exception. - */ - private final int statusCode; - - /** - * The body of the response that triggered the exception. - */ - private final Object body; - - private final Map> headers; - - public SeedApiApiException(String message, int statusCode, Object body) { - super(message); - this.statusCode = statusCode; - this.body = body; - this.headers = new HashMap<>(); - } - - public SeedApiApiException(String message, int statusCode, Object body, Response rawResponse) { - super(message); - this.statusCode = statusCode; - this.body = body; - this.headers = new HashMap<>(); - rawResponse.headers().forEach(header -> { - String key = header.component1(); - String value = header.component2(); - this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); - }); - } - - /** - * @return the statusCode - */ - public int statusCode() { - return this.statusCode; - } - - /** - * @return the body - */ - public Object body() { - return this.body; - } - - /** - * @return the headers - */ - public Map> headers() { - return this.headers; - } - - @Override - public String toString() { - return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " - + ObjectMappers.stringify(body) + "}"; - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java deleted file mode 100644 index cf8236066674..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiException.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -/** - * This class serves as the base exception for all errors in the SDK. - */ -public class SeedApiException extends RuntimeException { - public SeedApiException(String message) { - super(message); - } - - public SeedApiException(String message, Exception e) { - super(message, e); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java deleted file mode 100644 index 814561a79e08..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SeedApiHttpResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import okhttp3.Response; - -public final class SeedApiHttpResponse { - - private final T body; - - private final Map> headers; - - public SeedApiHttpResponse(T body, Response rawResponse) { - this.body = body; - - Map> headers = new HashMap<>(); - rawResponse.headers().forEach(header -> { - String key = header.component1(); - String value = header.component2(); - headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); - }); - this.headers = headers; - } - - public T body() { - return this.body; - } - - public Map> headers() { - return headers; - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java deleted file mode 100644 index 9775f2110865..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEvent.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Objects; -import java.util.Optional; - -/** - * Represents a Server-Sent Event with all standard fields. - * Used for event-level discrimination where the discriminator is at the SSE envelope level. - * - * @param The type of the data field - */ -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonIgnoreProperties(ignoreUnknown = true) -public final class SseEvent { - private final String event; - private final T data; - private final String id; - private final Long retry; - - private SseEvent(String event, T data, String id, Long retry) { - this.event = event; - this.data = data; - this.id = id; - this.retry = retry; - } - - @JsonProperty("event") - public Optional getEvent() { - return Optional.ofNullable(event); - } - - @JsonProperty("data") - public T getData() { - return data; - } - - @JsonProperty("id") - public Optional getId() { - return Optional.ofNullable(id); - } - - @JsonProperty("retry") - public Optional getRetry() { - return Optional.ofNullable(retry); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SseEvent sseEvent = (SseEvent) o; - return Objects.equals(event, sseEvent.event) - && Objects.equals(data, sseEvent.data) - && Objects.equals(id, sseEvent.id) - && Objects.equals(retry, sseEvent.retry); - } - - @Override - public int hashCode() { - return Objects.hash(event, data, id, retry); - } - - @Override - public String toString() { - return "SseEvent{" + "event='" - + event + '\'' + ", data=" - + data + ", id='" - + id + '\'' + ", retry=" - + retry + '}'; - } - - public static Builder builder() { - return new Builder<>(); - } - - public static final class Builder { - private String event; - private T data; - private String id; - private Long retry; - - private Builder() {} - - public Builder event(String event) { - this.event = event; - return this; - } - - public Builder data(T data) { - this.data = data; - return this; - } - - public Builder id(String id) { - this.id = id; - return this; - } - - public Builder retry(Long retry) { - this.retry = retry; - return this; - } - - public SseEvent build() { - return new SseEvent<>(event, data, id, retry); - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java deleted file mode 100644 index b8a888d0dda0..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/SseEventParser.java +++ /dev/null @@ -1,228 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.core.type.TypeReference; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -/** - * Utility class for parsing Server-Sent Events with support for discriminated unions. - *

- * Handles two discrimination patterns: - *

    - *
  1. Data-level discrimination: The discriminator (e.g., 'type') is inside the JSON data payload. - * Jackson's polymorphic deserialization handles this automatically.
  2. - *
  3. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE envelope level. - * This requires constructing the full SSE envelope for Jackson to process.
  4. - *
- */ -public final class SseEventParser { - - private static final Set SSE_ENVELOPE_FIELDS = new HashSet<>(Arrays.asList("event", "data", "id", "retry")); - - private SseEventParser() { - // Utility class - } - - /** - * Parse an SSE event using event-level discrimination. - *

- * Constructs the full SSE envelope object with event, data, id, and retry fields, - * then deserializes it to the target union type. - * - * @param eventType The SSE event type (from event: field) - * @param data The SSE data content (from data: field) - * @param id The SSE event ID (from id: field), may be null - * @param retry The SSE retry value (from retry: field), may be null - * @param unionClass The target union class - * @param discriminatorProperty The property name used for discrimination (e.g., "event") - * @param The target type - * @return The deserialized object - */ - public static T parseEventLevelUnion( - String eventType, String data, String id, Long retry, Class unionClass, String discriminatorProperty) { - try { - // Determine if data should be parsed as JSON based on the variant's expected type - Object parsedData = parseDataForVariant(eventType, data, unionClass, discriminatorProperty); - - // Construct the SSE envelope object - Map envelope = new HashMap<>(); - envelope.put(discriminatorProperty, eventType); - envelope.put("data", parsedData); - if (id != null) { - envelope.put("id", id); - } - if (retry != null) { - envelope.put("retry", retry); - } - - // Serialize to JSON and deserialize to target type - String envelopeJson = ObjectMappers.JSON_MAPPER.writeValueAsString(envelope); - return ObjectMappers.JSON_MAPPER.readValue(envelopeJson, unionClass); - } catch (Exception e) { - throw new RuntimeException("Failed to parse SSE event with event-level discrimination", e); - } - } - - /** - * Parse an SSE event using data-level discrimination. - *

- * Simply parses the data field as JSON and deserializes it to the target type. - * Jackson's polymorphic deserialization handles the discrimination automatically. - * - * @param data The SSE data content (from data: field) - * @param valueType The target type - * @param The target type - * @return The deserialized object - */ - public static T parseDataLevelUnion(String data, Class valueType) { - try { - return ObjectMappers.JSON_MAPPER.readValue(data, valueType); - } catch (Exception e) { - throw new RuntimeException("Failed to parse SSE data with data-level discrimination", e); - } - } - - /** - * Determines if the given discriminator property indicates event-level discrimination. - * Event-level discrimination occurs when the discriminator is an SSE envelope field. - * - * @param discriminatorProperty The discriminator property name - * @return true if event-level discrimination, false otherwise - */ - public static boolean isEventLevelDiscrimination(String discriminatorProperty) { - return SSE_ENVELOPE_FIELDS.contains(discriminatorProperty); - } - - /** - * Attempts to find the discriminator property from the union class's Jackson annotations. - * - * @param unionClass The union class to inspect - * @return The discriminator property name, or empty if not found - */ - public static Optional findDiscriminatorProperty(Class unionClass) { - try { - // Look for JsonTypeInfo on the class itself - JsonTypeInfo typeInfo = unionClass.getAnnotation(JsonTypeInfo.class); - if (typeInfo != null && !typeInfo.property().isEmpty()) { - return Optional.of(typeInfo.property()); - } - - // Look for inner Value interface with JsonTypeInfo - for (Class innerClass : unionClass.getDeclaredClasses()) { - typeInfo = innerClass.getAnnotation(JsonTypeInfo.class); - if (typeInfo != null && !typeInfo.property().isEmpty()) { - return Optional.of(typeInfo.property()); - } - } - } catch (Exception e) { - // Ignore reflection errors - } - return Optional.empty(); - } - - /** - * Parse the data field based on what the matching variant expects. - * If the variant expects a String for its data field, returns the raw string. - * Otherwise, parses the data as JSON. - */ - private static Object parseDataForVariant( - String eventType, String data, Class unionClass, String discriminatorProperty) { - if (data == null || data.isEmpty()) { - return data; - } - - try { - // Try to find the variant class that matches this event type - Class variantClass = findVariantClass(unionClass, eventType, discriminatorProperty); - if (variantClass != null) { - // Check if the variant expects a String for the data field - Field dataField = findField(variantClass, "data"); - if (dataField != null && String.class.equals(dataField.getType())) { - // Variant expects String - return raw data - return data; - } - } - - // Try to parse as JSON - return ObjectMappers.JSON_MAPPER.readValue(data, new TypeReference>() {}); - } catch (Exception e) { - // If JSON parsing fails, return as string - return data; - } - } - - /** - * Find the variant class that matches the given discriminator value. - */ - private static Class findVariantClass( - Class unionClass, String discriminatorValue, String discriminatorProperty) { - try { - // Look for JsonSubTypes annotation - JsonSubTypes subTypes = findJsonSubTypes(unionClass); - if (subTypes == null) { - return null; - } - - for (JsonSubTypes.Type subType : subTypes.value()) { - JsonTypeName typeName = subType.value().getAnnotation(JsonTypeName.class); - if (typeName != null && typeName.value().equals(discriminatorValue)) { - return subType.value(); - } - // Also check the name attribute of @JsonSubTypes.Type - if (subType.name().equals(discriminatorValue)) { - return subType.value(); - } - } - } catch (Exception e) { - // Ignore reflection errors - } - return null; - } - - /** - * Find JsonSubTypes annotation on the class or its inner classes. - */ - private static JsonSubTypes findJsonSubTypes(Class unionClass) { - // Check the class itself - JsonSubTypes subTypes = unionClass.getAnnotation(JsonSubTypes.class); - if (subTypes != null) { - return subTypes; - } - - // Check inner classes (for Fern-style unions with inner Value interface) - for (Class innerClass : unionClass.getDeclaredClasses()) { - subTypes = innerClass.getAnnotation(JsonSubTypes.class); - if (subTypes != null) { - return subTypes; - } - } - return null; - } - - /** - * Find a field by name in a class, including private fields. - */ - private static Field findField(Class clazz, String fieldName) { - try { - return clazz.getDeclaredField(fieldName); - } catch (NoSuchFieldException e) { - // Check superclass - Class superClass = clazz.getSuperclass(); - if (superClass != null && superClass != Object.class) { - return findField(superClass, fieldName); - } - return null; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java deleted file mode 100644 index b4350ef9a699..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Stream.java +++ /dev/null @@ -1,513 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.io.Closeable; -import java.io.IOException; -import java.io.Reader; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Scanner; - -/** - * The {@code Stream} class implements {@link Iterable} to provide a simple mechanism for reading and parsing - * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. - *

- * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a - * {@code Scanner} to block during iteration if the next object is not available. - * Iterable stream for parsing JSON and Server-Sent Events (SSE) data. - * Supports both newline-delimited JSON and SSE with optional stream termination. - * - * @param The type of objects in the stream. - */ -public final class Stream implements Iterable, Closeable { - - private static final String NEWLINE = "\n"; - private static final String DATA_PREFIX = "data:"; - - public enum StreamType { - JSON, - SSE, - SSE_EVENT_DISCRIMINATED - } - - private final Class valueType; - private final Scanner scanner; - private final StreamType streamType; - private final String messageTerminator; - private final String streamTerminator; - private final Reader sseReader; - private final String discriminatorProperty; - private boolean isClosed = false; - - /** - * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. - * - * @param valueType The class of the objects in the stream. - * @param reader The reader that provides the streamed data. - * @param delimiter The delimiter used to separate elements in the stream. - */ - public Stream(Class valueType, Reader reader, String delimiter) { - this.valueType = valueType; - this.scanner = new Scanner(reader).useDelimiter(delimiter); - this.streamType = StreamType.JSON; - this.messageTerminator = delimiter; - this.streamTerminator = null; - this.sseReader = null; - this.discriminatorProperty = null; - } - - private Stream(Class valueType, StreamType type, Reader reader, String terminator) { - this(valueType, type, reader, terminator, null); - } - - private Stream( - Class valueType, StreamType type, Reader reader, String terminator, String discriminatorProperty) { - this.valueType = valueType; - this.streamType = type; - this.discriminatorProperty = discriminatorProperty; - if (type == StreamType.JSON) { - this.scanner = new Scanner(reader).useDelimiter(terminator); - this.messageTerminator = terminator; - this.streamTerminator = null; - this.sseReader = null; - } else { - this.scanner = null; - this.messageTerminator = NEWLINE; - this.streamTerminator = terminator; - this.sseReader = reader; - } - } - - public static Stream fromJson(Class valueType, Reader reader, String delimiter) { - return new Stream<>(valueType, reader, delimiter); - } - - public static Stream fromJson(Class valueType, Reader reader) { - return new Stream<>(valueType, reader, NEWLINE); - } - - public static Stream fromSse(Class valueType, Reader sseReader) { - return new Stream<>(valueType, StreamType.SSE, sseReader, null); - } - - public static Stream fromSse(Class valueType, Reader sseReader, String streamTerminator) { - return new Stream<>(valueType, StreamType.SSE, sseReader, streamTerminator); - } - - /** - * Creates a stream from SSE data with event-level discrimination support. - * Use this when the SSE payload is a discriminated union where the discriminator - * is an SSE envelope field (e.g., 'event'). - * - * @param valueType The class of the objects in the stream. - * @param sseReader The reader that provides the SSE data. - * @param discriminatorProperty The property name used for discrimination (e.g., "event"). - * @param The type of objects in the stream. - * @return A new Stream instance configured for SSE with event-level discrimination. - */ - public static Stream fromSseWithEventDiscrimination( - Class valueType, Reader sseReader, String discriminatorProperty) { - return new Stream<>(valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, null, discriminatorProperty); - } - - /** - * Creates a stream from SSE data with event-level discrimination support and a stream terminator. - * - * @param valueType The class of the objects in the stream. - * @param sseReader The reader that provides the SSE data. - * @param discriminatorProperty The property name used for discrimination (e.g., "event"). - * @param streamTerminator The terminator string that signals end of stream (e.g., "[DONE]"). - * @param The type of objects in the stream. - * @return A new Stream instance configured for SSE with event-level discrimination. - */ - public static Stream fromSseWithEventDiscrimination( - Class valueType, Reader sseReader, String discriminatorProperty, String streamTerminator) { - return new Stream<>( - valueType, StreamType.SSE_EVENT_DISCRIMINATED, sseReader, streamTerminator, discriminatorProperty); - } - - @Override - public void close() throws IOException { - if (!isClosed) { - isClosed = true; - if (scanner != null) { - scanner.close(); - } - if (sseReader != null) { - sseReader.close(); - } - } - } - - private boolean isStreamClosed() { - return isClosed; - } - - /** - * Returns an iterator over the elements in this stream that blocks during iteration when the next object is - * not yet available. - * - * @return An iterator that can be used to traverse the elements in the stream. - */ - @Override - public Iterator iterator() { - switch (streamType) { - case SSE: - return new SSEIterator(); - case SSE_EVENT_DISCRIMINATED: - return new SSEEventDiscriminatedIterator(); - case JSON: - default: - return new JsonIterator(); - } - } - - private final class JsonIterator implements Iterator { - - /** - * Returns {@code true} if there are more elements in the stream. - *

- * Will block and wait for input if the stream has not ended and the next object is not yet available. - * - * @return {@code true} if there are more elements, {@code false} otherwise. - */ - @Override - public boolean hasNext() { - if (isStreamClosed()) { - return false; - } - return scanner.hasNext(); - } - - /** - * Returns the next element in the stream. - *

- * Will block and wait for input if the stream has not ended and the next object is not yet available. - * - * @return The next element in the stream. - * @throws NoSuchElementException If there are no more elements in the stream. - */ - @Override - public T next() { - if (isStreamClosed()) { - throw new NoSuchElementException("Stream is closed"); - } - - if (!scanner.hasNext()) { - throw new NoSuchElementException(); - } else { - try { - T parsedResponse = - ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); - return parsedResponse; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - private final class SSEIterator implements Iterator { - private Scanner sseScanner; - private T nextItem; - private boolean hasNextItem = false; - private boolean endOfStream = false; - private StringBuilder eventDataBuffer = new StringBuilder(); - private String currentEventType = null; - - private SSEIterator() { - if (sseReader != null && !isStreamClosed()) { - this.sseScanner = new Scanner(sseReader); - } else { - this.endOfStream = true; - } - } - - @Override - public boolean hasNext() { - if (isStreamClosed() || endOfStream) { - return false; - } - - if (hasNextItem) { - return true; - } - - return readNextMessage(); - } - - @Override - public T next() { - if (!hasNext()) { - throw new NoSuchElementException("No more elements in stream"); - } - - T result = nextItem; - nextItem = null; - hasNextItem = false; - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - private boolean readNextMessage() { - if (sseScanner == null || isStreamClosed()) { - endOfStream = true; - return false; - } - - try { - while (sseScanner.hasNextLine()) { - String line = sseScanner.nextLine(); - - if (line.trim().isEmpty()) { - if (eventDataBuffer.length() > 0) { - try { - nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); - hasNextItem = true; - eventDataBuffer.setLength(0); - currentEventType = null; - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); - eventDataBuffer.setLength(0); - currentEventType = null; - continue; - } - } - continue; - } - - if (line.startsWith(DATA_PREFIX)) { - String dataContent = line.substring(DATA_PREFIX.length()); - if (dataContent.startsWith(" ")) { - dataContent = dataContent.substring(1); - } - - if (eventDataBuffer.length() == 0 - && streamTerminator != null - && dataContent.trim().equals(streamTerminator)) { - endOfStream = true; - return false; - } - - if (eventDataBuffer.length() > 0) { - eventDataBuffer.append('\n'); - } - eventDataBuffer.append(dataContent); - } else if (line.startsWith("event:")) { - String eventValue = line.length() > 6 ? line.substring(6) : ""; - if (eventValue.startsWith(" ")) { - eventValue = eventValue.substring(1); - } - currentEventType = eventValue; - } else if (line.startsWith("id:")) { - // Event ID field (ignored) - } else if (line.startsWith("retry:")) { - // Retry field (ignored) - } else if (line.startsWith(":")) { - // Comment line (ignored) - } - } - - if (eventDataBuffer.length() > 0) { - try { - nextItem = ObjectMappers.JSON_MAPPER.readValue(eventDataBuffer.toString(), valueType); - hasNextItem = true; - eventDataBuffer.setLength(0); - currentEventType = null; - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); - eventDataBuffer.setLength(0); - currentEventType = null; - } - } - - endOfStream = true; - return false; - - } catch (Exception e) { - System.err.println("Failed to parse SSE stream: " + e.getMessage()); - endOfStream = true; - return false; - } - } - } - - /** - * Iterator for SSE streams with event-level discrimination. - * Uses SseEventParser to construct the full SSE envelope for Jackson deserialization. - */ - private final class SSEEventDiscriminatedIterator implements Iterator { - private Scanner sseScanner; - private T nextItem; - private boolean hasNextItem = false; - private boolean endOfStream = false; - private StringBuilder eventDataBuffer = new StringBuilder(); - private String currentEventType = null; - private String currentEventId = null; - private Long currentRetry = null; - - private SSEEventDiscriminatedIterator() { - if (sseReader != null && !isStreamClosed()) { - this.sseScanner = new Scanner(sseReader); - } else { - this.endOfStream = true; - } - } - - @Override - public boolean hasNext() { - if (isStreamClosed() || endOfStream) { - return false; - } - - if (hasNextItem) { - return true; - } - - return readNextMessage(); - } - - @Override - public T next() { - if (!hasNext()) { - throw new NoSuchElementException("No more elements in stream"); - } - - T result = nextItem; - nextItem = null; - hasNextItem = false; - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - private boolean readNextMessage() { - if (sseScanner == null || isStreamClosed()) { - endOfStream = true; - return false; - } - - try { - while (sseScanner.hasNextLine()) { - String line = sseScanner.nextLine(); - - if (line.trim().isEmpty()) { - if (eventDataBuffer.length() > 0 || currentEventType != null) { - try { - // Use SseEventParser for event-level discrimination - nextItem = SseEventParser.parseEventLevelUnion( - currentEventType, - eventDataBuffer.toString(), - currentEventId, - currentRetry, - valueType, - discriminatorProperty); - hasNextItem = true; - resetEventState(); - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse SSE event: " + parseEx.getMessage()); - resetEventState(); - continue; - } - } - continue; - } - - if (line.startsWith(DATA_PREFIX)) { - String dataContent = line.substring(DATA_PREFIX.length()); - if (dataContent.startsWith(" ")) { - dataContent = dataContent.substring(1); - } - - if (eventDataBuffer.length() == 0 - && streamTerminator != null - && dataContent.trim().equals(streamTerminator)) { - endOfStream = true; - return false; - } - - if (eventDataBuffer.length() > 0) { - eventDataBuffer.append('\n'); - } - eventDataBuffer.append(dataContent); - } else if (line.startsWith("event:")) { - String eventValue = line.length() > 6 ? line.substring(6) : ""; - if (eventValue.startsWith(" ")) { - eventValue = eventValue.substring(1); - } - currentEventType = eventValue; - } else if (line.startsWith("id:")) { - String idValue = line.length() > 3 ? line.substring(3) : ""; - if (idValue.startsWith(" ")) { - idValue = idValue.substring(1); - } - currentEventId = idValue; - } else if (line.startsWith("retry:")) { - String retryValue = line.length() > 6 ? line.substring(6) : ""; - if (retryValue.startsWith(" ")) { - retryValue = retryValue.substring(1); - } - try { - currentRetry = Long.parseLong(retryValue.trim()); - } catch (NumberFormatException e) { - // Ignore invalid retry values - } - } else if (line.startsWith(":")) { - // Comment line (ignored) - } - } - - // Handle any remaining buffered data at end of stream - if (eventDataBuffer.length() > 0 || currentEventType != null) { - try { - nextItem = SseEventParser.parseEventLevelUnion( - currentEventType, - eventDataBuffer.toString(), - currentEventId, - currentRetry, - valueType, - discriminatorProperty); - hasNextItem = true; - resetEventState(); - return true; - } catch (Exception parseEx) { - System.err.println("Failed to parse final SSE event: " + parseEx.getMessage()); - resetEventState(); - } - } - - endOfStream = true; - return false; - - } catch (Exception e) { - System.err.println("Failed to parse SSE stream: " + e.getMessage()); - endOfStream = true; - return false; - } - } - - private void resetEventState() { - eventDataBuffer.setLength(0); - currentEventType = null; - currentEventId = null; - currentRetry = null; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java deleted file mode 100644 index a3c24e968576..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/core/Suppliers.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -public final class Suppliers { - private Suppliers() {} - - public static Supplier memoize(Supplier delegate) { - AtomicReference value = new AtomicReference<>(); - return () -> { - T val = value.get(); - if (val == null) { - val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); - } - return val; - }; - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java b/seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java deleted file mode 100644 index 0f39ad2d3cf4..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/requests/RuleCreateRequest.java +++ /dev/null @@ -1,142 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.requests; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import com.seed.api.types.RuleExecutionContext; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleCreateRequest.Builder.class) -public final class RuleCreateRequest { - private final String name; - - private final RuleExecutionContext executionContext; - - private final Map additionalProperties; - - private RuleCreateRequest( - String name, RuleExecutionContext executionContext, Map additionalProperties) { - this.name = name; - this.executionContext = executionContext; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("executionContext") - public RuleExecutionContext getExecutionContext() { - return executionContext; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleCreateRequest && equalTo((RuleCreateRequest) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleCreateRequest other) { - return name.equals(other.name) && executionContext.equals(other.executionContext); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.executionContext); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NameStage builder() { - return new Builder(); - } - - public interface NameStage { - ExecutionContextStage name(@NotNull String name); - - Builder from(RuleCreateRequest other); - } - - public interface ExecutionContextStage { - _FinalStage executionContext(@NotNull RuleExecutionContext executionContext); - } - - public interface _FinalStage { - RuleCreateRequest build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements NameStage, ExecutionContextStage, _FinalStage { - private String name; - - private RuleExecutionContext executionContext; - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleCreateRequest other) { - name(other.getName()); - executionContext(other.getExecutionContext()); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public ExecutionContextStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("executionContext") - public _FinalStage executionContext(@NotNull RuleExecutionContext executionContext) { - this.executionContext = Objects.requireNonNull(executionContext, "executionContext must not be null"); - return this; - } - - @java.lang.Override - public RuleCreateRequest build() { - return new RuleCreateRequest(name, executionContext, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java b/seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java deleted file mode 100644 index 80659000defc..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/requests/SearchRuleTypesRequest.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.requests; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SearchRuleTypesRequest.Builder.class) -public final class SearchRuleTypesRequest { - private final Optional query; - - private final Map additionalProperties; - - private SearchRuleTypesRequest(Optional query, Map additionalProperties) { - this.query = query; - this.additionalProperties = additionalProperties; - } - - @JsonIgnore - public Optional getQuery() { - return query; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SearchRuleTypesRequest && equalTo((SearchRuleTypesRequest) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SearchRuleTypesRequest other) { - return query.equals(other.query); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.query); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional query = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SearchRuleTypesRequest other) { - query(other.getQuery()); - return this; - } - - @JsonSetter(value = "query", nulls = Nulls.SKIP) - public Builder query(Optional query) { - this.query = query; - return this; - } - - public Builder query(String query) { - this.query = Optional.ofNullable(query); - return this; - } - - public SearchRuleTypesRequest build() { - return new SearchRuleTypesRequest(query, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java deleted file mode 100644 index 2502167c781a..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/AuditInfo.java +++ /dev/null @@ -1,208 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.time.OffsetDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AuditInfo.Builder.class) -public final class AuditInfo implements IAuditInfo { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private final Map additionalProperties; - - private AuditInfo( - Optional createdBy, - Optional createdDateTime, - Optional modifiedBy, - Optional modifiedDateTime, - Map additionalProperties) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - this.additionalProperties = additionalProperties; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - @java.lang.Override - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - @java.lang.Override - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - @java.lang.Override - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - @java.lang.Override - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AuditInfo && equalTo((AuditInfo) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(AuditInfo other) { - return createdBy.equals(other.createdBy) - && createdDateTime.equals(other.createdDateTime) - && modifiedBy.equals(other.modifiedBy) - && modifiedDateTime.equals(other.modifiedDateTime); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.createdBy, this.createdDateTime, this.modifiedBy, this.modifiedDateTime); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional createdBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AuditInfo other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - return this; - } - - /** - *

The user who created this resource.

- */ - @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) - public Builder createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - public Builder createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

When this resource was created.

- */ - @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) - public Builder createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - public Builder createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) - public Builder modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - public Builder modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

When this resource was last modified.

- */ - @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) - public Builder modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - public Builder modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - public AuditInfo build() { - return new AuditInfo(createdBy, createdDateTime, modifiedBy, modifiedDateTime, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java deleted file mode 100644 index 2ff2e67bb3b4..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrg.java +++ /dev/null @@ -1,148 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = BaseOrg.Builder.class) -public final class BaseOrg { - private final String id; - - private final Optional metadata; - - private final Map additionalProperties; - - private BaseOrg(String id, Optional metadata, Map additionalProperties) { - this.id = id; - this.metadata = metadata; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrg && equalTo((BaseOrg) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(BaseOrg other) { - return id.equals(other.id) && metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - _FinalStage id(@NotNull String id); - - Builder from(BaseOrg other); - } - - public interface _FinalStage { - BaseOrg build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(BaseOrgMetadata metadata); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional metadata = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(BaseOrg other) { - id(other.getId()); - metadata(other.getMetadata()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(BaseOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter(value = "metadata", nulls = Nulls.SKIP) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public BaseOrg build() { - return new BaseOrg(id, metadata, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java deleted file mode 100644 index a444b87ccc24..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/BaseOrgMetadata.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = BaseOrgMetadata.Builder.class) -public final class BaseOrgMetadata { - private final String region; - - private final Optional tier; - - private final Map additionalProperties; - - private BaseOrgMetadata(String region, Optional tier, Map additionalProperties) { - this.region = region; - this.tier = tier; - this.additionalProperties = additionalProperties; - } - - /** - * @return Deployment region from BaseOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Subscription tier. - */ - @JsonProperty("tier") - public Optional getTier() { - return tier; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof BaseOrgMetadata && equalTo((BaseOrgMetadata) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(BaseOrgMetadata other) { - return region.equals(other.region) && tier.equals(other.tier); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.tier); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from BaseOrg.

- */ - _FinalStage region(@NotNull String region); - - Builder from(BaseOrgMetadata other); - } - - public interface _FinalStage { - BaseOrgMetadata build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Subscription tier.

- */ - _FinalStage tier(Optional tier); - - _FinalStage tier(String tier); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional tier = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(BaseOrgMetadata other) { - region(other.getRegion()); - tier(other.getTier()); - return this; - } - - /** - *

Deployment region from BaseOrg.

- *

Deployment region from BaseOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(@NotNull String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Subscription tier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage tier(String tier) { - this.tier = Optional.ofNullable(tier); - return this; - } - - /** - *

Subscription tier.

- */ - @java.lang.Override - @JsonSetter(value = "tier", nulls = Nulls.SKIP) - public _FinalStage tier(Optional tier) { - this.tier = tier; - return this; - } - - @java.lang.Override - public BaseOrgMetadata build() { - return new BaseOrgMetadata(region, tier, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java deleted file mode 100644 index ca2464fede5e..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntity.java +++ /dev/null @@ -1,243 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = CombinedEntity.Builder.class) -public final class CombinedEntity { - private final CombinedEntityStatus status; - - private final String id; - - private final Optional name; - - private final Optional summary; - - private final Map additionalProperties; - - private CombinedEntity( - CombinedEntityStatus status, - String id, - Optional name, - Optional summary, - Map additionalProperties) { - this.status = status; - this.id = id; - this.name = name; - this.summary = summary; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("status") - public CombinedEntityStatus getStatus() { - return status; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Identifiable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof CombinedEntity && equalTo((CombinedEntity) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(CombinedEntity other) { - return status.equals(other.status) - && id.equals(other.id) - && name.equals(other.name) - && summary.equals(other.summary); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.status, this.id, this.name, this.summary); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static StatusStage builder() { - return new Builder(); - } - - public interface StatusStage { - IdStage status(@NotNull CombinedEntityStatus status); - - Builder from(CombinedEntity other); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - _FinalStage id(@NotNull String id); - } - - public interface _FinalStage { - CombinedEntity build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Display name from Identifiable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - - /** - *

A short summary.

- */ - _FinalStage summary(Optional summary); - - _FinalStage summary(String summary); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements StatusStage, IdStage, _FinalStage { - private CombinedEntityStatus status; - - private String id; - - private Optional summary = Optional.empty(); - - private Optional name = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(CombinedEntity other) { - status(other.getStatus()); - id(other.getId()); - name(other.getName()); - summary(other.getSummary()); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public IdStage status(@NotNull CombinedEntityStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - /** - *

A short summary.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - /** - *

A short summary.

- */ - @java.lang.Override - @JsonSetter(value = "summary", nulls = Nulls.SKIP) - public _FinalStage summary(Optional summary) { - this.summary = summary; - return this; - } - - /** - *

Display name from Identifiable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Identifiable.

- */ - @java.lang.Override - @JsonSetter(value = "name", nulls = Nulls.SKIP) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public CombinedEntity build() { - return new CombinedEntity(status, id, name, summary, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java deleted file mode 100644 index 86e82fb63f85..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/CombinedEntityStatus.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class CombinedEntityStatus { - public static final CombinedEntityStatus ARCHIVED = new CombinedEntityStatus(Value.ARCHIVED, "archived"); - - public static final CombinedEntityStatus ACTIVE = new CombinedEntityStatus(Value.ACTIVE, "active"); - - private final Value value; - - private final String string; - - CombinedEntityStatus(Value value, String string) { - this.value = value; - this.string = string; - } - - public Value getEnumValue() { - return value; - } - - @java.lang.Override - @JsonValue - public String toString() { - return this.string; - } - - @java.lang.Override - public boolean equals(Object other) { - return (this == other) - || (other instanceof CombinedEntityStatus && this.string.equals(((CombinedEntityStatus) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case ARCHIVED: - return visitor.visitArchived(); - case ACTIVE: - return visitor.visitActive(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static CombinedEntityStatus valueOf(String value) { - switch (value) { - case "archived": - return ARCHIVED; - case "active": - return ACTIVE; - default: - return new CombinedEntityStatus(Value.UNKNOWN, value); - } - } - - public enum Value { - ACTIVE, - - ARCHIVED, - - UNKNOWN - } - - public interface Visitor { - T visitActive(); - - T visitArchived(); - - T visitUnknown(String unknownType); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java deleted file mode 100644 index b16e91712b0e..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Describable.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = Describable.Builder.class) -public final class Describable { - private final Optional name; - - private final Optional summary; - - private final Map additionalProperties; - - private Describable(Optional name, Optional summary, Map additionalProperties) { - this.name = name; - this.summary = summary; - this.additionalProperties = additionalProperties; - } - - /** - * @return Display name from Describable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - /** - * @return A short summary. - */ - @JsonProperty("summary") - public Optional getSummary() { - return summary; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Describable && equalTo((Describable) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(Describable other) { - return name.equals(other.name) && summary.equals(other.summary); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.summary); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional name = Optional.empty(); - - private Optional summary = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(Describable other) { - name(other.getName()); - summary(other.getSummary()); - return this; - } - - /** - *

Display name from Describable.

- */ - @JsonSetter(value = "name", nulls = Nulls.SKIP) - public Builder name(Optional name) { - this.name = name; - return this; - } - - public Builder name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

A short summary.

- */ - @JsonSetter(value = "summary", nulls = Nulls.SKIP) - public Builder summary(Optional summary) { - this.summary = summary; - return this; - } - - public Builder summary(String summary) { - this.summary = Optional.ofNullable(summary); - return this; - } - - public Describable build() { - return new Describable(name, summary, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java deleted file mode 100644 index ef605e7427da..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrg.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = DetailedOrg.Builder.class) -public final class DetailedOrg { - private final Optional metadata; - - private final Map additionalProperties; - - private DetailedOrg(Optional metadata, Map additionalProperties) { - this.metadata = metadata; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrg && equalTo((DetailedOrg) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(DetailedOrg other) { - return metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional metadata = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(DetailedOrg other) { - metadata(other.getMetadata()); - return this; - } - - @JsonSetter(value = "metadata", nulls = Nulls.SKIP) - public Builder metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - public Builder metadata(DetailedOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - public DetailedOrg build() { - return new DetailedOrg(metadata, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java deleted file mode 100644 index e65ef4975e85..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/DetailedOrgMetadata.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = DetailedOrgMetadata.Builder.class) -public final class DetailedOrgMetadata { - private final String region; - - private final Optional domain; - - private final Map additionalProperties; - - private DetailedOrgMetadata(String region, Optional domain, Map additionalProperties) { - this.region = region; - this.domain = domain; - this.additionalProperties = additionalProperties; - } - - /** - * @return Deployment region from DetailedOrg. - */ - @JsonProperty("region") - public String getRegion() { - return region; - } - - /** - * @return Custom domain name. - */ - @JsonProperty("domain") - public Optional getDomain() { - return domain; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DetailedOrgMetadata && equalTo((DetailedOrgMetadata) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(DetailedOrgMetadata other) { - return region.equals(other.region) && domain.equals(other.domain); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.region, this.domain); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static RegionStage builder() { - return new Builder(); - } - - public interface RegionStage { - /** - *

Deployment region from DetailedOrg.

- */ - _FinalStage region(@NotNull String region); - - Builder from(DetailedOrgMetadata other); - } - - public interface _FinalStage { - DetailedOrgMetadata build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Custom domain name.

- */ - _FinalStage domain(Optional domain); - - _FinalStage domain(String domain); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements RegionStage, _FinalStage { - private String region; - - private Optional domain = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(DetailedOrgMetadata other) { - region(other.getRegion()); - domain(other.getDomain()); - return this; - } - - /** - *

Deployment region from DetailedOrg.

- *

Deployment region from DetailedOrg.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("region") - public _FinalStage region(@NotNull String region) { - this.region = Objects.requireNonNull(region, "region must not be null"); - return this; - } - - /** - *

Custom domain name.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage domain(String domain) { - this.domain = Optional.ofNullable(domain); - return this; - } - - /** - *

Custom domain name.

- */ - @java.lang.Override - @JsonSetter(value = "domain", nulls = Nulls.SKIP) - public _FinalStage domain(Optional domain) { - this.domain = domain; - return this; - } - - @java.lang.Override - public DetailedOrgMetadata build() { - return new DetailedOrgMetadata(region, domain, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java deleted file mode 100644 index 3270f1b1de90..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/IAuditInfo.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import java.time.OffsetDateTime; -import java.util.Optional; - -public interface IAuditInfo { - Optional getCreatedBy(); - - Optional getCreatedDateTime(); - - Optional getModifiedBy(); - - Optional getModifiedDateTime(); -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java deleted file mode 100644 index 8a35f505a9bf..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Identifiable.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = Identifiable.Builder.class) -public final class Identifiable { - private final String id; - - private final Optional name; - - private final Map additionalProperties; - - private Identifiable(String id, Optional name, Map additionalProperties) { - this.id = id; - this.name = name; - this.additionalProperties = additionalProperties; - } - - /** - * @return Unique identifier. - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Display name from Identifiable. - */ - @JsonProperty("name") - public Optional getName() { - return name; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Identifiable && equalTo((Identifiable) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(Identifiable other) { - return id.equals(other.id) && name.equals(other.name); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - /** - *

Unique identifier.

- */ - _FinalStage id(@NotNull String id); - - Builder from(Identifiable other); - } - - public interface _FinalStage { - Identifiable build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Display name from Identifiable.

- */ - _FinalStage name(Optional name); - - _FinalStage name(String name); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, _FinalStage { - private String id; - - private Optional name = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(Identifiable other) { - id(other.getId()); - name(other.getName()); - return this; - } - - /** - *

Unique identifier.

- *

Unique identifier.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - /** - *

Display name from Identifiable.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage name(String name) { - this.name = Optional.ofNullable(name); - return this; - } - - /** - *

Display name from Identifiable.

- */ - @java.lang.Override - @JsonSetter(value = "name", nulls = Nulls.SKIP) - public _FinalStage name(Optional name) { - this.name = name; - return this; - } - - @java.lang.Override - public Identifiable build() { - return new Identifiable(id, name, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java deleted file mode 100644 index 8cf63af62d4b..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/Organization.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = Organization.Builder.class) -public final class Organization { - private final String name; - - private final String id; - - private final Optional metadata; - - private final Map additionalProperties; - - private Organization( - String name, String id, Optional metadata, Map additionalProperties) { - this.name = name; - this.id = id; - this.metadata = metadata; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("metadata") - public Optional getMetadata() { - return metadata; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof Organization && equalTo((Organization) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(Organization other) { - return name.equals(other.name) && id.equals(other.id) && metadata.equals(other.metadata); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.name, this.id, this.metadata); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NameStage builder() { - return new Builder(); - } - - public interface NameStage { - IdStage name(@NotNull String name); - - Builder from(Organization other); - } - - public interface IdStage { - _FinalStage id(@NotNull String id); - } - - public interface _FinalStage { - Organization build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - _FinalStage metadata(Optional metadata); - - _FinalStage metadata(BaseOrgMetadata metadata); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements NameStage, IdStage, _FinalStage { - private String name; - - private String id; - - private Optional metadata = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(Organization other) { - name(other.getName()); - id(other.getId()); - metadata(other.getMetadata()); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public IdStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public _FinalStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage metadata(BaseOrgMetadata metadata) { - this.metadata = Optional.ofNullable(metadata); - return this; - } - - @java.lang.Override - @JsonSetter(value = "metadata", nulls = Nulls.SKIP) - public _FinalStage metadata(Optional metadata) { - this.metadata = metadata; - return this; - } - - @java.lang.Override - public Organization build() { - return new Organization(name, id, metadata, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java deleted file mode 100644 index 2994a6d8c2fd..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/PaginatedResult.java +++ /dev/null @@ -1,179 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = PaginatedResult.Builder.class) -public final class PaginatedResult { - private final PagingCursors paging; - - private final List results; - - private final Map additionalProperties; - - private PaginatedResult(PagingCursors paging, List results, Map additionalProperties) { - this.paging = paging; - this.results = results; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public List getResults() { - return results; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PaginatedResult && equalTo((PaginatedResult) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(PaginatedResult other) { - return paging.equals(other.paging) && results.equals(other.results); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.paging, this.results); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(@NotNull PagingCursors paging); - - Builder from(PaginatedResult other); - } - - public interface _FinalStage { - PaginatedResult build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(List results); - - _FinalStage addResults(Object results); - - _FinalStage addAllResults(List results); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private List results = new ArrayList<>(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(PaginatedResult other) { - paging(other.getPaging()); - results(other.getResults()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(@NotNull PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addAllResults(List results) { - if (results != null) { - this.results.addAll(results); - } - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage addResults(Object results) { - this.results.add(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public _FinalStage results(List results) { - this.results.clear(); - if (results != null) { - this.results.addAll(results); - } - return this; - } - - @java.lang.Override - public PaginatedResult build() { - return new PaginatedResult(paging, results, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java deleted file mode 100644 index 5453abc20ef3..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/PagingCursors.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = PagingCursors.Builder.class) -public final class PagingCursors { - private final String next; - - private final Optional previous; - - private final Map additionalProperties; - - private PagingCursors(String next, Optional previous, Map additionalProperties) { - this.next = next; - this.previous = previous; - this.additionalProperties = additionalProperties; - } - - /** - * @return Cursor for the next page of results. - */ - @JsonProperty("next") - public String getNext() { - return next; - } - - /** - * @return Cursor for the previous page of results. - */ - @JsonProperty("previous") - public Optional getPrevious() { - return previous; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof PagingCursors && equalTo((PagingCursors) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(PagingCursors other) { - return next.equals(other.next) && previous.equals(other.previous); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.next, this.previous); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static NextStage builder() { - return new Builder(); - } - - public interface NextStage { - /** - *

Cursor for the next page of results.

- */ - _FinalStage next(@NotNull String next); - - Builder from(PagingCursors other); - } - - public interface _FinalStage { - PagingCursors build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Cursor for the previous page of results.

- */ - _FinalStage previous(Optional previous); - - _FinalStage previous(String previous); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements NextStage, _FinalStage { - private String next; - - private Optional previous = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(PagingCursors other) { - next(other.getNext()); - previous(other.getPrevious()); - return this; - } - - /** - *

Cursor for the next page of results.

- *

Cursor for the next page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("next") - public _FinalStage next(@NotNull String next) { - this.next = Objects.requireNonNull(next, "next must not be null"); - return this; - } - - /** - *

Cursor for the previous page of results.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage previous(String previous) { - this.previous = Optional.ofNullable(previous); - return this; - } - - /** - *

Cursor for the previous page of results.

- */ - @java.lang.Override - @JsonSetter(value = "previous", nulls = Nulls.SKIP) - public _FinalStage previous(Optional previous) { - this.previous = previous; - return this; - } - - @java.lang.Override - public PagingCursors build() { - return new PagingCursors(next, previous, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java deleted file mode 100644 index 46c429ed089e..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleExecutionContext.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class RuleExecutionContext { - public static final RuleExecutionContext PROD = new RuleExecutionContext(Value.PROD, "prod"); - - public static final RuleExecutionContext DEV = new RuleExecutionContext(Value.DEV, "dev"); - - public static final RuleExecutionContext STAGING = new RuleExecutionContext(Value.STAGING, "staging"); - - private final Value value; - - private final String string; - - RuleExecutionContext(Value value, String string) { - this.value = value; - this.string = string; - } - - public Value getEnumValue() { - return value; - } - - @java.lang.Override - @JsonValue - public String toString() { - return this.string; - } - - @java.lang.Override - public boolean equals(Object other) { - return (this == other) - || (other instanceof RuleExecutionContext && this.string.equals(((RuleExecutionContext) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case PROD: - return visitor.visitProd(); - case DEV: - return visitor.visitDev(); - case STAGING: - return visitor.visitStaging(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static RuleExecutionContext valueOf(String value) { - switch (value) { - case "prod": - return PROD; - case "dev": - return DEV; - case "staging": - return STAGING; - default: - return new RuleExecutionContext(Value.UNKNOWN, value); - } - } - - public enum Value { - PROD, - - STAGING, - - DEV, - - UNKNOWN - } - - public interface Visitor { - T visitProd(); - - T visitStaging(); - - T visitDev(); - - T visitUnknown(String unknownType); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java deleted file mode 100644 index 78c325f9465a..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponse.java +++ /dev/null @@ -1,394 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.time.OffsetDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleResponse.Builder.class) -public final class RuleResponse implements IAuditInfo { - private final Optional createdBy; - - private final Optional createdDateTime; - - private final Optional modifiedBy; - - private final Optional modifiedDateTime; - - private final String id; - - private final String name; - - private final RuleResponseStatus status; - - private final Optional executionContext; - - private final Map additionalProperties; - - private RuleResponse( - Optional createdBy, - Optional createdDateTime, - Optional modifiedBy, - Optional modifiedDateTime, - String id, - String name, - RuleResponseStatus status, - Optional executionContext, - Map additionalProperties) { - this.createdBy = createdBy; - this.createdDateTime = createdDateTime; - this.modifiedBy = modifiedBy; - this.modifiedDateTime = modifiedDateTime; - this.id = id; - this.name = name; - this.status = status; - this.executionContext = executionContext; - this.additionalProperties = additionalProperties; - } - - /** - * @return The user who created this resource. - */ - @JsonProperty("createdBy") - @java.lang.Override - public Optional getCreatedBy() { - return createdBy; - } - - /** - * @return When this resource was created. - */ - @JsonProperty("createdDateTime") - @java.lang.Override - public Optional getCreatedDateTime() { - return createdDateTime; - } - - /** - * @return The user who last modified this resource. - */ - @JsonProperty("modifiedBy") - @java.lang.Override - public Optional getModifiedBy() { - return modifiedBy; - } - - /** - * @return When this resource was last modified. - */ - @JsonProperty("modifiedDateTime") - @java.lang.Override - public Optional getModifiedDateTime() { - return modifiedDateTime; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("status") - public RuleResponseStatus getStatus() { - return status; - } - - @JsonProperty("executionContext") - public Optional getExecutionContext() { - return executionContext; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleResponse && equalTo((RuleResponse) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleResponse other) { - return createdBy.equals(other.createdBy) - && createdDateTime.equals(other.createdDateTime) - && modifiedBy.equals(other.modifiedBy) - && modifiedDateTime.equals(other.modifiedDateTime) - && id.equals(other.id) - && name.equals(other.name) - && status.equals(other.status) - && executionContext.equals(other.executionContext); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash( - this.createdBy, - this.createdDateTime, - this.modifiedBy, - this.modifiedDateTime, - this.id, - this.name, - this.status, - this.executionContext); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(@NotNull String id); - - Builder from(RuleResponse other); - } - - public interface NameStage { - StatusStage name(@NotNull String name); - } - - public interface StatusStage { - _FinalStage status(@NotNull RuleResponseStatus status); - } - - public interface _FinalStage { - RuleResponse build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

The user who created this resource.

- */ - _FinalStage createdBy(Optional createdBy); - - _FinalStage createdBy(String createdBy); - - /** - *

When this resource was created.

- */ - _FinalStage createdDateTime(Optional createdDateTime); - - _FinalStage createdDateTime(OffsetDateTime createdDateTime); - - /** - *

The user who last modified this resource.

- */ - _FinalStage modifiedBy(Optional modifiedBy); - - _FinalStage modifiedBy(String modifiedBy); - - /** - *

When this resource was last modified.

- */ - _FinalStage modifiedDateTime(Optional modifiedDateTime); - - _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime); - - _FinalStage executionContext(Optional executionContext); - - _FinalStage executionContext(RuleExecutionContext executionContext); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, NameStage, StatusStage, _FinalStage { - private String id; - - private String name; - - private RuleResponseStatus status; - - private Optional executionContext = Optional.empty(); - - private Optional modifiedDateTime = Optional.empty(); - - private Optional modifiedBy = Optional.empty(); - - private Optional createdDateTime = Optional.empty(); - - private Optional createdBy = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleResponse other) { - createdBy(other.getCreatedBy()); - createdDateTime(other.getCreatedDateTime()); - modifiedBy(other.getModifiedBy()); - modifiedDateTime(other.getModifiedDateTime()); - id(other.getId()); - name(other.getName()); - status(other.getStatus()); - executionContext(other.getExecutionContext()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public StatusStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("status") - public _FinalStage status(@NotNull RuleResponseStatus status) { - this.status = Objects.requireNonNull(status, "status must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage executionContext(RuleExecutionContext executionContext) { - this.executionContext = Optional.ofNullable(executionContext); - return this; - } - - @java.lang.Override - @JsonSetter(value = "executionContext", nulls = Nulls.SKIP) - public _FinalStage executionContext(Optional executionContext) { - this.executionContext = executionContext; - return this; - } - - /** - *

When this resource was last modified.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedDateTime(OffsetDateTime modifiedDateTime) { - this.modifiedDateTime = Optional.ofNullable(modifiedDateTime); - return this; - } - - /** - *

When this resource was last modified.

- */ - @java.lang.Override - @JsonSetter(value = "modifiedDateTime", nulls = Nulls.SKIP) - public _FinalStage modifiedDateTime(Optional modifiedDateTime) { - this.modifiedDateTime = modifiedDateTime; - return this; - } - - /** - *

The user who last modified this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage modifiedBy(String modifiedBy) { - this.modifiedBy = Optional.ofNullable(modifiedBy); - return this; - } - - /** - *

The user who last modified this resource.

- */ - @java.lang.Override - @JsonSetter(value = "modifiedBy", nulls = Nulls.SKIP) - public _FinalStage modifiedBy(Optional modifiedBy) { - this.modifiedBy = modifiedBy; - return this; - } - - /** - *

When this resource was created.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdDateTime(OffsetDateTime createdDateTime) { - this.createdDateTime = Optional.ofNullable(createdDateTime); - return this; - } - - /** - *

When this resource was created.

- */ - @java.lang.Override - @JsonSetter(value = "createdDateTime", nulls = Nulls.SKIP) - public _FinalStage createdDateTime(Optional createdDateTime) { - this.createdDateTime = createdDateTime; - return this; - } - - /** - *

The user who created this resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage createdBy(String createdBy) { - this.createdBy = Optional.ofNullable(createdBy); - return this; - } - - /** - *

The user who created this resource.

- */ - @java.lang.Override - @JsonSetter(value = "createdBy", nulls = Nulls.SKIP) - public _FinalStage createdBy(Optional createdBy) { - this.createdBy = createdBy; - return this; - } - - @java.lang.Override - public RuleResponse build() { - return new RuleResponse( - createdBy, - createdDateTime, - modifiedBy, - modifiedDateTime, - id, - name, - status, - executionContext, - additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java deleted file mode 100644 index b771bb61005f..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleResponseStatus.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class RuleResponseStatus { - public static final RuleResponseStatus INACTIVE = new RuleResponseStatus(Value.INACTIVE, "inactive"); - - public static final RuleResponseStatus ACTIVE = new RuleResponseStatus(Value.ACTIVE, "active"); - - public static final RuleResponseStatus DRAFT = new RuleResponseStatus(Value.DRAFT, "draft"); - - private final Value value; - - private final String string; - - RuleResponseStatus(Value value, String string) { - this.value = value; - this.string = string; - } - - public Value getEnumValue() { - return value; - } - - @java.lang.Override - @JsonValue - public String toString() { - return this.string; - } - - @java.lang.Override - public boolean equals(Object other) { - return (this == other) - || (other instanceof RuleResponseStatus && this.string.equals(((RuleResponseStatus) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case INACTIVE: - return visitor.visitInactive(); - case ACTIVE: - return visitor.visitActive(); - case DRAFT: - return visitor.visitDraft(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static RuleResponseStatus valueOf(String value) { - switch (value) { - case "inactive": - return INACTIVE; - case "active": - return ACTIVE; - case "draft": - return DRAFT; - default: - return new RuleResponseStatus(Value.UNKNOWN, value); - } - } - - public enum Value { - ACTIVE, - - INACTIVE, - - DRAFT, - - UNKNOWN - } - - public interface Visitor { - T visitActive(); - - T visitInactive(); - - T visitDraft(); - - T visitUnknown(String unknownType); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java deleted file mode 100644 index 18d016fde1bd..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleType.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleType.Builder.class) -public final class RuleType { - private final String id; - - private final String name; - - private final Optional description; - - private final Map additionalProperties; - - private RuleType(String id, String name, Optional description, Map additionalProperties) { - this.id = id; - this.name = name; - this.description = description; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("description") - public Optional getDescription() { - return description; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleType && equalTo((RuleType) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleType other) { - return id.equals(other.id) && name.equals(other.name) && description.equals(other.description); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.description); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - NameStage id(@NotNull String id); - - Builder from(RuleType other); - } - - public interface NameStage { - _FinalStage name(@NotNull String name); - } - - public interface _FinalStage { - RuleType build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - _FinalStage description(Optional description); - - _FinalStage description(String description); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, NameStage, _FinalStage { - private String id; - - private String name; - - private Optional description = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleType other) { - id(other.getId()); - name(other.getName()); - description(other.getDescription()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public NameStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("name") - public _FinalStage name(@NotNull String name) { - this.name = Objects.requireNonNull(name, "name must not be null"); - return this; - } - - @java.lang.Override - public _FinalStage description(String description) { - this.description = Optional.ofNullable(description); - return this; - } - - @java.lang.Override - @JsonSetter(value = "description", nulls = Nulls.SKIP) - public _FinalStage description(Optional description) { - this.description = description; - return this; - } - - @java.lang.Override - public RuleType build() { - return new RuleType(id, name, description, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java deleted file mode 100644 index 6df397a8a58d..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/RuleTypeSearchResponse.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = RuleTypeSearchResponse.Builder.class) -public final class RuleTypeSearchResponse { - private final Optional> results; - - private final PagingCursors paging; - - private final Map additionalProperties; - - private RuleTypeSearchResponse( - Optional> results, PagingCursors paging, Map additionalProperties) { - this.results = results; - this.paging = paging; - this.additionalProperties = additionalProperties; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof RuleTypeSearchResponse && equalTo((RuleTypeSearchResponse) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(RuleTypeSearchResponse other) { - return results.equals(other.results) && paging.equals(other.paging); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.results, this.paging); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(@NotNull PagingCursors paging); - - Builder from(RuleTypeSearchResponse other); - } - - public interface _FinalStage { - RuleTypeSearchResponse build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(RuleTypeSearchResponse other) { - results(other.getResults()); - paging(other.getPaging()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(@NotNull PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public RuleTypeSearchResponse build() { - return new RuleTypeSearchResponse(results, paging, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java deleted file mode 100644 index cb2b9f73c34e..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/User.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = User.Builder.class) -public final class User { - private final String id; - - private final String email; - - private final Map additionalProperties; - - private User(String id, String email, Map additionalProperties) { - this.id = id; - this.email = email; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("id") - public String getId() { - return id; - } - - @JsonProperty("email") - public String getEmail() { - return email; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof User && equalTo((User) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(User other) { - return id.equals(other.id) && email.equals(other.email); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.email); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - EmailStage id(@NotNull String id); - - Builder from(User other); - } - - public interface EmailStage { - _FinalStage email(@NotNull String email); - } - - public interface _FinalStage { - User build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements IdStage, EmailStage, _FinalStage { - private String id; - - private String email; - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(User other) { - id(other.getId()); - email(other.getEmail()); - return this; - } - - @java.lang.Override - @JsonSetter("id") - public EmailStage id(@NotNull String id) { - this.id = Objects.requireNonNull(id, "id must not be null"); - return this; - } - - @java.lang.Override - @JsonSetter("email") - public _FinalStage email(@NotNull String email) { - this.email = Objects.requireNonNull(email, "email must not be null"); - return this; - } - - @java.lang.Override - public User build() { - return new User(id, email, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java b/seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java deleted file mode 100644 index 320e2df960ff..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/seed/api/types/UserSearchResponse.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.types; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.seed.api.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = UserSearchResponse.Builder.class) -public final class UserSearchResponse { - private final Optional> results; - - private final PagingCursors paging; - - private final Map additionalProperties; - - private UserSearchResponse( - Optional> results, PagingCursors paging, Map additionalProperties) { - this.results = results; - this.paging = paging; - this.additionalProperties = additionalProperties; - } - - /** - * @return Current page of results from the requested resource. - */ - @JsonProperty("results") - public Optional> getResults() { - return results; - } - - @JsonProperty("paging") - public PagingCursors getPaging() { - return paging; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof UserSearchResponse && equalTo((UserSearchResponse) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(UserSearchResponse other) { - return results.equals(other.results) && paging.equals(other.paging); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.results, this.paging); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static PagingStage builder() { - return new Builder(); - } - - public interface PagingStage { - _FinalStage paging(@NotNull PagingCursors paging); - - Builder from(UserSearchResponse other); - } - - public interface _FinalStage { - UserSearchResponse build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Current page of results from the requested resource.

- */ - _FinalStage results(Optional> results); - - _FinalStage results(List results); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements PagingStage, _FinalStage { - private PagingCursors paging; - - private Optional> results = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(UserSearchResponse other) { - results(other.getResults()); - paging(other.getPaging()); - return this; - } - - @java.lang.Override - @JsonSetter("paging") - public _FinalStage paging(@NotNull PagingCursors paging) { - this.paging = Objects.requireNonNull(paging, "paging must not be null"); - return this; - } - - /** - *

Current page of results from the requested resource.

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - public _FinalStage results(List results) { - this.results = Optional.ofNullable(results); - return this; - } - - /** - *

Current page of results from the requested resource.

- */ - @java.lang.Override - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public _FinalStage results(Optional> results) { - this.results = results; - return this; - } - - @java.lang.Override - public UserSearchResponse build() { - return new UserSearchResponse(results, paging, additionalProperties); - } - - @java.lang.Override - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - @java.lang.Override - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example0.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example0.java deleted file mode 100644 index 22ad146af59c..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example0.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.SearchRuleTypesRequest; - -public class Example0 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.searchRuleTypes(SearchRuleTypesRequest.builder().build()); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example1.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example1.java deleted file mode 100644 index 20488261da14..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example1.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.SearchRuleTypesRequest; - -public class Example1 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.searchRuleTypes(SearchRuleTypesRequest.builder().query("query").build()); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example2.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example2.java deleted file mode 100644 index e0f6cdddaea0..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example2.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.types.RuleExecutionContext; - -public class Example2 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.createRule(RuleCreateRequest.builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build()); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example3.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example3.java deleted file mode 100644 index 7f1d77d08358..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example3.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; -import com.seed.api.requests.RuleCreateRequest; -import com.seed.api.types.RuleExecutionContext; - -public class Example3 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.createRule(RuleCreateRequest.builder() - .name("name") - .executionContext(RuleExecutionContext.PROD) - .build()); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example4.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example4.java deleted file mode 100644 index e680b39ebabe..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example4.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example4 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.listUsers(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example5.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example5.java deleted file mode 100644 index 83e4c14bbe78..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example5.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example5 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.listUsers(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example6.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example6.java deleted file mode 100644 index e1757715676e..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example6.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example6 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getEntity(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example7.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example7.java deleted file mode 100644 index e180edb8e8a3..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example7.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example7 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getEntity(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example8.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example8.java deleted file mode 100644 index e80deb53f21a..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example8.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example8 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getOrganization(); - } -} diff --git a/seed/java-sdk/allof/src/main/java/com/snippets/Example9.java b/seed/java-sdk/allof/src/main/java/com/snippets/Example9.java deleted file mode 100644 index f5ab1ab4544d..000000000000 --- a/seed/java-sdk/allof/src/main/java/com/snippets/Example9.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.snippets; - -import com.seed.api.SeedApiClient; - -public class Example9 { - public static void main(String[] args) { - SeedApiClient client = - SeedApiClient.builder().url("https://api.fern.com").build(); - - client.getOrganization(); - } -} diff --git a/seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java b/seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java deleted file mode 100644 index 9bf64b7a1cf5..000000000000 --- a/seed/java-sdk/allof/src/test/java/com/seed/api/StreamTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -import static org.junit.jupiter.api.Assertions.*; - -import com.seed.api.core.ObjectMappers; -import com.seed.api.core.Stream; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.junit.jupiter.api.Test; - -public final class StreamTest { - @Test - public void testJsonStream() { - List> messages = - Arrays.asList(createMap("message", "hello"), createMap("message", "world")); - List jsonStrings = messages.stream().map(StreamTest::mapToJson).collect(Collectors.toList()); - String input = String.join("\n", jsonStrings); - StringReader jsonInput = new StringReader(input); - Stream jsonStream = Stream.fromJson(Map.class, jsonInput); - int expectedMessages = 2; - int actualMessages = 0; - for (Map jsonObject : jsonStream) { - actualMessages++; - assertTrue(jsonObject.containsKey("message")); - } - assertEquals(expectedMessages, actualMessages); - } - - @Test - public void testSseStream() { - List> events = Arrays.asList(createMap("event", "start"), createMap("event", "end")); - List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); - String input = String.join("\n" + "\n", sseStrings); - StringReader sseInput = new StringReader(input); - Stream sseStream = Stream.fromSse(Map.class, sseInput); - int expectedEvents = 2; - int actualEvents = 0; - for (Map eventData : sseStream) { - actualEvents++; - assertTrue(eventData.containsKey("event")); - } - assertEquals(expectedEvents, actualEvents); - } - - @Test - public void testSseStreamWithTerminator() { - List> events = Arrays.asList(createMap("message", "first"), createMap("message", "second")); - List sseStrings = - new ArrayList<>(events.stream().map(StreamTest::mapToSse).collect(Collectors.toList())); - sseStrings.add("data: [DONE]"); - String input = String.join("\n" + "\n", sseStrings); - StringReader sseInput = new StringReader(input); - Stream sseStream = Stream.fromSse(Map.class, sseInput, "[DONE]"); - int expectedEvents = 2; - int actualEvents = 0; - for (Map eventData : sseStream) { - actualEvents++; - assertTrue(eventData.containsKey("message")); - } - assertEquals(expectedEvents, actualEvents); - } - - @Test - public void testSseEventDiscriminatedStream() { - List sseStrings = Arrays.asList( - mapToSseWithEvent("start", createMap("status", "pending")), - mapToSseWithEvent("end", createMap("status", "complete"))); - String input = String.join("\n" + "\n", sseStrings); - StringReader sseInput = new StringReader(input); - Stream sseStream = Stream.fromSseWithEventDiscrimination(Map.class, sseInput, "event"); - int expectedEvents = 2; - int actualEvents = 0; - for (Map eventData : sseStream) { - actualEvents++; - // Event-level discrimination includes the event field in the parsed result - assertTrue(eventData.containsKey("event")); - assertTrue(eventData.containsKey("data")); - } - assertEquals(expectedEvents, actualEvents); - } - - @Test - public void testStreamResourceManagement() throws IOException { - StringReader testInput = new StringReader("{\"test\":\"data\"}"); - Stream testStream = Stream.fromJson(Map.class, testInput); - testStream.close(); - assertFalse(testStream.iterator().hasNext()); - } - - private static String mapToJson(Map map) { - try { - return ObjectMappers.JSON_MAPPER.writeValueAsString(map); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static String mapToSse(Map map) { - return "data: " + mapToJson(map); - } - - private static String mapToSseWithEvent(String eventType, Map data) { - return "event: " + eventType + "\n" + "data: " + mapToJson(data); - } - - private static Map createMap(String key, String value) { - Map map = new HashMap<>(); - map.put(key, value); - return map; - } -} diff --git a/seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java deleted file mode 100644 index 1686cfd803c1..000000000000 --- a/seed/java-sdk/allof/src/test/java/com/seed/api/TestClient.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api; - -public final class TestClient { - public void test() { - // Add tests here and mark this file in .fernignore - assert true; - } -} diff --git a/seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java b/seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java deleted file mode 100644 index ead1a49af7a2..000000000000 --- a/seed/java-sdk/allof/src/test/java/com/seed/api/core/QueryStringMapperTest.java +++ /dev/null @@ -1,339 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.seed.api.core; - -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import okhttp3.HttpUrl; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public final class QueryStringMapperTest { - @Test - public void testObjectWithQuotedString_indexedArrays() { - Map map = new HashMap() { - { - put("hello", "\"world\""); - } - }; - - String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; - - String actualQueryString = queryString( - new HashMap() { - { - put("withquoted", map); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectWithQuotedString_arraysAsRepeats() { - Map map = new HashMap() { - { - put("hello", "\"world\""); - } - }; - - String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; - - String actualQueryString = queryString( - new HashMap() { - { - put("withquoted", map); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObject_indexedArrays() { - Map map = new HashMap() { - { - put("foo", "bar"); - put("baz", "qux"); - } - }; - - String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; - - String actualQueryString = queryString( - new HashMap() { - { - put("metadata", map); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObject_arraysAsRepeats() { - Map map = new HashMap() { - { - put("foo", "bar"); - put("baz", "qux"); - } - }; - - String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; - - String actualQueryString = queryString( - new HashMap() { - { - put("metadata", map); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testNestedObject_indexedArrays() { - Map> nestedMap = new HashMap>() { - { - put("mapkey1", new HashMap() { - { - put("mapkey1mapkey1", "mapkey1mapkey1value"); - put("mapkey1mapkey2", "mapkey1mapkey2value"); - } - }); - put("mapkey2", new HashMap() { - { - put("mapkey2mapkey1", "mapkey2mapkey1value"); - } - }); - } - }; - - String expectedQueryString = - "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" - + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; - - String actualQueryString = queryString( - new HashMap() { - { - put("nested", nestedMap); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testNestedObject_arraysAsRepeats() { - Map> nestedMap = new HashMap>() { - { - put("mapkey1", new HashMap() { - { - put("mapkey1mapkey1", "mapkey1mapkey1value"); - put("mapkey1mapkey2", "mapkey1mapkey2value"); - } - }); - put("mapkey2", new HashMap() { - { - put("mapkey2mapkey1", "mapkey2mapkey1value"); - } - }); - } - }; - - String expectedQueryString = - "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" - + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; - - String actualQueryString = queryString( - new HashMap() { - { - put("nested", nestedMap); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testDateTime_indexedArrays() { - OffsetDateTime dateTime = - OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); - - String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; - - String actualQueryString = queryString( - new HashMap() { - { - put("datetime", dateTime); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testDateTime_arraysAsRepeats() { - OffsetDateTime dateTime = - OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); - - String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; - - String actualQueryString = queryString( - new HashMap() { - { - put("datetime", dateTime); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectArray_indexedArrays() { - List> mapArray = new ArrayList>() { - { - add(new HashMap() { - { - put("key", "hello"); - put("value", "world"); - } - }); - add(new HashMap() { - { - put("key", "foo"); - put("value", "bar"); - } - }); - add(new HashMap<>()); - } - }; - - String expectedQueryString = "objects%5B0%5D%5Bvalue%5D=world&objects%5B0%5D%5Bkey%5D=hello&objects%5B1%5D" - + "%5Bvalue%5D=bar&objects%5B1%5D%5Bkey%5D=foo"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objects", mapArray); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectArray_arraysAsRepeats() { - List> mapArray = new ArrayList>() { - { - add(new HashMap() { - { - put("key", "hello"); - put("value", "world"); - } - }); - add(new HashMap() { - { - put("key", "foo"); - put("value", "bar"); - } - }); - add(new HashMap<>()); - } - }; - - String expectedQueryString = - "objects%5Bvalue%5D=world&objects%5Bkey%5D=hello&objects%5Bvalue" + "%5D=bar&objects%5Bkey%5D=foo"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objects", mapArray); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectWithArray_indexedArrays() { - Map objectWithArray = new HashMap() { - { - put("id", "abc123"); - put("contactIds", new ArrayList() { - { - add("id1"); - add("id2"); - add("id3"); - } - }); - } - }; - - String expectedQueryString = - "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds%5D%5B0%5D=id1&objectwitharray" - + "%5BcontactIds%5D%5B1%5D=id2&objectwitharray%5BcontactIds%5D%5B2%5D=id3"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objectwitharray", objectWithArray); - } - }, - false); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - @Test - public void testObjectWithArray_arraysAsRepeats() { - Map objectWithArray = new HashMap() { - { - put("id", "abc123"); - put("contactIds", new ArrayList() { - { - add("id1"); - add("id2"); - add("id3"); - } - }); - } - }; - - String expectedQueryString = "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds" - + "%5D=id1&objectwitharray%5BcontactIds%5D=id2&objectwitharray%5BcontactIds%5D=id3"; - - String actualQueryString = queryString( - new HashMap() { - { - put("objectwitharray", objectWithArray); - } - }, - true); - - Assertions.assertEquals(expectedQueryString, actualQueryString); - } - - private static String queryString(Map params, boolean arraysAsRepeats) { - HttpUrl.Builder httpUrl = HttpUrl.parse("http://www.fakewebsite.com/").newBuilder(); - params.forEach((paramName, paramValue) -> - QueryStringMapper.addQueryParameter(httpUrl, paramName, paramValue, arraysAsRepeats)); - return httpUrl.build().encodedQuery(); - } -} diff --git a/seed/openapi/allof-inline/openapi.yml b/seed/openapi/allof-inline/openapi.yml deleted file mode 100644 index 3f991de537f7..000000000000 --- a/seed/openapi/allof-inline/openapi.yml +++ /dev/null @@ -1,432 +0,0 @@ -openapi: 3.0.1 -info: - title: allOf Composition - version: '' -paths: - /rule-types: - get: - operationId: searchRuleTypes - tags: - - '' - parameters: - - name: query - in: query - required: false - schema: - type: string - nullable: true - responses: - '200': - description: Paginated list of rule types - content: - application/json: - schema: - $ref: '#/components/schemas/RuleTypeSearchResponse' - examples: - Example1: - value: - paging: - next: next - previous: previous - results: - - id: id - name: name - description: description - summary: Search rule types with paginated results - /rules: - post: - operationId: createRule - tags: - - '' - parameters: [] - responses: - '200': - description: Created rule - content: - application/json: - schema: - $ref: '#/components/schemas/RuleResponse' - examples: - Example1: - value: - createdBy: createdBy - createdDateTime: '2024-01-15T09:30:00Z' - modifiedBy: modifiedBy - modifiedDateTime: '2024-01-15T09:30:00Z' - id: id - name: name - status: active - executionContext: prod - summary: Create a rule with constrained execution context - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - example: name - executionContext: - $ref: '#/components/schemas/RuleExecutionContext' - required: - - name - - executionContext - examples: - Example1: - value: - name: name - executionContext: prod - /users: - get: - operationId: listUsers - tags: - - '' - parameters: [] - responses: - '200': - description: Paginated list of users - content: - application/json: - schema: - $ref: '#/components/schemas/UserSearchResponse' - examples: - Example1: - value: - paging: - next: next - previous: previous - results: - - id: id - email: email - summary: List users with paginated results - /entities: - get: - operationId: getEntity - tags: - - '' - parameters: [] - responses: - '200': - description: An entity with properties from multiple parents - content: - application/json: - schema: - $ref: '#/components/schemas/CombinedEntity' - examples: - Example1: - value: - id: id - name: name - summary: summary - status: active - summary: Get an entity that combines multiple parents - /organizations: - get: - operationId: getOrganization - tags: - - '' - parameters: [] - responses: - '200': - description: An organization whose metadata is merged from two parents - content: - application/json: - schema: - $ref: '#/components/schemas/Organization' - examples: - Example1: - value: - id: id - metadata: - region: region - domain: domain - name: name - summary: Get an organization with merged object-typed properties -components: - schemas: - PaginatedResult: - title: PaginatedResult - type: object - properties: - paging: - $ref: '#/components/schemas/PagingCursors' - results: - type: array - items: {} - description: Current page of results from the requested resource. - required: - - paging - - results - PagingCursors: - title: PagingCursors - type: object - properties: - next: - type: string - description: Cursor for the next page of results. - previous: - type: string - nullable: true - description: Cursor for the previous page of results. - required: - - next - RuleExecutionContext: - title: RuleExecutionContext - type: string - enum: - - prod - - staging - - dev - description: Execution environment for a rule. - AuditInfo: - title: AuditInfo - type: object - description: Common audit metadata. - properties: - createdBy: - type: string - nullable: true - description: The user who created this resource. - createdDateTime: - type: string - format: date-time - nullable: true - description: When this resource was created. - modifiedBy: - type: string - nullable: true - description: The user who last modified this resource. - modifiedDateTime: - type: string - format: date-time - nullable: true - description: When this resource was last modified. - RuleType: - title: RuleType - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - nullable: true - required: - - id - - name - RuleTypeSearchResponse: - title: RuleTypeSearchResponse - type: object - properties: - paging: - $ref: '#/components/schemas/PagingCursors' - results: - type: array - items: - $ref: '#/components/schemas/RuleType' - nullable: true - description: Current page of results from the requested resource. - required: - - paging - User: - title: User - type: object - properties: - id: - type: string - email: - type: string - format: email - required: - - id - - email - UserSearchResponse: - title: UserSearchResponse - type: object - properties: - paging: - $ref: '#/components/schemas/PagingCursors' - results: - type: array - items: - $ref: '#/components/schemas/User' - nullable: true - description: Current page of results from the requested resource. - required: - - paging - RuleResponseStatus: - title: RuleResponseStatus - type: string - enum: - - active - - inactive - - draft - RuleResponse: - title: RuleResponse - type: object - properties: - createdBy: - type: string - nullable: true - description: The user who created this resource. - createdDateTime: - type: string - format: date-time - nullable: true - description: When this resource was created. - modifiedBy: - type: string - nullable: true - description: The user who last modified this resource. - modifiedDateTime: - type: string - format: date-time - nullable: true - description: When this resource was last modified. - id: - type: string - example: id - name: - type: string - example: name - status: - $ref: '#/components/schemas/RuleResponseStatus' - executionContext: - $ref: '#/components/schemas/RuleExecutionContext' - nullable: true - required: - - id - - name - - status - Identifiable: - title: Identifiable - type: object - properties: - id: - type: string - description: Unique identifier. - name: - type: string - nullable: true - description: Display name from Identifiable. - required: - - id - Describable: - title: Describable - type: object - properties: - name: - type: string - nullable: true - description: Display name from Describable. - summary: - type: string - nullable: true - description: A short summary. - CombinedEntityStatus: - title: CombinedEntityStatus - type: string - enum: - - active - - archived - CombinedEntity: - title: CombinedEntity - type: object - properties: - id: - type: string - description: Unique identifier. - example: id - name: - type: string - nullable: true - description: Display name from Describable. - summary: - type: string - nullable: true - description: A short summary. - status: - $ref: '#/components/schemas/CombinedEntityStatus' - required: - - id - - status - BaseOrgMetadata: - title: BaseOrgMetadata - type: object - properties: - region: - type: string - description: Deployment region from BaseOrg. - tier: - type: string - nullable: true - description: Subscription tier. - required: - - region - BaseOrg: - title: BaseOrg - type: object - properties: - id: - type: string - metadata: - $ref: '#/components/schemas/BaseOrgMetadata' - nullable: true - required: - - id - DetailedOrgMetadata: - title: DetailedOrgMetadata - type: object - properties: - region: - type: string - description: Deployment region from DetailedOrg. - domain: - type: string - nullable: true - description: Custom domain name. - required: - - region - DetailedOrg: - title: DetailedOrg - type: object - properties: - metadata: - $ref: '#/components/schemas/DetailedOrgMetadata' - nullable: true - OrganizationMetadata: - title: OrganizationMetadata - type: object - properties: - region: - type: string - description: Deployment region from DetailedOrg. - domain: - type: string - nullable: true - description: Custom domain name. - required: - - region - Organization: - title: Organization - type: object - properties: - id: - type: string - example: id - metadata: - $ref: '#/components/schemas/OrganizationMetadata' - nullable: true - name: - type: string - example: name - required: - - id - - name - securitySchemes: {} -servers: - - url: https://api.example.com - description: Default diff --git a/seed/openapi/allof-inline/snippet.json b/seed/openapi/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/openapi/allof/openapi.yml b/seed/openapi/allof/openapi.yml deleted file mode 100644 index a8927f6d41d3..000000000000 --- a/seed/openapi/allof/openapi.yml +++ /dev/null @@ -1,403 +0,0 @@ -openapi: 3.0.1 -info: - title: allOf Composition - version: '' -paths: - /rule-types: - get: - operationId: searchRuleTypes - tags: - - '' - parameters: - - name: query - in: query - required: false - schema: - type: string - nullable: true - responses: - '200': - description: Paginated list of rule types - content: - application/json: - schema: - $ref: '#/components/schemas/RuleTypeSearchResponse' - examples: - Example1: - value: - paging: - next: next - previous: previous - results: - - id: id - name: name - description: description - summary: Search rule types with paginated results - /rules: - post: - operationId: createRule - tags: - - '' - parameters: [] - responses: - '200': - description: Created rule - content: - application/json: - schema: - $ref: '#/components/schemas/RuleResponse' - examples: - Example1: - value: - createdBy: createdBy - createdDateTime: '2024-01-15T09:30:00Z' - modifiedBy: modifiedBy - modifiedDateTime: '2024-01-15T09:30:00Z' - id: id - name: name - status: active - executionContext: prod - summary: Create a rule with constrained execution context - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - example: name - executionContext: - $ref: '#/components/schemas/RuleExecutionContext' - required: - - name - - executionContext - examples: - Example1: - value: - name: name - executionContext: prod - /users: - get: - operationId: listUsers - tags: - - '' - parameters: [] - responses: - '200': - description: Paginated list of users - content: - application/json: - schema: - $ref: '#/components/schemas/UserSearchResponse' - examples: - Example1: - value: - paging: - next: next - previous: previous - results: - - id: id - email: email - summary: List users with paginated results - /entities: - get: - operationId: getEntity - tags: - - '' - parameters: [] - responses: - '200': - description: An entity with properties from multiple parents - content: - application/json: - schema: - $ref: '#/components/schemas/CombinedEntity' - examples: - Example1: - value: - name: name - summary: summary - id: id - status: active - summary: Get an entity that combines multiple parents - /organizations: - get: - operationId: getOrganization - tags: - - '' - parameters: [] - responses: - '200': - description: An organization whose metadata is merged from two parents - content: - application/json: - schema: - $ref: '#/components/schemas/Organization' - examples: - Example1: - value: - metadata: - region: region - tier: tier - id: id - name: name - summary: Get an organization with merged object-typed properties -components: - schemas: - PaginatedResult: - title: PaginatedResult - type: object - properties: - paging: - $ref: '#/components/schemas/PagingCursors' - results: - type: array - items: {} - description: Current page of results from the requested resource. - required: - - paging - - results - PagingCursors: - title: PagingCursors - type: object - properties: - next: - type: string - description: Cursor for the next page of results. - previous: - type: string - nullable: true - description: Cursor for the previous page of results. - required: - - next - RuleExecutionContext: - title: RuleExecutionContext - type: string - enum: - - prod - - staging - - dev - description: Execution environment for a rule. - AuditInfo: - title: AuditInfo - type: object - description: Common audit metadata. - properties: - createdBy: - type: string - nullable: true - description: The user who created this resource. - createdDateTime: - type: string - format: date-time - nullable: true - description: When this resource was created. - modifiedBy: - type: string - nullable: true - description: The user who last modified this resource. - modifiedDateTime: - type: string - format: date-time - nullable: true - description: When this resource was last modified. - RuleType: - title: RuleType - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - nullable: true - required: - - id - - name - RuleTypeSearchResponse: - title: RuleTypeSearchResponse - type: object - properties: - results: - type: array - items: - $ref: '#/components/schemas/RuleType' - nullable: true - description: Current page of results from the requested resource. - paging: - $ref: '#/components/schemas/PagingCursors' - required: - - paging - User: - title: User - type: object - properties: - id: - type: string - email: - type: string - format: email - required: - - id - - email - UserSearchResponse: - title: UserSearchResponse - type: object - properties: - results: - type: array - items: - $ref: '#/components/schemas/User' - nullable: true - description: Current page of results from the requested resource. - paging: - $ref: '#/components/schemas/PagingCursors' - required: - - paging - RuleResponseStatus: - title: RuleResponseStatus - type: string - enum: - - active - - inactive - - draft - RuleResponse: - title: RuleResponse - type: object - properties: - id: - type: string - example: id - name: - type: string - example: name - status: - $ref: '#/components/schemas/RuleResponseStatus' - executionContext: - $ref: '#/components/schemas/RuleExecutionContext' - nullable: true - required: - - id - - name - - status - allOf: - - $ref: '#/components/schemas/AuditInfo' - Identifiable: - title: Identifiable - type: object - properties: - id: - type: string - description: Unique identifier. - name: - type: string - nullable: true - description: Display name from Identifiable. - required: - - id - Describable: - title: Describable - type: object - properties: - name: - type: string - nullable: true - description: Display name from Describable. - summary: - type: string - nullable: true - description: A short summary. - CombinedEntityStatus: - title: CombinedEntityStatus - type: string - enum: - - active - - archived - CombinedEntity: - title: CombinedEntity - type: object - properties: - status: - $ref: '#/components/schemas/CombinedEntityStatus' - id: - type: string - description: Unique identifier. - example: id - name: - type: string - nullable: true - description: Display name from Identifiable. - summary: - type: string - nullable: true - description: A short summary. - required: - - status - - id - BaseOrgMetadata: - title: BaseOrgMetadata - type: object - properties: - region: - type: string - description: Deployment region from BaseOrg. - tier: - type: string - nullable: true - description: Subscription tier. - required: - - region - BaseOrg: - title: BaseOrg - type: object - properties: - id: - type: string - metadata: - $ref: '#/components/schemas/BaseOrgMetadata' - nullable: true - required: - - id - DetailedOrgMetadata: - title: DetailedOrgMetadata - type: object - properties: - region: - type: string - description: Deployment region from DetailedOrg. - domain: - type: string - nullable: true - description: Custom domain name. - required: - - region - DetailedOrg: - title: DetailedOrg - type: object - properties: - metadata: - $ref: '#/components/schemas/DetailedOrgMetadata' - nullable: true - Organization: - title: Organization - type: object - properties: - name: - type: string - example: name - id: - type: string - example: id - metadata: - $ref: '#/components/schemas/BaseOrgMetadata' - nullable: true - required: - - name - - id - securitySchemes: {} -servers: - - url: https://api.example.com - description: Default diff --git a/seed/openapi/allof/snippet.json b/seed/openapi/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/php-model/allof-inline/.fern/metadata.json b/seed/php-model/allof-inline/.fern/metadata.json deleted file mode 100644 index 20bd10351f7f..000000000000 --- a/seed/php-model/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-php-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/php-model/allof-inline/.github/workflows/ci.yml b/seed/php-model/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 678eb6c9e141..000000000000 --- a/seed/php-model/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Build - run: | - composer build - - - name: Analyze - run: | - composer analyze - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Run Tests - run: | - composer test diff --git a/seed/php-model/allof-inline/.gitignore b/seed/php-model/allof-inline/.gitignore deleted file mode 100644 index 31a1aeb14f35..000000000000 --- a/seed/php-model/allof-inline/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.idea -.php-cs-fixer.cache -.phpunit.result.cache -composer.lock -vendor/ \ No newline at end of file diff --git a/seed/php-model/allof-inline/composer.json b/seed/php-model/allof-inline/composer.json deleted file mode 100644 index ad30960a8764..000000000000 --- a/seed/php-model/allof-inline/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "seed/seed", - "version": "0.0.1", - "description": "Seed PHP Library", - "keywords": [ - "seed", - "api", - "sdk" - ], - "license": [], - "require": { - "php": "^8.1", - "ext-json": "*", - "psr/http-client": "^1.0", - "psr/http-client-implementation": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-factory-implementation": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "php-http/discovery": "^1.0", - "php-http/multipart-stream-builder": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0", - "friendsofphp/php-cs-fixer": "3.5.0", - "phpstan/phpstan": "^1.12", - "guzzlehttp/guzzle": "^7.4" - }, - "autoload": { - "psr-4": { - "Seed\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Seed\\Tests\\": "tests/" - } - }, - "scripts": { - "build": [ - "@php -l src", - "@php -l tests" - ], - "test": "phpunit", - "analyze": "phpstan analyze src tests --memory-limit=1G" - } -} \ No newline at end of file diff --git a/seed/php-model/allof-inline/phpstan.neon b/seed/php-model/allof-inline/phpstan.neon deleted file mode 100644 index 780706b8f8a2..000000000000 --- a/seed/php-model/allof-inline/phpstan.neon +++ /dev/null @@ -1,6 +0,0 @@ -parameters: - level: max - reportUnmatchedIgnoredErrors: false - paths: - - src - - tests \ No newline at end of file diff --git a/seed/php-model/allof-inline/phpunit.xml b/seed/php-model/allof-inline/phpunit.xml deleted file mode 100644 index 54630a51163c..000000000000 --- a/seed/php-model/allof-inline/phpunit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - tests - - - \ No newline at end of file diff --git a/seed/php-model/allof-inline/snippet.json b/seed/php-model/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/php-model/allof-inline/src/AuditInfo.php b/seed/php-model/allof-inline/src/AuditInfo.php deleted file mode 100644 index 36f65f9b9e51..000000000000 --- a/seed/php-model/allof-inline/src/AuditInfo.php +++ /dev/null @@ -1,63 +0,0 @@ -createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/BaseOrg.php b/seed/php-model/allof-inline/src/BaseOrg.php deleted file mode 100644 index 058cc81a7740..000000000000 --- a/seed/php-model/allof-inline/src/BaseOrg.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/BaseOrgMetadata.php b/seed/php-model/allof-inline/src/BaseOrgMetadata.php deleted file mode 100644 index d19465ec45a8..000000000000 --- a/seed/php-model/allof-inline/src/BaseOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->tier = $values['tier'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/CombinedEntity.php b/seed/php-model/allof-inline/src/CombinedEntity.php deleted file mode 100644 index c7bca9c380d5..000000000000 --- a/seed/php-model/allof-inline/src/CombinedEntity.php +++ /dev/null @@ -1,58 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @param array{ - * id: string, - * status: value-of, - * name?: ?string, - * summary?: ?string, - * } $values - */ - public function __construct( - array $values, - ) { - $this->id = $values['id']; - $this->name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - $this->status = $values['status']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/CombinedEntityStatus.php b/seed/php-model/allof-inline/src/CombinedEntityStatus.php deleted file mode 100644 index 0f880e407b51..000000000000 --- a/seed/php-model/allof-inline/src/CombinedEntityStatus.php +++ /dev/null @@ -1,9 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: $json"); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php b/seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php deleted file mode 100644 index 1a250c614e45..000000000000 --- a/seed/php-model/allof-inline/src/Core/Json/JsonDeserializer.php +++ /dev/null @@ -1,218 +0,0 @@ - $data The array to be deserialized. - * @param array $type The type definition from the annotation. - * @return array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (\Throwable) { - // Catching Throwable instead of Exception to handle TypeError - // that occurs when assigning null to non-nullable typed properties - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: $type" - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - /** @var array $data */ - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement JsonSerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, JsonSerializableType::class)) { - throw new JsonException("$type is not a subclass of JsonSerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php b/seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php deleted file mode 100644 index 0dbf3fcc9948..000000000000 --- a/seed/php-model/allof-inline/src/Core/Json/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ - Extra properties from JSON that don't map to class properties */ - private array $__additionalProperties = []; - - /** @var array Properties that have been explicitly set via setter methods */ - private array $__explicitlySetProperties = []; - - /** - * Serializes the object to a JSON string. - * - * @return string JSON-encoded string representation of the object. - * @throws Exception If encoding fails. - */ - public function toJson(): string - { - $serializedObject = $this->jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey === null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === Date::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - // Include the value if it's not null, OR if it was explicitly set (even to null) - if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { - $result[$jsonKey] = $value; - } - } - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - /** @var array $decodedJson */ - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - $properties = []; - $additionalProperties = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - $properties[$jsonKey] = $property; - } - - foreach ($data as $jsonKey => $value) { - if (!isset($properties[$jsonKey])) { - // This JSON key doesn't map to any class property - add it to additionalProperties - $additionalProperties[$jsonKey] = $value; - continue; - } - - $property = $properties[$jsonKey]; - - // Handle Date annotation - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === Date::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle Array annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - /** @var array $arrayValue */ - $arrayValue = $value; - $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); - } - - $args[$property->getName()] = $value; - } - - // Fill in any missing properties with defaults - foreach ($properties as $property) { - if (!isset($args[$property->getName()])) { - $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; - } - } - - // @phpstan-ignore-next-line - $result = new static($args); - $result->__additionalProperties = $additionalProperties; - return $result; - } - - /** - * Get properties from JSON that weren't mapped to class fields - * @return array - */ - public function getAdditionalProperties(): array - { - return $this->__additionalProperties; - } - - /** - * Mark a property as explicitly set. - * This ensures the property will be included in JSON serialization even if null. - * - * @param string $propertyName The name of the property to mark as explicitly set. - */ - protected function _setField(string $propertyName): void - { - $this->__explicitlySetProperties[$propertyName] = true; - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php b/seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php deleted file mode 100644 index f7d80ed5e8f3..000000000000 --- a/seed/php-model/allof-inline/src/Core/Json/JsonSerializer.php +++ /dev/null @@ -1,205 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - $formatted = $date->format(Constant::DateTimeFormat); - if (str_ends_with($formatted, '+00:00')) { - return substr($formatted, 0, -6) . 'Z'; - } - return $formatted; - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param array $data The array to be serialized. - * @param array $type The type definition from the annotation. - * @return array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: $unionType" - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/allof-inline/src/Core/Json/Utils.php b/seed/php-model/allof-inline/src/Core/Json/Utils.php deleted file mode 100644 index 4099b8253005..000000000000 --- a/seed/php-model/allof-inline/src/Core/Json/Utils.php +++ /dev/null @@ -1,62 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return int|string The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): int|string - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - // PHP arrays don't support float keys; truncate to int - 'float' => (int)$key, - 'string' => (string)$key, - default => is_int($key) ? $key : (string)$key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/allof-inline/src/Core/Types/ArrayType.php b/seed/php-model/allof-inline/src/Core/Types/ArrayType.php deleted file mode 100644 index a26d29008ec3..000000000000 --- a/seed/php-model/allof-inline/src/Core/Types/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/allof-inline/src/Core/Types/Constant.php b/seed/php-model/allof-inline/src/Core/Types/Constant.php deleted file mode 100644 index 5ac4518cc6d6..000000000000 --- a/seed/php-model/allof-inline/src/Core/Types/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/allof-inline/src/Describable.php b/seed/php-model/allof-inline/src/Describable.php deleted file mode 100644 index 54244b8b6d52..000000000000 --- a/seed/php-model/allof-inline/src/Describable.php +++ /dev/null @@ -1,42 +0,0 @@ -name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/DetailedOrg.php b/seed/php-model/allof-inline/src/DetailedOrg.php deleted file mode 100644 index 398ab2d6da4d..000000000000 --- a/seed/php-model/allof-inline/src/DetailedOrg.php +++ /dev/null @@ -1,34 +0,0 @@ -metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/DetailedOrgMetadata.php b/seed/php-model/allof-inline/src/DetailedOrgMetadata.php deleted file mode 100644 index cd14f445e6bb..000000000000 --- a/seed/php-model/allof-inline/src/DetailedOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->domain = $values['domain'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/Identifiable.php b/seed/php-model/allof-inline/src/Identifiable.php deleted file mode 100644 index beffe19d83ea..000000000000 --- a/seed/php-model/allof-inline/src/Identifiable.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->name = $values['name'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/Organization.php b/seed/php-model/allof-inline/src/Organization.php deleted file mode 100644 index a19d1d695019..000000000000 --- a/seed/php-model/allof-inline/src/Organization.php +++ /dev/null @@ -1,50 +0,0 @@ -id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - $this->name = $values['name']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/OrganizationMetadata.php b/seed/php-model/allof-inline/src/OrganizationMetadata.php deleted file mode 100644 index 1e0038954f23..000000000000 --- a/seed/php-model/allof-inline/src/OrganizationMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->domain = $values['domain'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/PaginatedResult.php b/seed/php-model/allof-inline/src/PaginatedResult.php deleted file mode 100644 index 708f16632a8a..000000000000 --- a/seed/php-model/allof-inline/src/PaginatedResult.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType(['mixed'])] - public array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/PagingCursors.php b/seed/php-model/allof-inline/src/PagingCursors.php deleted file mode 100644 index d5fd868b3f76..000000000000 --- a/seed/php-model/allof-inline/src/PagingCursors.php +++ /dev/null @@ -1,42 +0,0 @@ -next = $values['next']; - $this->previous = $values['previous'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/RuleExecutionContext.php b/seed/php-model/allof-inline/src/RuleExecutionContext.php deleted file mode 100644 index f74be26cce99..000000000000 --- a/seed/php-model/allof-inline/src/RuleExecutionContext.php +++ /dev/null @@ -1,10 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @var ?value-of $executionContext - */ - #[JsonProperty('executionContext')] - public ?string $executionContext; - - /** - * @param array{ - * id: string, - * name: string, - * status: value-of, - * createdBy?: ?string, - * createdDateTime?: ?DateTime, - * modifiedBy?: ?string, - * modifiedDateTime?: ?DateTime, - * executionContext?: ?value-of, - * } $values - */ - public function __construct( - array $values, - ) { - $this->createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - $this->id = $values['id']; - $this->name = $values['name']; - $this->status = $values['status']; - $this->executionContext = $values['executionContext'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/RuleResponseStatus.php b/seed/php-model/allof-inline/src/RuleResponseStatus.php deleted file mode 100644 index c14b15f636cf..000000000000 --- a/seed/php-model/allof-inline/src/RuleResponseStatus.php +++ /dev/null @@ -1,10 +0,0 @@ -id = $values['id']; - $this->name = $values['name']; - $this->description = $values['description'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/RuleTypeSearchResponse.php b/seed/php-model/allof-inline/src/RuleTypeSearchResponse.php deleted file mode 100644 index f80bd406c292..000000000000 --- a/seed/php-model/allof-inline/src/RuleTypeSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([RuleType::class])] - public ?array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/User.php b/seed/php-model/allof-inline/src/User.php deleted file mode 100644 index 42f9a1b02b7f..000000000000 --- a/seed/php-model/allof-inline/src/User.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->email = $values['email']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/src/UserSearchResponse.php b/seed/php-model/allof-inline/src/UserSearchResponse.php deleted file mode 100644 index e959fabc832f..000000000000 --- a/seed/php-model/allof-inline/src/UserSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([User::class])] - public ?array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php deleted file mode 100644 index 2c32002340e7..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php +++ /dev/null @@ -1,76 +0,0 @@ -name; - } - - /** - * @return string|null - */ - public function getEmail(): ?string - { - return $this->email; - } - - /** - * @param array{ - * name: string, - * email?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->name = $values['name']; - $this->email = $values['email'] ?? null; - } -} - -class AdditionalPropertiesTest extends TestCase -{ - public function testExtraProperties(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'name' => 'john.doe', - 'email' => 'john.doe@example.com', - 'age' => 42 - ], - ); - - $person = Person::fromJson($expectedJson); - $this->assertEquals('john.doe', $person->getName()); - $this->assertEquals('john.doe@example.com', $person->getEmail()); - $this->assertEquals( - [ - 'age' => 42 - ], - $person->getAdditionalProperties(), - ); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php deleted file mode 100644 index e7794d652432..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/DateArrayTest.php +++ /dev/null @@ -1,54 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTest extends TestCase -{ - public function testDateTimeInArrays(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ], - ); - - $object = DateArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php deleted file mode 100644 index b5f217e01f76..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/EmptyArrayTest.php +++ /dev/null @@ -1,71 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArrayTest extends TestCase -{ - public function testEmptyArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ], - ); - - $object = EmptyArray::fromJson($expectedJson); - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/EnumTest.php b/seed/php-model/allof-inline/tests/Core/Json/EnumTest.php deleted file mode 100644 index 72dc6f2cfa00..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/EnumTest.php +++ /dev/null @@ -1,77 +0,0 @@ -value; - } -} - -class ShapeType extends JsonSerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = JsonEncoder::encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ]); - - $actualJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $actualJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php b/seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php deleted file mode 100644 index 4c288378b48b..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/ExhaustiveTest.php +++ /dev/null @@ -1,197 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class Type extends JsonSerializableType -{ - /** - * @var Nested nestedType - */ - #[JsonProperty('nested_type')] - public Nested $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[Date(Date::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[Date(Date::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(Nested::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: Nested, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class ExhaustiveTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in Type. - */ - public function testExhaustive(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // Omit 'nullable_property' to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56Z', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> - ], - ); - - $object = Type::fromJson($expectedJson); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php b/seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php deleted file mode 100644 index 9d845ea113b8..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/InvalidTest.php +++ /dev/null @@ -1,42 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTest extends TestCase -{ - public function testInvalidJsonThrowsException(): void - { - $this->expectException(\TypeError::class); - $json = JsonEncoder::encode( - [ - 'integer_property' => 'not_an_integer' - ], - ); - Invalid::fromJson($json); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php deleted file mode 100644 index 8fbbeb939f02..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/NestedUnionArrayTest.php +++ /dev/null @@ -1,89 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArray extends JsonSerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTest extends TestCase -{ - public function testNestedUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ], - ); - - $object = NestedUnionArray::fromJson($expectedJson); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php b/seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php deleted file mode 100644 index ce20a2442825..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/NullPropertyTest.php +++ /dev/null @@ -1,53 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullProperty( - [ - "nonNullProperty" => "Test String", - "nullProperty" => null - ] - ); - - $serialized = $object->jsonSerialize(); - $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); - $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php deleted file mode 100644 index d1749c434a4c..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/NullableArrayTest.php +++ /dev/null @@ -1,49 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTest extends TestCase -{ - public function testNullableArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nullable_string_array' => ['one', null, 'three'] - ], - ); - - $object = NullableArray::fromJson($expectedJson); - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php b/seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php deleted file mode 100644 index ad4db0251bb5..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/ScalarTest.php +++ /dev/null @@ -1,116 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats - ], - ); - - $object = Scalar::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/TraitTest.php b/seed/php-model/allof-inline/tests/Core/Json/TraitTest.php deleted file mode 100644 index e18f06d4191b..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/TraitTest.php +++ /dev/null @@ -1,60 +0,0 @@ -integerProperty = $values['integerProperty']; - $this->stringProperty = $values['stringProperty']; - } -} - -class TraitTest extends TestCase -{ - public function testTraitPropertyAndString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'string_property' => 'Hello, World!', - ], - ); - - $object = TypeWithTrait::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php b/seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php deleted file mode 100644 index de20cf9fde1b..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/UnionArrayTest.php +++ /dev/null @@ -1,57 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class UnionArrayTest extends TestCase -{ - public function testUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00Z', - 2 => null, - 3 => 'Some String' - ] - ], - ); - - $object = UnionArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php b/seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php deleted file mode 100644 index f733062cfabc..000000000000 --- a/seed/php-model/allof-inline/tests/Core/Json/UnionPropertyTest.php +++ /dev/null @@ -1,111 +0,0 @@ - 'integer'], UnionProperty::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionProperty - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => [1 => 100, 2 => 200] - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => new UnionProperty( - [ - 'complexUnion' => 'Nested String' - ] - ) - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $expectedJson = JsonEncoder::encode( - [], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 42 - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 'Some String' - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/allof/.fern/metadata.json b/seed/php-model/allof/.fern/metadata.json deleted file mode 100644 index 20bd10351f7f..000000000000 --- a/seed/php-model/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-php-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/php-model/allof/.github/workflows/ci.yml b/seed/php-model/allof/.github/workflows/ci.yml deleted file mode 100644 index 678eb6c9e141..000000000000 --- a/seed/php-model/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Build - run: | - composer build - - - name: Analyze - run: | - composer analyze - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Run Tests - run: | - composer test diff --git a/seed/php-model/allof/.gitignore b/seed/php-model/allof/.gitignore deleted file mode 100644 index 31a1aeb14f35..000000000000 --- a/seed/php-model/allof/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.idea -.php-cs-fixer.cache -.phpunit.result.cache -composer.lock -vendor/ \ No newline at end of file diff --git a/seed/php-model/allof/composer.json b/seed/php-model/allof/composer.json deleted file mode 100644 index ad30960a8764..000000000000 --- a/seed/php-model/allof/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "seed/seed", - "version": "0.0.1", - "description": "Seed PHP Library", - "keywords": [ - "seed", - "api", - "sdk" - ], - "license": [], - "require": { - "php": "^8.1", - "ext-json": "*", - "psr/http-client": "^1.0", - "psr/http-client-implementation": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-factory-implementation": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "php-http/discovery": "^1.0", - "php-http/multipart-stream-builder": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0", - "friendsofphp/php-cs-fixer": "3.5.0", - "phpstan/phpstan": "^1.12", - "guzzlehttp/guzzle": "^7.4" - }, - "autoload": { - "psr-4": { - "Seed\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Seed\\Tests\\": "tests/" - } - }, - "scripts": { - "build": [ - "@php -l src", - "@php -l tests" - ], - "test": "phpunit", - "analyze": "phpstan analyze src tests --memory-limit=1G" - } -} \ No newline at end of file diff --git a/seed/php-model/allof/phpstan.neon b/seed/php-model/allof/phpstan.neon deleted file mode 100644 index 780706b8f8a2..000000000000 --- a/seed/php-model/allof/phpstan.neon +++ /dev/null @@ -1,6 +0,0 @@ -parameters: - level: max - reportUnmatchedIgnoredErrors: false - paths: - - src - - tests \ No newline at end of file diff --git a/seed/php-model/allof/phpunit.xml b/seed/php-model/allof/phpunit.xml deleted file mode 100644 index 54630a51163c..000000000000 --- a/seed/php-model/allof/phpunit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - tests - - - \ No newline at end of file diff --git a/seed/php-model/allof/snippet.json b/seed/php-model/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/php-model/allof/src/AuditInfo.php b/seed/php-model/allof/src/AuditInfo.php deleted file mode 100644 index 36f65f9b9e51..000000000000 --- a/seed/php-model/allof/src/AuditInfo.php +++ /dev/null @@ -1,63 +0,0 @@ -createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/BaseOrg.php b/seed/php-model/allof/src/BaseOrg.php deleted file mode 100644 index 058cc81a7740..000000000000 --- a/seed/php-model/allof/src/BaseOrg.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/BaseOrgMetadata.php b/seed/php-model/allof/src/BaseOrgMetadata.php deleted file mode 100644 index d19465ec45a8..000000000000 --- a/seed/php-model/allof/src/BaseOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->tier = $values['tier'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/CombinedEntity.php b/seed/php-model/allof/src/CombinedEntity.php deleted file mode 100644 index df398f7242f3..000000000000 --- a/seed/php-model/allof/src/CombinedEntity.php +++ /dev/null @@ -1,58 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @var string $id Unique identifier. - */ - #[JsonProperty('id')] - public string $id; - - /** - * @var ?string $name Display name from Identifiable. - */ - #[JsonProperty('name')] - public ?string $name; - - /** - * @var ?string $summary A short summary. - */ - #[JsonProperty('summary')] - public ?string $summary; - - /** - * @param array{ - * status: value-of, - * id: string, - * name?: ?string, - * summary?: ?string, - * } $values - */ - public function __construct( - array $values, - ) { - $this->status = $values['status']; - $this->id = $values['id']; - $this->name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/CombinedEntityStatus.php b/seed/php-model/allof/src/CombinedEntityStatus.php deleted file mode 100644 index 0f880e407b51..000000000000 --- a/seed/php-model/allof/src/CombinedEntityStatus.php +++ /dev/null @@ -1,9 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: $json"); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/allof/src/Core/Json/JsonDeserializer.php b/seed/php-model/allof/src/Core/Json/JsonDeserializer.php deleted file mode 100644 index 1a250c614e45..000000000000 --- a/seed/php-model/allof/src/Core/Json/JsonDeserializer.php +++ /dev/null @@ -1,218 +0,0 @@ - $data The array to be deserialized. - * @param array $type The type definition from the annotation. - * @return array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (\Throwable) { - // Catching Throwable instead of Exception to handle TypeError - // that occurs when assigning null to non-nullable typed properties - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: $type" - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - /** @var array $data */ - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement JsonSerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, JsonSerializableType::class)) { - throw new JsonException("$type is not a subclass of JsonSerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/allof/src/Core/Json/JsonEncoder.php b/seed/php-model/allof/src/Core/Json/JsonEncoder.php deleted file mode 100644 index 0dbf3fcc9948..000000000000 --- a/seed/php-model/allof/src/Core/Json/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ - Extra properties from JSON that don't map to class properties */ - private array $__additionalProperties = []; - - /** @var array Properties that have been explicitly set via setter methods */ - private array $__explicitlySetProperties = []; - - /** - * Serializes the object to a JSON string. - * - * @return string JSON-encoded string representation of the object. - * @throws Exception If encoding fails. - */ - public function toJson(): string - { - $serializedObject = $this->jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey === null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === Date::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - // Include the value if it's not null, OR if it was explicitly set (even to null) - if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { - $result[$jsonKey] = $value; - } - } - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - /** @var array $decodedJson */ - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - $properties = []; - $additionalProperties = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - $properties[$jsonKey] = $property; - } - - foreach ($data as $jsonKey => $value) { - if (!isset($properties[$jsonKey])) { - // This JSON key doesn't map to any class property - add it to additionalProperties - $additionalProperties[$jsonKey] = $value; - continue; - } - - $property = $properties[$jsonKey]; - - // Handle Date annotation - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === Date::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle Array annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - /** @var array $arrayValue */ - $arrayValue = $value; - $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); - } - - $args[$property->getName()] = $value; - } - - // Fill in any missing properties with defaults - foreach ($properties as $property) { - if (!isset($args[$property->getName()])) { - $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; - } - } - - // @phpstan-ignore-next-line - $result = new static($args); - $result->__additionalProperties = $additionalProperties; - return $result; - } - - /** - * Get properties from JSON that weren't mapped to class fields - * @return array - */ - public function getAdditionalProperties(): array - { - return $this->__additionalProperties; - } - - /** - * Mark a property as explicitly set. - * This ensures the property will be included in JSON serialization even if null. - * - * @param string $propertyName The name of the property to mark as explicitly set. - */ - protected function _setField(string $propertyName): void - { - $this->__explicitlySetProperties[$propertyName] = true; - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/allof/src/Core/Json/JsonSerializer.php b/seed/php-model/allof/src/Core/Json/JsonSerializer.php deleted file mode 100644 index f7d80ed5e8f3..000000000000 --- a/seed/php-model/allof/src/Core/Json/JsonSerializer.php +++ /dev/null @@ -1,205 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - $formatted = $date->format(Constant::DateTimeFormat); - if (str_ends_with($formatted, '+00:00')) { - return substr($formatted, 0, -6) . 'Z'; - } - return $formatted; - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param array $data The array to be serialized. - * @param array $type The type definition from the annotation. - * @return array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: $unionType" - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/allof/src/Core/Json/Utils.php b/seed/php-model/allof/src/Core/Json/Utils.php deleted file mode 100644 index 4099b8253005..000000000000 --- a/seed/php-model/allof/src/Core/Json/Utils.php +++ /dev/null @@ -1,62 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return int|string The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): int|string - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - // PHP arrays don't support float keys; truncate to int - 'float' => (int)$key, - 'string' => (string)$key, - default => is_int($key) ? $key : (string)$key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/allof/src/Core/Types/ArrayType.php b/seed/php-model/allof/src/Core/Types/ArrayType.php deleted file mode 100644 index a26d29008ec3..000000000000 --- a/seed/php-model/allof/src/Core/Types/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/allof/src/Core/Types/Constant.php b/seed/php-model/allof/src/Core/Types/Constant.php deleted file mode 100644 index 5ac4518cc6d6..000000000000 --- a/seed/php-model/allof/src/Core/Types/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/allof/src/Describable.php b/seed/php-model/allof/src/Describable.php deleted file mode 100644 index 54244b8b6d52..000000000000 --- a/seed/php-model/allof/src/Describable.php +++ /dev/null @@ -1,42 +0,0 @@ -name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/DetailedOrg.php b/seed/php-model/allof/src/DetailedOrg.php deleted file mode 100644 index 398ab2d6da4d..000000000000 --- a/seed/php-model/allof/src/DetailedOrg.php +++ /dev/null @@ -1,34 +0,0 @@ -metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/DetailedOrgMetadata.php b/seed/php-model/allof/src/DetailedOrgMetadata.php deleted file mode 100644 index cd14f445e6bb..000000000000 --- a/seed/php-model/allof/src/DetailedOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->domain = $values['domain'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/Identifiable.php b/seed/php-model/allof/src/Identifiable.php deleted file mode 100644 index beffe19d83ea..000000000000 --- a/seed/php-model/allof/src/Identifiable.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->name = $values['name'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/Organization.php b/seed/php-model/allof/src/Organization.php deleted file mode 100644 index 51776ae2fe4b..000000000000 --- a/seed/php-model/allof/src/Organization.php +++ /dev/null @@ -1,50 +0,0 @@ -name = $values['name']; - $this->id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/PaginatedResult.php b/seed/php-model/allof/src/PaginatedResult.php deleted file mode 100644 index 708f16632a8a..000000000000 --- a/seed/php-model/allof/src/PaginatedResult.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType(['mixed'])] - public array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/PagingCursors.php b/seed/php-model/allof/src/PagingCursors.php deleted file mode 100644 index d5fd868b3f76..000000000000 --- a/seed/php-model/allof/src/PagingCursors.php +++ /dev/null @@ -1,42 +0,0 @@ -next = $values['next']; - $this->previous = $values['previous'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/RuleExecutionContext.php b/seed/php-model/allof/src/RuleExecutionContext.php deleted file mode 100644 index f74be26cce99..000000000000 --- a/seed/php-model/allof/src/RuleExecutionContext.php +++ /dev/null @@ -1,10 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @var ?value-of $executionContext - */ - #[JsonProperty('executionContext')] - public ?string $executionContext; - - /** - * @param array{ - * id: string, - * name: string, - * status: value-of, - * createdBy?: ?string, - * createdDateTime?: ?DateTime, - * modifiedBy?: ?string, - * modifiedDateTime?: ?DateTime, - * executionContext?: ?value-of, - * } $values - */ - public function __construct( - array $values, - ) { - $this->createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - $this->id = $values['id']; - $this->name = $values['name']; - $this->status = $values['status']; - $this->executionContext = $values['executionContext'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/RuleResponseStatus.php b/seed/php-model/allof/src/RuleResponseStatus.php deleted file mode 100644 index c14b15f636cf..000000000000 --- a/seed/php-model/allof/src/RuleResponseStatus.php +++ /dev/null @@ -1,10 +0,0 @@ -id = $values['id']; - $this->name = $values['name']; - $this->description = $values['description'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/RuleTypeSearchResponse.php b/seed/php-model/allof/src/RuleTypeSearchResponse.php deleted file mode 100644 index 45632f5487a1..000000000000 --- a/seed/php-model/allof/src/RuleTypeSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([RuleType::class])] - public ?array $results; - - /** - * @var PagingCursors $paging - */ - #[JsonProperty('paging')] - public PagingCursors $paging; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->results = $values['results'] ?? null; - $this->paging = $values['paging']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/Traits/AuditInfo.php b/seed/php-model/allof/src/Traits/AuditInfo.php deleted file mode 100644 index 2c2ce1e5f033..000000000000 --- a/seed/php-model/allof/src/Traits/AuditInfo.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->email = $values['email']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/src/UserSearchResponse.php b/seed/php-model/allof/src/UserSearchResponse.php deleted file mode 100644 index d43e218c9d99..000000000000 --- a/seed/php-model/allof/src/UserSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([User::class])] - public ?array $results; - - /** - * @var PagingCursors $paging - */ - #[JsonProperty('paging')] - public PagingCursors $paging; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->results = $values['results'] ?? null; - $this->paging = $values['paging']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php deleted file mode 100644 index 2c32002340e7..000000000000 --- a/seed/php-model/allof/tests/Core/Json/AdditionalPropertiesTest.php +++ /dev/null @@ -1,76 +0,0 @@ -name; - } - - /** - * @return string|null - */ - public function getEmail(): ?string - { - return $this->email; - } - - /** - * @param array{ - * name: string, - * email?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->name = $values['name']; - $this->email = $values['email'] ?? null; - } -} - -class AdditionalPropertiesTest extends TestCase -{ - public function testExtraProperties(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'name' => 'john.doe', - 'email' => 'john.doe@example.com', - 'age' => 42 - ], - ); - - $person = Person::fromJson($expectedJson); - $this->assertEquals('john.doe', $person->getName()); - $this->assertEquals('john.doe@example.com', $person->getEmail()); - $this->assertEquals( - [ - 'age' => 42 - ], - $person->getAdditionalProperties(), - ); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/DateArrayTest.php b/seed/php-model/allof/tests/Core/Json/DateArrayTest.php deleted file mode 100644 index e7794d652432..000000000000 --- a/seed/php-model/allof/tests/Core/Json/DateArrayTest.php +++ /dev/null @@ -1,54 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTest extends TestCase -{ - public function testDateTimeInArrays(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ], - ); - - $object = DateArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php b/seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php deleted file mode 100644 index b5f217e01f76..000000000000 --- a/seed/php-model/allof/tests/Core/Json/EmptyArrayTest.php +++ /dev/null @@ -1,71 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArrayTest extends TestCase -{ - public function testEmptyArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ], - ); - - $object = EmptyArray::fromJson($expectedJson); - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/EnumTest.php b/seed/php-model/allof/tests/Core/Json/EnumTest.php deleted file mode 100644 index 72dc6f2cfa00..000000000000 --- a/seed/php-model/allof/tests/Core/Json/EnumTest.php +++ /dev/null @@ -1,77 +0,0 @@ -value; - } -} - -class ShapeType extends JsonSerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = JsonEncoder::encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ]); - - $actualJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $actualJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php b/seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php deleted file mode 100644 index 4c288378b48b..000000000000 --- a/seed/php-model/allof/tests/Core/Json/ExhaustiveTest.php +++ /dev/null @@ -1,197 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class Type extends JsonSerializableType -{ - /** - * @var Nested nestedType - */ - #[JsonProperty('nested_type')] - public Nested $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[Date(Date::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[Date(Date::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(Nested::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: Nested, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class ExhaustiveTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in Type. - */ - public function testExhaustive(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // Omit 'nullable_property' to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56Z', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> - ], - ); - - $object = Type::fromJson($expectedJson); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/InvalidTest.php b/seed/php-model/allof/tests/Core/Json/InvalidTest.php deleted file mode 100644 index 9d845ea113b8..000000000000 --- a/seed/php-model/allof/tests/Core/Json/InvalidTest.php +++ /dev/null @@ -1,42 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTest extends TestCase -{ - public function testInvalidJsonThrowsException(): void - { - $this->expectException(\TypeError::class); - $json = JsonEncoder::encode( - [ - 'integer_property' => 'not_an_integer' - ], - ); - Invalid::fromJson($json); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php deleted file mode 100644 index 8fbbeb939f02..000000000000 --- a/seed/php-model/allof/tests/Core/Json/NestedUnionArrayTest.php +++ /dev/null @@ -1,89 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArray extends JsonSerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTest extends TestCase -{ - public function testNestedUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ], - ); - - $object = NestedUnionArray::fromJson($expectedJson); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/NullPropertyTest.php b/seed/php-model/allof/tests/Core/Json/NullPropertyTest.php deleted file mode 100644 index ce20a2442825..000000000000 --- a/seed/php-model/allof/tests/Core/Json/NullPropertyTest.php +++ /dev/null @@ -1,53 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullProperty( - [ - "nonNullProperty" => "Test String", - "nullProperty" => null - ] - ); - - $serialized = $object->jsonSerialize(); - $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); - $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/NullableArrayTest.php b/seed/php-model/allof/tests/Core/Json/NullableArrayTest.php deleted file mode 100644 index d1749c434a4c..000000000000 --- a/seed/php-model/allof/tests/Core/Json/NullableArrayTest.php +++ /dev/null @@ -1,49 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTest extends TestCase -{ - public function testNullableArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nullable_string_array' => ['one', null, 'three'] - ], - ); - - $object = NullableArray::fromJson($expectedJson); - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/ScalarTest.php b/seed/php-model/allof/tests/Core/Json/ScalarTest.php deleted file mode 100644 index ad4db0251bb5..000000000000 --- a/seed/php-model/allof/tests/Core/Json/ScalarTest.php +++ /dev/null @@ -1,116 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats - ], - ); - - $object = Scalar::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/TraitTest.php b/seed/php-model/allof/tests/Core/Json/TraitTest.php deleted file mode 100644 index e18f06d4191b..000000000000 --- a/seed/php-model/allof/tests/Core/Json/TraitTest.php +++ /dev/null @@ -1,60 +0,0 @@ -integerProperty = $values['integerProperty']; - $this->stringProperty = $values['stringProperty']; - } -} - -class TraitTest extends TestCase -{ - public function testTraitPropertyAndString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'string_property' => 'Hello, World!', - ], - ); - - $object = TypeWithTrait::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/UnionArrayTest.php b/seed/php-model/allof/tests/Core/Json/UnionArrayTest.php deleted file mode 100644 index de20cf9fde1b..000000000000 --- a/seed/php-model/allof/tests/Core/Json/UnionArrayTest.php +++ /dev/null @@ -1,57 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class UnionArrayTest extends TestCase -{ - public function testUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00Z', - 2 => null, - 3 => 'Some String' - ] - ], - ); - - $object = UnionArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php b/seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php deleted file mode 100644 index f733062cfabc..000000000000 --- a/seed/php-model/allof/tests/Core/Json/UnionPropertyTest.php +++ /dev/null @@ -1,111 +0,0 @@ - 'integer'], UnionProperty::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionProperty - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => [1 => 100, 2 => 200] - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => new UnionProperty( - [ - 'complexUnion' => 'Nested String' - ] - ) - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $expectedJson = JsonEncoder::encode( - [], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 42 - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 'Some String' - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/allof-inline/.fern/metadata.json b/seed/php-sdk/allof-inline/.fern/metadata.json deleted file mode 100644 index 143d7b896167..000000000000 --- a/seed/php-sdk/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-php-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/.github/workflows/ci.yml b/seed/php-sdk/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 678eb6c9e141..000000000000 --- a/seed/php-sdk/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Build - run: | - composer build - - - name: Analyze - run: | - composer analyze - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Run Tests - run: | - composer test diff --git a/seed/php-sdk/allof-inline/.gitignore b/seed/php-sdk/allof-inline/.gitignore deleted file mode 100644 index 31a1aeb14f35..000000000000 --- a/seed/php-sdk/allof-inline/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.idea -.php-cs-fixer.cache -.phpunit.result.cache -composer.lock -vendor/ \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/README.md b/seed/php-sdk/allof-inline/README.md deleted file mode 100644 index 0b89aa3897c7..000000000000 --- a/seed/php-sdk/allof-inline/README.md +++ /dev/null @@ -1,169 +0,0 @@ -# Seed PHP Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPHP) -[![php shield](https://img.shields.io/badge/php-packagist-pink)](https://packagist.org/packages/seed/seed) - -The Seed PHP library provides convenient access to the Seed APIs from PHP. - -## Table of Contents - -- [Requirements](#requirements) -- [Installation](#installation) -- [Usage](#usage) -- [Environments](#environments) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Custom Client](#custom-client) - - [Retries](#retries) - - [Timeouts](#timeouts) -- [Contributing](#contributing) - -## Requirements - -This SDK requires PHP ^8.1. - -## Installation - -```sh -composer require seed/seed -``` - -## Usage - -Instantiate and use the client with the following: - -```php -createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); - -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```php -The SDK defaults to the `Default_` environment. To use a different environment, pass it to the client constructor: - -```php -use Seed\SeedClient; -use Seed\Environments; - -$client = new SeedClient( - token: '', - options: [ - 'baseUrl' => Environments::Staging->value - ] -); -``` - -Available environments: -- `Environments::Default_` -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), an exception will be thrown. - -```php -use Seed\Exceptions\SeedApiException; -use Seed\Exceptions\SeedException; - -try { - $response = $client->createRule(...); -} catch (SeedApiException $e) { - echo 'API Exception occurred: ' . $e->getMessage() . "\n"; - echo 'Status Code: ' . $e->getCode() . "\n"; - echo 'Response Body: ' . $e->getBody() . "\n"; - // Optionally, rethrow the exception or handle accordingly. -} -``` - -## Advanced - -### Custom Client - -This SDK is built to work with any HTTP client that implements the [PSR-18](https://www.php-fig.org/psr/psr-18/) `ClientInterface`. -By default, if no client is provided, the SDK will use `php-http/discovery` to find an installed HTTP client. -However, you can pass your own client that adheres to `ClientInterface`: - -```php -use Seed\SeedClient; - -// Pass any PSR-18 compatible HTTP client implementation. -// For example, using Guzzle: -$customClient = new \GuzzleHttp\Client([ - 'timeout' => 5.0, -]); - -$client = new SeedClient(options: [ - 'client' => $customClient -]); - -// Or using Symfony HttpClient: -// $customClient = (new \Symfony\Component\HttpClient\Psr18Client()) -// ->withOptions(['timeout' => 5.0]); -// -// $client = new SeedClient(options: [ -// 'client' => $customClient -// ]); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` request option to configure this behavior. - -```php -$response = $client->createRule( - ..., - options: [ - 'maxRetries' => 0 // Override maxRetries at the request level - ] -); -``` - -### Timeouts - -The SDK defaults to a 30 second timeout. Use the `timeout` option to configure this behavior. - -```php -$response = $client->createRule( - ..., - options: [ - 'timeout' => 3.0 // Override timeout at the request level - ] -); -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/php-sdk/allof-inline/composer.json b/seed/php-sdk/allof-inline/composer.json deleted file mode 100644 index ad30960a8764..000000000000 --- a/seed/php-sdk/allof-inline/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "seed/seed", - "version": "0.0.1", - "description": "Seed PHP Library", - "keywords": [ - "seed", - "api", - "sdk" - ], - "license": [], - "require": { - "php": "^8.1", - "ext-json": "*", - "psr/http-client": "^1.0", - "psr/http-client-implementation": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-factory-implementation": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "php-http/discovery": "^1.0", - "php-http/multipart-stream-builder": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0", - "friendsofphp/php-cs-fixer": "3.5.0", - "phpstan/phpstan": "^1.12", - "guzzlehttp/guzzle": "^7.4" - }, - "autoload": { - "psr-4": { - "Seed\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Seed\\Tests\\": "tests/" - } - }, - "scripts": { - "build": [ - "@php -l src", - "@php -l tests" - ], - "test": "phpunit", - "analyze": "phpstan analyze src tests --memory-limit=1G" - } -} \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/phpstan.neon b/seed/php-sdk/allof-inline/phpstan.neon deleted file mode 100644 index 780706b8f8a2..000000000000 --- a/seed/php-sdk/allof-inline/phpstan.neon +++ /dev/null @@ -1,6 +0,0 @@ -parameters: - level: max - reportUnmatchedIgnoredErrors: false - paths: - - src - - tests \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/phpunit.xml b/seed/php-sdk/allof-inline/phpunit.xml deleted file mode 100644 index 54630a51163c..000000000000 --- a/seed/php-sdk/allof-inline/phpunit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - tests - - - \ No newline at end of file diff --git a/seed/php-sdk/allof-inline/reference.md b/seed/php-sdk/allof-inline/reference.md deleted file mode 100644 index b824179f7851..000000000000 --- a/seed/php-sdk/allof-inline/reference.md +++ /dev/null @@ -1,171 +0,0 @@ -# Reference -
$client->searchRuleTypes($request) -> ?RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->searchRuleTypes( - new SearchRuleTypesRequest([]), -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**$query:** `?string` - -
-
-
-
- - -
-
-
- -
$client->createRule($request) -> ?RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**$name:** `string` - -
-
- -
-
- -**$executionContext:** `string` - -
-
-
-
- - -
-
-
- -
$client->listUsers() -> ?UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->listUsers(); -``` -
-
-
-
- - -
-
-
- -
$client->getEntity() -> ?CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->getEntity(); -``` -
-
-
-
- - -
-
-
- -
$client->getOrganization() -> ?Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->getOrganization(); -``` -
-
-
-
- - -
-
-
- diff --git a/seed/php-sdk/allof-inline/snippet.json b/seed/php-sdk/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php deleted file mode 100644 index 5e1283e2b6f6..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Client/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php b/seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php deleted file mode 100644 index 8ac806af0325..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Client/HttpClientBuilder.php +++ /dev/null @@ -1,56 +0,0 @@ - - */ - private array $responses = []; - - /** - * @var array - */ - private array $requests = []; - - /** - * @param ResponseInterface ...$responses - */ - public function append(ResponseInterface ...$responses): void - { - foreach ($responses as $response) { - $this->responses[] = $response; - } - } - - /** - * @param RequestInterface $request - * @return ResponseInterface - */ - public function sendRequest(RequestInterface $request): ResponseInterface - { - $this->requests[] = $request; - - if (empty($this->responses)) { - throw new RuntimeException('No more responses in the queue. Add responses using append().'); - } - - return array_shift($this->responses); - } - - /** - * @return ?RequestInterface - */ - public function getLastRequest(): ?RequestInterface - { - if (empty($this->requests)) { - return null; - } - return $this->requests[count($this->requests) - 1]; - } - - /** - * @return int - */ - public function getRequestCount(): int - { - return count($this->requests); - } - - /** - * Returns the number of remaining responses in the queue. - * - * @return int - */ - public function count(): int - { - return count($this->responses); - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Client/RawClient.php b/seed/php-sdk/allof-inline/src/Core/Client/RawClient.php deleted file mode 100644 index 14716c7d678b..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Client/RawClient.php +++ /dev/null @@ -1,310 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @var ?(callable(): array) $getAuthHeaders - */ - private $getAuthHeaders; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * getAuthHeaders?: callable(): array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = HttpClientBuilder::build( - $this->options['client'] ?? null, - $this->options['maxRetries'] ?? 2, - ); - $this->requestFactory = HttpClientBuilder::requestFactory(); - $this->streamFactory = HttpClientBuilder::streamFactory(); - $this->headers = $this->options['headers'] ?? []; - $this->getAuthHeaders = $this->options['getAuthHeaders'] ?? null; - } - - /** - * @param BaseApiRequest $request - * @param ?array{ - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ?array $options = null, - ): ResponseInterface { - $opts = $options ?? []; - $httpRequest = $this->buildRequest($request, $opts); - - $timeout = $opts['timeout'] ?? $this->options['timeout'] ?? null; - $maxRetries = $opts['maxRetries'] ?? null; - - return $this->client->send($httpRequest, $timeout, $maxRetries); - } - - /** - * @param BaseApiRequest $request - * @param array{ - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return RequestInterface - */ - private function buildRequest( - BaseApiRequest $request, - array $options - ): RequestInterface { - $url = $this->buildUrl($request, $options); - $headers = $this->encodeHeaders($request, $options); - - $httpRequest = $this->requestFactory->createRequest( - $request->method->name, - $url, - ); - - // Encode body and, for multipart, capture the Content-Type with boundary. - if ($request instanceof MultipartApiRequest && $request->body !== null) { - $builder = new MultipartStreamBuilder($this->streamFactory); - $request->body->addToBuilder($builder); - $httpRequest = $httpRequest->withBody($builder->build()); - $headers['Content-Type'] = "multipart/form-data; boundary={$builder->getBoundary()}"; - } else { - $body = $this->encodeRequestBody($request, $options); - if ($body !== null) { - $httpRequest = $httpRequest->withBody($body); - } - } - - foreach ($headers as $name => $value) { - $httpRequest = $httpRequest->withHeader($name, $value); - } - - return $httpRequest; - } - - /** - * @param BaseApiRequest $request - * @param array{ - * headers?: array, - * } $options - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request, - array $options, - ): array { - $authHeaders = $this->getAuthHeaders !== null ? ($this->getAuthHeaders)() : []; - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - [ - "Content-Type" => "application/json", - "Accept" => "*/*", - ], - $this->headers, - $authHeaders, - $request->headers, - $options['headers'] ?? [], - ), - MultipartApiRequest::class => array_merge( - $this->headers, - $authHeaders, - $request->headers, - $options['headers'] ?? [], - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - /** - * @param BaseApiRequest $request - * @param array{ - * bodyProperties?: array, - * } $options - * @return ?StreamInterface - */ - private function encodeRequestBody( - BaseApiRequest $request, - array $options, - ): ?StreamInterface { - if ($request instanceof JsonApiRequest) { - return $request->body === null ? null : $this->streamFactory->createStream( - JsonEncoder::encode( - $this->buildJsonBody( - $request->body, - $options, - ), - ) - ); - } - - if ($request instanceof MultipartApiRequest) { - return null; - } - - throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)); - } - - /** - * @param mixed $body - * @param array{ - * bodyProperties?: array, - * } $options - * @return mixed - */ - private function buildJsonBody( - mixed $body, - array $options, - ): mixed { - $overrideProperties = $options['bodyProperties'] ?? []; - if (is_array($body) && (empty($body) || self::isSequential($body))) { - return array_merge($body, $overrideProperties); - } - - if ($body instanceof JsonSerializable) { - $result = $body->jsonSerialize(); - } else { - $result = $body; - } - if (is_array($result)) { - $result = array_merge($result, $overrideProperties); - if (empty($result)) { - // force to be serialized as {} instead of [] - return (object)($result); - } - } - - return $result; - } - - /** - * @param BaseApiRequest $request - * @param array{ - * queryParameters?: array, - * } $options - * @return string - */ - private function buildUrl( - BaseApiRequest $request, - array $options, - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - $query = array_merge( - $request->query, - $options['queryParameters'] ?? [], - ); - if (!empty($query)) { - $url .= '?' . $this->encodeQuery($query); - } - return $url; - } - - /** - * @param array $query - * @return string - */ - private function encodeQuery(array $query): string - { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue(mixed $value): string - { - if (is_string($value)) { - return urlencode($value); - } - if (is_bool($value)) { - return $value ? 'true' : 'false'; - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(JsonEncoder::encode($value)); - } - - /** - * Check if an array is sequential, not associative. - * @param mixed[] $arr - * @return bool - */ - private static function isSequential(array $arr): bool - { - if (empty($arr)) { - return false; - } - $length = count($arr); - $keys = array_keys($arr); - for ($i = 0; $i < $length; $i++) { - if ($keys[$i] !== $i) { - return false; - } - } - return true; - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php deleted file mode 100644 index b16170cf2805..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php +++ /dev/null @@ -1,241 +0,0 @@ -client = $client; - $this->maxRetries = $maxRetries; - $this->baseDelay = $baseDelay; - $this->sleepFunction = $sleepFunction ?? 'usleep'; - } - - /** - * @param RequestInterface $request - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - public function sendRequest(RequestInterface $request): ResponseInterface - { - return $this->send($request); - } - - /** - * Sends a request with optional per-request timeout and retry overrides. - * - * When a Guzzle or Symfony PSR-18 client is detected, the timeout is - * forwarded via the client's native API. For other PSR-18 clients the - * timeout value is silently ignored. - * - * @param RequestInterface $request - * @param ?float $timeout Timeout in seconds, or null to use the client default. - * @param ?int $maxRetries Maximum retry attempts, or null to use the client default. - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - public function send( - RequestInterface $request, - ?float $timeout = null, - ?int $maxRetries = null, - ): ResponseInterface { - $maxRetries = $maxRetries ?? $this->maxRetries; - $retryAttempt = 0; - $lastResponse = null; - - while (true) { - try { - $lastResponse = $this->doSend($request, $timeout); - if (!$this->shouldRetry($retryAttempt, $maxRetries, $lastResponse)) { - return $lastResponse; - } - } catch (ClientExceptionInterface $e) { - if ($retryAttempt >= $maxRetries) { - throw $e; - } - } - - $retryAttempt++; - $delay = $this->getRetryDelay($retryAttempt, $lastResponse); - ($this->sleepFunction)($delay * 1000); // Convert milliseconds to microseconds - - // Rewind the request body so retries don't send an empty body. - $request->getBody()->rewind(); - } - } - - /** - * Dispatches the request to the underlying client, forwarding the timeout - * option to Guzzle or Symfony when available. - * - * @param RequestInterface $request - * @param ?float $timeout - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - private function doSend(RequestInterface $request, ?float $timeout): ResponseInterface - { - static $warned = false; - - if ($timeout === null) { - return $this->client->sendRequest($request); - } - - if (class_exists('GuzzleHttp\ClientInterface') - && $this->client instanceof \GuzzleHttp\ClientInterface - ) { - return $this->client->send($request, ['timeout' => $timeout]); - } - if (class_exists('Symfony\Component\HttpClient\Psr18Client') - && $this->client instanceof \Symfony\Component\HttpClient\Psr18Client - ) { - /** @var ClientInterface $clientWithTimeout */ - $clientWithTimeout = $this->client->withOptions(['timeout' => $timeout]); - return $clientWithTimeout->sendRequest($request); - } - - if ($warned) { - return $this->client->sendRequest($request); - } - $warned = true; - trigger_error( - 'Timeout option is not supported for the current PSR-18 client (' - . get_class($this->client) - . '). Use Guzzle or Symfony HttpClient for timeout support.', - E_USER_WARNING, - ); - return $this->client->sendRequest($request); - } - - /** - * @param int $retryAttempt - * @param int $maxRetries - * @param ?ResponseInterface $response - * @return bool - */ - private function shouldRetry( - int $retryAttempt, - int $maxRetries, - ?ResponseInterface $response = null, - ): bool { - if ($retryAttempt >= $maxRetries) { - return false; - } - - if ($response !== null) { - return $response->getStatusCode() >= 500 || - in_array($response->getStatusCode(), self::RETRY_STATUS_CODES); - } - - return false; - } - - /** - * Calculate the retry delay based on response headers or exponential backoff. - * - * @param int $retryAttempt - * @param ?ResponseInterface $response - * @return int milliseconds - */ - private function getRetryDelay(int $retryAttempt, ?ResponseInterface $response): int - { - if ($response !== null) { - // Check Retry-After header - $retryAfter = $response->getHeaderLine('Retry-After'); - if ($retryAfter !== '') { - // Try parsing as integer (seconds) - if (is_numeric($retryAfter)) { - $retryAfterSeconds = (int)$retryAfter; - if ($retryAfterSeconds > 0) { - return min($retryAfterSeconds * 1000, self::MAX_RETRY_DELAY); - } - } - - // Try parsing as HTTP date - $retryAfterDate = strtotime($retryAfter); - if ($retryAfterDate !== false) { - $delay = ($retryAfterDate - time()) * 1000; - if ($delay > 0) { - return min(max($delay, 0), self::MAX_RETRY_DELAY); - } - } - } - - // Check X-RateLimit-Reset header - $rateLimitReset = $response->getHeaderLine('X-RateLimit-Reset'); - if ($rateLimitReset !== '' && is_numeric($rateLimitReset)) { - $resetTime = (int)$rateLimitReset; - $delay = ($resetTime * 1000) - (int)(microtime(true) * 1000); - if ($delay > 0) { - return $this->addPositiveJitter(min($delay, self::MAX_RETRY_DELAY)); - } - } - } - - // Fall back to exponential backoff with symmetric jitter - return $this->addSymmetricJitter( - min($this->exponentialDelay($retryAttempt), self::MAX_RETRY_DELAY) - ); - } - - /** - * Add positive jitter (0% to +20%) to the delay. - * - * @param int $delay - * @return int - */ - private function addPositiveJitter(int $delay): int - { - $jitterMultiplier = 1 + (mt_rand() / mt_getrandmax()) * self::JITTER_FACTOR; - return (int)($delay * $jitterMultiplier); - } - - /** - * Add symmetric jitter (-10% to +10%) to the delay. - * - * @param int $delay - * @return int - */ - private function addSymmetricJitter(int $delay): int - { - $jitterMultiplier = 1 + ((mt_rand() / mt_getrandmax()) - 0.5) * self::JITTER_FACTOR; - return (int)($delay * $jitterMultiplier); - } - - /** - * Default exponential backoff delay function. - * - * @return int milliseconds. - */ - private function exponentialDelay(int $retryAttempt): int - { - return 2 ** ($retryAttempt - 1) * $this->baseDelay; - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php deleted file mode 100644 index 8fdf493606e6..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Json/JsonApiRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php deleted file mode 100644 index 2da34087c644..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Json/JsonDecoder.php +++ /dev/null @@ -1,161 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: $json"); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php deleted file mode 100644 index 1a250c614e45..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Json/JsonDeserializer.php +++ /dev/null @@ -1,218 +0,0 @@ - $data The array to be deserialized. - * @param array $type The type definition from the annotation. - * @return array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (\Throwable) { - // Catching Throwable instead of Exception to handle TypeError - // that occurs when assigning null to non-nullable typed properties - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: $type" - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - /** @var array $data */ - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement JsonSerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, JsonSerializableType::class)) { - throw new JsonException("$type is not a subclass of JsonSerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php deleted file mode 100644 index 0dbf3fcc9948..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Json/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ - Extra properties from JSON that don't map to class properties */ - private array $__additionalProperties = []; - - /** @var array Properties that have been explicitly set via setter methods */ - private array $__explicitlySetProperties = []; - - /** - * Serializes the object to a JSON string. - * - * @return string JSON-encoded string representation of the object. - * @throws Exception If encoding fails. - */ - public function toJson(): string - { - $serializedObject = $this->jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey === null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === Date::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - // Include the value if it's not null, OR if it was explicitly set (even to null) - if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { - $result[$jsonKey] = $value; - } - } - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - /** @var array $decodedJson */ - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - $properties = []; - $additionalProperties = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - $properties[$jsonKey] = $property; - } - - foreach ($data as $jsonKey => $value) { - if (!isset($properties[$jsonKey])) { - // This JSON key doesn't map to any class property - add it to additionalProperties - $additionalProperties[$jsonKey] = $value; - continue; - } - - $property = $properties[$jsonKey]; - - // Handle Date annotation - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === Date::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle Array annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - /** @var array $arrayValue */ - $arrayValue = $value; - $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); - } - - $args[$property->getName()] = $value; - } - - // Fill in any missing properties with defaults - foreach ($properties as $property) { - if (!isset($args[$property->getName()])) { - $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; - } - } - - // @phpstan-ignore-next-line - $result = new static($args); - $result->__additionalProperties = $additionalProperties; - return $result; - } - - /** - * Get properties from JSON that weren't mapped to class fields - * @return array - */ - public function getAdditionalProperties(): array - { - return $this->__additionalProperties; - } - - /** - * Mark a property as explicitly set. - * This ensures the property will be included in JSON serialization even if null. - * - * @param string $propertyName The name of the property to mark as explicitly set. - */ - protected function _setField(string $propertyName): void - { - $this->__explicitlySetProperties[$propertyName] = true; - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php b/seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php deleted file mode 100644 index f7d80ed5e8f3..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Json/JsonSerializer.php +++ /dev/null @@ -1,205 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - $formatted = $date->format(Constant::DateTimeFormat); - if (str_ends_with($formatted, '+00:00')) { - return substr($formatted, 0, -6) . 'Z'; - } - return $formatted; - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param array $data The array to be serialized. - * @param array $type The type definition from the annotation. - * @return array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: $unionType" - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Json/Utils.php b/seed/php-sdk/allof-inline/src/Core/Json/Utils.php deleted file mode 100644 index 4099b8253005..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Json/Utils.php +++ /dev/null @@ -1,62 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return int|string The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): int|string - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - // PHP arrays don't support float keys; truncate to int - 'float' => (int)$key, - 'string' => (string)$key, - default => is_int($key) ? $key : (string)$key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php deleted file mode 100644 index 7760366456c8..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartApiRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param ?MultipartFormData $body The multipart form data for the request (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly ?MultipartFormData $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php deleted file mode 100644 index 911a28b6ad64..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormData.php +++ /dev/null @@ -1,58 +0,0 @@ - - */ - private array $parts = []; - - /** - * Adds a new part to the multipart form data. - * - * @param string $name - * @param string|int|bool|float|StreamInterface $value - * @param ?string $contentType - */ - public function add( - string $name, - string|int|bool|float|StreamInterface $value, - ?string $contentType = null, - ): void { - $headers = $contentType !== null ? ['Content-Type' => $contentType] : null; - $this->addPart( - new MultipartFormDataPart( - name: $name, - value: $value, - headers: $headers, - ) - ); - } - - /** - * Adds a new part to the multipart form data. - * - * @param MultipartFormDataPart $part - */ - public function addPart(MultipartFormDataPart $part): void - { - $this->parts[] = $part; - } - - /** - * Adds all parts to a MultipartStreamBuilder. - * - * @param MultipartStreamBuilder $builder - */ - public function addToBuilder(MultipartStreamBuilder $builder): void - { - foreach ($this->parts as $part) { - $part->addToBuilder($builder); - } - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php b/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php deleted file mode 100644 index 4db35e58ae37..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Multipart/MultipartFormDataPart.php +++ /dev/null @@ -1,62 +0,0 @@ - - */ - private ?array $headers; - - /** - * @param string $name - * @param string|bool|float|int|StreamInterface $value - * @param ?string $filename - * @param ?array $headers - */ - public function __construct( - string $name, - string|bool|float|int|StreamInterface $value, - ?string $filename = null, - ?array $headers = null - ) { - $this->name = $name; - $this->contents = $value instanceof StreamInterface ? $value : (string)$value; - $this->filename = $filename; - $this->headers = $headers; - } - - /** - * Adds this part to a MultipartStreamBuilder. - * - * @param MultipartStreamBuilder $builder - */ - public function addToBuilder(MultipartStreamBuilder $builder): void - { - $options = array_filter([ - 'filename' => $this->filename, - 'headers' => $this->headers, - ], fn ($value) => $value !== null); - - $builder->addResource($this->name, $this->contents, $options); - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php b/seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php deleted file mode 100644 index a26d29008ec3..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Types/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/allof-inline/src/Core/Types/Constant.php b/seed/php-sdk/allof-inline/src/Core/Types/Constant.php deleted file mode 100644 index 5ac4518cc6d6..000000000000 --- a/seed/php-sdk/allof-inline/src/Core/Types/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/allof-inline/src/Environments.php b/seed/php-sdk/allof-inline/src/Environments.php deleted file mode 100644 index 43d64cdb5e37..000000000000 --- a/seed/php-sdk/allof-inline/src/Environments.php +++ /dev/null @@ -1,8 +0,0 @@ -body = $body; - parent::__construct($message, $statusCode, $previous); - } - - /** - * Returns the body of the response that triggered the exception. - * - * @return mixed - */ - public function getBody(): mixed - { - return $this->body; - } - - /** - * @return string - */ - public function __toString(): string - { - if (empty($this->body)) { - return $this->message . '; Status Code: ' . $this->getCode() . "\n"; - } - return $this->message . '; Status Code: ' . $this->getCode() . '; Body: ' . print_r($this->body, true) . "\n"; - } -} diff --git a/seed/php-sdk/allof-inline/src/Exceptions/SeedException.php b/seed/php-sdk/allof-inline/src/Exceptions/SeedException.php deleted file mode 100644 index 457035276737..000000000000 --- a/seed/php-sdk/allof-inline/src/Exceptions/SeedException.php +++ /dev/null @@ -1,12 +0,0 @@ - $executionContext - */ - #[JsonProperty('executionContext')] - public string $executionContext; - - /** - * @param array{ - * name: string, - * executionContext: value-of, - * } $values - */ - public function __construct( - array $values, - ) { - $this->name = $values['name']; - $this->executionContext = $values['executionContext']; - } -} diff --git a/seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php b/seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php deleted file mode 100644 index bd49b88a3872..000000000000 --- a/seed/php-sdk/allof-inline/src/Requests/SearchRuleTypesRequest.php +++ /dev/null @@ -1,24 +0,0 @@ -query = $values['query'] ?? null; - } -} diff --git a/seed/php-sdk/allof-inline/src/SeedClient.php b/seed/php-sdk/allof-inline/src/SeedClient.php deleted file mode 100644 index 63b560f9b6c7..000000000000 --- a/seed/php-sdk/allof-inline/src/SeedClient.php +++ /dev/null @@ -1,302 +0,0 @@ -, - * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator - */ - private array $options; - - /** - * @var RawClient $client - */ - private RawClient $client; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * } $options - */ - public function __construct( - ?array $options = null, - ) { - $defaultHeaders = [ - 'X-Fern-Language' => 'PHP', - 'X-Fern-SDK-Name' => 'Seed', - 'X-Fern-SDK-Version' => '0.0.1', - 'User-Agent' => 'seed/seed/0.0.1', - ]; - - $this->options = $options ?? []; - - $this->options['headers'] = array_merge( - $defaultHeaders, - $this->options['headers'] ?? [], - ); - - $this->client = new RawClient( - options: $this->options, - ); - } - - /** - * @param SearchRuleTypesRequest $request - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?RuleTypeSearchResponse - * @throws SeedException - * @throws SeedApiException - */ - public function searchRuleTypes(SearchRuleTypesRequest $request = new SearchRuleTypesRequest(), ?array $options = null): ?RuleTypeSearchResponse - { - $options = array_merge($this->options, $options ?? []); - $query = []; - if ($request->query != null) { - $query['query'] = $request->query; - } - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "rule-types", - method: HttpMethod::GET, - query: $query, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return RuleTypeSearchResponse::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param RuleCreateRequest $request - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?RuleResponse - * @throws SeedException - * @throws SeedApiException - */ - public function createRule(RuleCreateRequest $request, ?array $options = null): ?RuleResponse - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "rules", - method: HttpMethod::POST, - body: $request, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return RuleResponse::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?UserSearchResponse - * @throws SeedException - * @throws SeedApiException - */ - public function listUsers(?array $options = null): ?UserSearchResponse - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "users", - method: HttpMethod::GET, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return UserSearchResponse::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?CombinedEntity - * @throws SeedException - * @throws SeedApiException - */ - public function getEntity(?array $options = null): ?CombinedEntity - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "entities", - method: HttpMethod::GET, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return CombinedEntity::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?Organization - * @throws SeedException - * @throws SeedApiException - */ - public function getOrganization(?array $options = null): ?Organization - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "organizations", - method: HttpMethod::GET, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return Organization::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/AuditInfo.php b/seed/php-sdk/allof-inline/src/Types/AuditInfo.php deleted file mode 100644 index 49fd43fd80f3..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/AuditInfo.php +++ /dev/null @@ -1,63 +0,0 @@ -createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/BaseOrg.php b/seed/php-sdk/allof-inline/src/Types/BaseOrg.php deleted file mode 100644 index 5d403a06b445..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/BaseOrg.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php b/seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php deleted file mode 100644 index e974fa97bb1a..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/BaseOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->tier = $values['tier'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/CombinedEntity.php b/seed/php-sdk/allof-inline/src/Types/CombinedEntity.php deleted file mode 100644 index 53a5e19ec1e6..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/CombinedEntity.php +++ /dev/null @@ -1,58 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @param array{ - * id: string, - * status: value-of, - * name?: ?string, - * summary?: ?string, - * } $values - */ - public function __construct( - array $values, - ) { - $this->id = $values['id']; - $this->name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - $this->status = $values['status']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php b/seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php deleted file mode 100644 index 63aba6a34b11..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/CombinedEntityStatus.php +++ /dev/null @@ -1,9 +0,0 @@ -name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/DetailedOrg.php b/seed/php-sdk/allof-inline/src/Types/DetailedOrg.php deleted file mode 100644 index c53ac92b926a..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/DetailedOrg.php +++ /dev/null @@ -1,34 +0,0 @@ -metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php b/seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php deleted file mode 100644 index ea98caf5f68a..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/DetailedOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->domain = $values['domain'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/Identifiable.php b/seed/php-sdk/allof-inline/src/Types/Identifiable.php deleted file mode 100644 index 83c1714a7eb8..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/Identifiable.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->name = $values['name'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/Organization.php b/seed/php-sdk/allof-inline/src/Types/Organization.php deleted file mode 100644 index 823e2cf645ac..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/Organization.php +++ /dev/null @@ -1,50 +0,0 @@ -id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - $this->name = $values['name']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php b/seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php deleted file mode 100644 index feed3e97ef52..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/OrganizationMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->domain = $values['domain'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/PaginatedResult.php b/seed/php-sdk/allof-inline/src/Types/PaginatedResult.php deleted file mode 100644 index c24634881db3..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/PaginatedResult.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType(['mixed'])] - public array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/PagingCursors.php b/seed/php-sdk/allof-inline/src/Types/PagingCursors.php deleted file mode 100644 index b4a3809a1dbb..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/PagingCursors.php +++ /dev/null @@ -1,42 +0,0 @@ -next = $values['next']; - $this->previous = $values['previous'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php b/seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php deleted file mode 100644 index bc98b8b52369..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/RuleExecutionContext.php +++ /dev/null @@ -1,10 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @var ?value-of $executionContext - */ - #[JsonProperty('executionContext')] - public ?string $executionContext; - - /** - * @param array{ - * id: string, - * name: string, - * status: value-of, - * createdBy?: ?string, - * createdDateTime?: ?DateTime, - * modifiedBy?: ?string, - * modifiedDateTime?: ?DateTime, - * executionContext?: ?value-of, - * } $values - */ - public function __construct( - array $values, - ) { - $this->createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - $this->id = $values['id']; - $this->name = $values['name']; - $this->status = $values['status']; - $this->executionContext = $values['executionContext'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php b/seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php deleted file mode 100644 index cef363294474..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/RuleResponseStatus.php +++ /dev/null @@ -1,10 +0,0 @@ -id = $values['id']; - $this->name = $values['name']; - $this->description = $values['description'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php b/seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php deleted file mode 100644 index af3e5734a366..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/RuleTypeSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([RuleType::class])] - public ?array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/User.php b/seed/php-sdk/allof-inline/src/Types/User.php deleted file mode 100644 index aab3174ca08c..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/User.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->email = $values['email']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php b/seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php deleted file mode 100644 index 7c572643b3c1..000000000000 --- a/seed/php-sdk/allof-inline/src/Types/UserSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([User::class])] - public ?array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof-inline/src/Utils/File.php b/seed/php-sdk/allof-inline/src/Utils/File.php deleted file mode 100644 index ee2af27b8909..000000000000 --- a/seed/php-sdk/allof-inline/src/Utils/File.php +++ /dev/null @@ -1,129 +0,0 @@ -filename = $filename; - $this->contentType = $contentType; - $this->stream = $stream; - } - - /** - * Creates a File instance from a filepath. - * - * @param string $filepath - * @param ?string $filename - * @param ?string $contentType - * @return File - * @throws Exception - */ - public static function createFromFilepath( - string $filepath, - ?string $filename = null, - ?string $contentType = null, - ): File { - $resource = @fopen($filepath, 'r'); - if (!$resource) { - throw new Exception("Unable to open file $filepath"); - } - $stream = Psr17FactoryDiscovery::findStreamFactory()->createStreamFromResource($resource); - if (!$stream->isReadable()) { - throw new Exception("File $filepath is not readable"); - } - return new self( - stream: $stream, - filename: $filename ?? basename($filepath), - contentType: $contentType, - ); - } - - /** - * Creates a File instance from a string. - * - * @param string $content - * @param ?string $filename - * @param ?string $contentType - * @return File - */ - public static function createFromString( - string $content, - ?string $filename, - ?string $contentType = null, - ): File { - return new self( - stream: Psr17FactoryDiscovery::findStreamFactory()->createStream($content), - filename: $filename, - contentType: $contentType, - ); - } - - /** - * Maps this File into a multipart form data part. - * - * @param string $name The name of the multipart form data part. - * @param ?string $contentType Overrides the Content-Type associated with the file, if any. - * @return MultipartFormDataPart - */ - public function toMultipartFormDataPart(string $name, ?string $contentType = null): MultipartFormDataPart - { - $contentType ??= $this->contentType; - $headers = $contentType !== null - ? ['Content-Type' => $contentType] - : null; - - return new MultipartFormDataPart( - name: $name, - value: $this->stream, - filename: $this->filename, - headers: $headers, - ); - } - - /** - * Closes the file stream. - */ - public function close(): void - { - $this->stream->close(); - } - - /** - * Destructor to ensure stream is closed. - */ - public function __destruct() - { - try { - $this->close(); - } catch (\Throwable) { - // Swallow errors during garbage collection to avoid fatal errors. - } - } -} diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php deleted file mode 100644 index 1e384a760b83..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example0/snippet.php +++ /dev/null @@ -1,15 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->searchRuleTypes( - new SearchRuleTypesRequest([]), -); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php deleted file mode 100644 index 826582f7bc27..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example1/snippet.php +++ /dev/null @@ -1,17 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->searchRuleTypes( - new SearchRuleTypesRequest([ - 'query' => 'query', - ]), -); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php deleted file mode 100644 index 5539210a6505..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example2/snippet.php +++ /dev/null @@ -1,19 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php deleted file mode 100644 index 5539210a6505..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example3/snippet.php +++ /dev/null @@ -1,19 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php deleted file mode 100644 index 477f8f61c34c..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example4/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->listUsers(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php deleted file mode 100644 index 477f8f61c34c..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example5/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->listUsers(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php deleted file mode 100644 index 4b4021abb4a6..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example6/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getEntity(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php deleted file mode 100644 index 4b4021abb4a6..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example7/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getEntity(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php deleted file mode 100644 index bcf929bea40c..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example8/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getOrganization(); diff --git a/seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php b/seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php deleted file mode 100644 index bcf929bea40c..000000000000 --- a/seed/php-sdk/allof-inline/src/dynamic-snippets/example9/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getOrganization(); diff --git a/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php b/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php deleted file mode 100644 index df36dc918894..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php +++ /dev/null @@ -1,1074 +0,0 @@ -name = $values['name']; - } - - /** - * @return string - */ - public function getName(): ?string - { - return $this->name; - } -} - -class RawClientTest extends TestCase -{ - private string $baseUrl = 'https://api.example.com'; - private MockHttpClient $mockClient; - private RawClient $rawClient; - - protected function setUp(): void - { - $this->mockClient = new MockHttpClient(); - $this->rawClient = new RawClient(['client' => $this->mockClient, 'maxRetries' => 0]); - } - - /** - * @throws ClientExceptionInterface - */ - public function testHeaders(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - /** - * @throws ClientExceptionInterface - */ - public function testQueryParameters(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - /** - * @throws ClientExceptionInterface - */ - public function testJsonBody(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(JsonEncoder::encode($body), (string)$lastRequest->getBody()); - } - - public function testAdditionalHeaders(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = new JsonRequest([ - 'name' => 'john.doe' - ]); - $headers = [ - 'X-API-Version' => '1.0.0', - ]; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - $headers, - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'headers' => [ - 'X-Tenancy' => 'test' - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('1.0.0', $lastRequest->getHeaderLine('X-API-Version')); - $this->assertEquals('test', $lastRequest->getHeaderLine('X-Tenancy')); - } - - public function testOverrideAdditionalHeaders(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = new JsonRequest([ - 'name' => 'john.doe' - ]); - $headers = [ - 'X-API-Version' => '1.0.0', - ]; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - $headers, - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'headers' => [ - 'X-API-Version' => '2.0.0' - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('2.0.0', $lastRequest->getHeaderLine('X-API-Version')); - } - - public function testAdditionalBodyProperties(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = new JsonRequest([ - 'name' => 'john.doe' - ]); - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'bodyProperties' => [ - 'age' => 42 - ] - ] - ); - - $expectedJson = [ - 'name' => 'john.doe', - 'age' => 42 - ]; - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); - } - - public function testOverrideAdditionalBodyProperties(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = [ - 'name' => 'john.doe' - ]; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'bodyProperties' => [ - 'name' => 'jane.doe' - ] - ] - ); - - $expectedJson = [ - 'name' => 'jane.doe', - ]; - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); - } - - public function testAdditionalQueryParameters(): void - { - $this->mockClient->append(self::createResponse(200)); - - $query = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - $query, - [] - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'queryParameters' => [ - 'extra' => 42 - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('key=value&extra=42', $lastRequest->getUri()->getQuery()); - } - - public function testOverrideQueryParameters(): void - { - $this->mockClient->append(self::createResponse(200)); - - $query = ['key' => 'invalid']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - $query, - [] - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'queryParameters' => [ - 'key' => 'value' - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('key=value', $lastRequest->getUri()->getQuery()); - } - - public function testDefaultRetries(): void - { - $this->mockClient->append(self::createResponse(500)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET - ); - - $response = $this->rawClient->sendRequest($request); - $this->assertEquals(500, $response->getStatusCode()); - $this->assertEquals(0, $this->mockClient->count()); - } - - /** - * @throws ClientExceptionInterface - */ - public function testExplicitRetriesSuccess(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(200)); - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(0, $mockClient->count()); - } - - public function testExplicitRetriesFailure(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(500)); - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(500, $response->getStatusCode()); - $this->assertEquals(0, $mockClient->count()); - } - - /** - * @throws ClientExceptionInterface - */ - public function testShouldRetryOnStatusCodes(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(408), - self::createResponse(429), - self::createResponse(500), - self::createResponse(501), - self::createResponse(502), - self::createResponse(503), - self::createResponse(504), - self::createResponse(505), - self::createResponse(599), - self::createResponse(200), - ); - $countOfErrorRequests = $mockClient->count() - 1; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: $countOfErrorRequests, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(0, $mockClient->count()); - } - - public function testShouldFailOn400Response(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(400), self::createResponse(200)); - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(400, $response->getStatusCode()); - $this->assertEquals(1, $mockClient->count()); - } - - public function testRetryAfterSecondsHeaderControlsDelay(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, ['Retry-After' => '10']), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); // Convert microseconds to milliseconds - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(10000, $capturedDelays[0]); - $this->assertLessThanOrEqual(12000, $capturedDelays[0]); - } - - public function testRetryAfterHttpDateHeaderIsHandled(): void - { - $retryAfterDate = gmdate('D, d M Y H:i:s \G\M\T', time() + 5); - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, ['Retry-After' => $retryAfterDate]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThan(0, $capturedDelays[0]); - $this->assertLessThanOrEqual(60000, $capturedDelays[0]); - } - - public function testRateLimitResetHeaderControlsDelay(): void - { - $resetTime = (int) floor(microtime(true)) + 5; - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThan(0, $capturedDelays[0]); - $this->assertLessThanOrEqual(60000, $capturedDelays[0]); - } - - public function testRateLimitResetHeaderRespectsMaxDelayAndPositiveJitter(): void - { - $resetTime = (int) floor(microtime(true)) + 1000; - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 1, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); - $this->assertLessThanOrEqual(72000, $capturedDelays[0]); - } - - public function testExponentialBackoffWithSymmetricJitterWhenNoHeaders(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 1, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(900, $capturedDelays[0]); - $this->assertLessThanOrEqual(1100, $capturedDelays[0]); - } - - public function testRetryAfterHeaderTakesPrecedenceOverRateLimitReset(): void - { - $resetTime = (int) floor(microtime(true)) + 30; - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, [ - 'Retry-After' => '5', - 'X-RateLimit-Reset' => (string) $resetTime, - ]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(5000, $capturedDelays[0]); - $this->assertLessThanOrEqual(6000, $capturedDelays[0]); - } - - public function testMaxDelayCapIsApplied(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, ['Retry-After' => '120']), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); - $this->assertLessThanOrEqual(72000, $capturedDelays[0]); - } - - public function testMultipartContentTypeIncludesBoundary(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->add('field', 'value'); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $contentType = $lastRequest->getHeaderLine('Content-Type'); - $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); - - $boundary = substr($contentType, strlen('multipart/form-data; boundary=')); - $body = (string) $lastRequest->getBody(); - $this->assertStringContainsString("--{$boundary}\r\n", $body); - $this->assertStringContainsString("Content-Disposition: form-data; name=\"field\"\r\n", $body); - $this->assertStringContainsString("value", $body); - $this->assertStringContainsString("--{$boundary}--\r\n", $body); - } - - public function testMultipartWithFilename(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->addPart(new MultipartFormDataPart( - name: 'document', - value: 'file-contents', - filename: 'report.pdf', - headers: ['Content-Type' => 'application/pdf'], - )); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $body = (string) $lastRequest->getBody(); - $this->assertStringContainsString( - 'Content-Disposition: form-data; name="document"; filename="report.pdf"', - $body, - ); - $this->assertStringContainsString('Content-Type: application/pdf', $body); - $this->assertStringContainsString('file-contents', $body); - } - - public function testMultipartWithMultipleParts(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->add('name', 'John'); - $formData->add('age', 30); - $formData->addPart(new MultipartFormDataPart( - name: 'avatar', - value: 'image-data', - filename: 'avatar.png', - )); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/profile', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $body = (string) $lastRequest->getBody(); - $this->assertStringContainsString('name="name"', $body); - $this->assertStringContainsString('John', $body); - $this->assertStringContainsString('name="age"', $body); - $this->assertStringContainsString('30', $body); - $this->assertStringContainsString('name="avatar"; filename="avatar.png"', $body); - $this->assertStringContainsString('image-data', $body); - } - - public function testMultipartDoesNotIncludeJsonContentType(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->add('field', 'value'); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $contentType = $lastRequest->getHeaderLine('Content-Type'); - $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); - $this->assertStringNotContainsString('application/json', $contentType); - } - - public function testMultipartNullBodySendsNoBody(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('', (string) $lastRequest->getBody()); - $this->assertStringNotContainsString('multipart/form-data', $lastRequest->getHeaderLine('Content-Type')); - } - - public function testJsonNullBodySendsNoBody(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('', (string) $lastRequest->getBody()); - } - - public function testEmptyJsonBodySerializesAsObject(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - ['key' => 'value'], - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'bodyProperties' => [ - 'key' => 'value', - ], - ], - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - // When bodyProperties override all keys, the merged result should still - // serialize as a JSON object {}, not an array []. - $decoded = json_decode((string) $lastRequest->getBody(), true); - $this->assertIsArray($decoded); - $this->assertEquals('value', $decoded['key']); - } - - public function testAuthHeadersAreIncluded(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], - ]); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - $rawClient->sendRequest($request); - - $lastRequest = $mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); - } - - public function testAuthHeadersAreIncludedInMultipart(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], - ]); - - $formData = new MultipartFormData(); - $formData->add('field', 'value'); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $rawClient->sendRequest($request); - - $lastRequest = $mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); - $this->assertStringStartsWith('multipart/form-data; boundary=', $lastRequest->getHeaderLine('Content-Type')); - } - - /** - * Creates a PSR-7 response using discovery, without depending on any specific implementation. - * - * @param int $statusCode - * @param array $headers - * @param string $body - * @return ResponseInterface - */ - private static function createResponse( - int $statusCode = 200, - array $headers = [], - string $body = '', - ): ResponseInterface { - $response = \Http\Discovery\Psr17FactoryDiscovery::findResponseFactory() - ->createResponse($statusCode); - foreach ($headers as $name => $value) { - $response = $response->withHeader($name, $value); - } - if ($body !== '') { - $response = $response->withBody( - \Http\Discovery\Psr17FactoryDiscovery::findStreamFactory() - ->createStream($body), - ); - } - return $response; - } - - - public function testTimeoutOptionIsAccepted(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - // MockHttpClient is not Guzzle/Symfony, so a warning is triggered once. - set_error_handler(static function (int $errno, string $errstr): bool { - return $errno === E_USER_WARNING - && str_contains($errstr, 'Timeout option is not supported'); - }); - - try { - $response = $this->rawClient->sendRequest( - $request, - options: [ - 'timeout' => 3.0 - ] - ); - - $this->assertEquals(200, $response->getStatusCode()); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - } finally { - restore_error_handler(); - } - } - - public function testClientLevelTimeoutIsAccepted(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'timeout' => 5.0, - ]); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - set_error_handler(static function (int $errno, string $errstr): bool { - return $errno === E_USER_WARNING - && str_contains($errstr, 'Timeout option is not supported'); - }); - - try { - $response = $rawClient->sendRequest($request); - $this->assertEquals(200, $response->getStatusCode()); - } finally { - restore_error_handler(); - } - } - - public function testPerRequestTimeoutOverridesClientTimeout(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'timeout' => 5.0, - ]); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - set_error_handler(static function (int $errno, string $errstr): bool { - return $errno === E_USER_WARNING - && str_contains($errstr, 'Timeout option is not supported'); - }); - - try { - $response = $rawClient->sendRequest( - $request, - options: [ - 'timeout' => 1.0 - ] - ); - - $this->assertEquals(200, $response->getStatusCode()); - } finally { - restore_error_handler(); - } - } - - public function testDiscoveryFindsHttpClient(): void - { - // HttpClientBuilder::build() with no client arg uses Psr18ClientDiscovery. - $client = HttpClientBuilder::build(); - $this->assertInstanceOf(\Psr\Http\Client\ClientInterface::class, $client); - } - - public function testDiscoveryFindsFactories(): void - { - $requestFactory = HttpClientBuilder::requestFactory(); - $this->assertInstanceOf(\Psr\Http\Message\RequestFactoryInterface::class, $requestFactory); - - $streamFactory = HttpClientBuilder::streamFactory(); - $this->assertInstanceOf(\Psr\Http\Message\StreamFactoryInterface::class, $streamFactory); - - // Verify they produce usable objects - $request = $requestFactory->createRequest('GET', 'https://example.com'); - $this->assertEquals('GET', $request->getMethod()); - - $stream = $streamFactory->createStream('hello'); - $this->assertEquals('hello', (string) $stream); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php deleted file mode 100644 index 2c32002340e7..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/AdditionalPropertiesTest.php +++ /dev/null @@ -1,76 +0,0 @@ -name; - } - - /** - * @return string|null - */ - public function getEmail(): ?string - { - return $this->email; - } - - /** - * @param array{ - * name: string, - * email?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->name = $values['name']; - $this->email = $values['email'] ?? null; - } -} - -class AdditionalPropertiesTest extends TestCase -{ - public function testExtraProperties(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'name' => 'john.doe', - 'email' => 'john.doe@example.com', - 'age' => 42 - ], - ); - - $person = Person::fromJson($expectedJson); - $this->assertEquals('john.doe', $person->getName()); - $this->assertEquals('john.doe@example.com', $person->getEmail()); - $this->assertEquals( - [ - 'age' => 42 - ], - $person->getAdditionalProperties(), - ); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php deleted file mode 100644 index e7794d652432..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/DateArrayTest.php +++ /dev/null @@ -1,54 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTest extends TestCase -{ - public function testDateTimeInArrays(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ], - ); - - $object = DateArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php deleted file mode 100644 index b5f217e01f76..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/EmptyArrayTest.php +++ /dev/null @@ -1,71 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArrayTest extends TestCase -{ - public function testEmptyArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ], - ); - - $object = EmptyArray::fromJson($expectedJson); - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php deleted file mode 100644 index 72dc6f2cfa00..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/EnumTest.php +++ /dev/null @@ -1,77 +0,0 @@ -value; - } -} - -class ShapeType extends JsonSerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = JsonEncoder::encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ]); - - $actualJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $actualJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php deleted file mode 100644 index 4c288378b48b..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/ExhaustiveTest.php +++ /dev/null @@ -1,197 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class Type extends JsonSerializableType -{ - /** - * @var Nested nestedType - */ - #[JsonProperty('nested_type')] - public Nested $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[Date(Date::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[Date(Date::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(Nested::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: Nested, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class ExhaustiveTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in Type. - */ - public function testExhaustive(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // Omit 'nullable_property' to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56Z', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> - ], - ); - - $object = Type::fromJson($expectedJson); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php deleted file mode 100644 index 9d845ea113b8..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/InvalidTest.php +++ /dev/null @@ -1,42 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTest extends TestCase -{ - public function testInvalidJsonThrowsException(): void - { - $this->expectException(\TypeError::class); - $json = JsonEncoder::encode( - [ - 'integer_property' => 'not_an_integer' - ], - ); - Invalid::fromJson($json); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php deleted file mode 100644 index 8fbbeb939f02..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/NestedUnionArrayTest.php +++ /dev/null @@ -1,89 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArray extends JsonSerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTest extends TestCase -{ - public function testNestedUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ], - ); - - $object = NestedUnionArray::fromJson($expectedJson); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php deleted file mode 100644 index ce20a2442825..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/NullPropertyTest.php +++ /dev/null @@ -1,53 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullProperty( - [ - "nonNullProperty" => "Test String", - "nullProperty" => null - ] - ); - - $serialized = $object->jsonSerialize(); - $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); - $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php deleted file mode 100644 index d1749c434a4c..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/NullableArrayTest.php +++ /dev/null @@ -1,49 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTest extends TestCase -{ - public function testNullableArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nullable_string_array' => ['one', null, 'three'] - ], - ); - - $object = NullableArray::fromJson($expectedJson); - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php deleted file mode 100644 index ad4db0251bb5..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/ScalarTest.php +++ /dev/null @@ -1,116 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats - ], - ); - - $object = Scalar::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php deleted file mode 100644 index e18f06d4191b..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/TraitTest.php +++ /dev/null @@ -1,60 +0,0 @@ -integerProperty = $values['integerProperty']; - $this->stringProperty = $values['stringProperty']; - } -} - -class TraitTest extends TestCase -{ - public function testTraitPropertyAndString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'string_property' => 'Hello, World!', - ], - ); - - $object = TypeWithTrait::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php deleted file mode 100644 index de20cf9fde1b..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/UnionArrayTest.php +++ /dev/null @@ -1,57 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class UnionArrayTest extends TestCase -{ - public function testUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00Z', - 2 => null, - 3 => 'Some String' - ] - ], - ); - - $object = UnionArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php b/seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php deleted file mode 100644 index f733062cfabc..000000000000 --- a/seed/php-sdk/allof-inline/tests/Core/Json/UnionPropertyTest.php +++ /dev/null @@ -1,111 +0,0 @@ - 'integer'], UnionProperty::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionProperty - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => [1 => 100, 2 => 200] - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => new UnionProperty( - [ - 'complexUnion' => 'Nested String' - ] - ) - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $expectedJson = JsonEncoder::encode( - [], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 42 - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 'Some String' - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/allof/.fern/metadata.json b/seed/php-sdk/allof/.fern/metadata.json deleted file mode 100644 index 143d7b896167..000000000000 --- a/seed/php-sdk/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-php-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/php-sdk/allof/.github/workflows/ci.yml b/seed/php-sdk/allof/.github/workflows/ci.yml deleted file mode 100644 index 678eb6c9e141..000000000000 --- a/seed/php-sdk/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Build - run: | - composer build - - - name: Analyze - run: | - composer analyze - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - - - name: Install tools - run: | - composer install - - - name: Run Tests - run: | - composer test diff --git a/seed/php-sdk/allof/.gitignore b/seed/php-sdk/allof/.gitignore deleted file mode 100644 index 31a1aeb14f35..000000000000 --- a/seed/php-sdk/allof/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.idea -.php-cs-fixer.cache -.phpunit.result.cache -composer.lock -vendor/ \ No newline at end of file diff --git a/seed/php-sdk/allof/README.md b/seed/php-sdk/allof/README.md deleted file mode 100644 index 0b89aa3897c7..000000000000 --- a/seed/php-sdk/allof/README.md +++ /dev/null @@ -1,169 +0,0 @@ -# Seed PHP Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPHP) -[![php shield](https://img.shields.io/badge/php-packagist-pink)](https://packagist.org/packages/seed/seed) - -The Seed PHP library provides convenient access to the Seed APIs from PHP. - -## Table of Contents - -- [Requirements](#requirements) -- [Installation](#installation) -- [Usage](#usage) -- [Environments](#environments) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Custom Client](#custom-client) - - [Retries](#retries) - - [Timeouts](#timeouts) -- [Contributing](#contributing) - -## Requirements - -This SDK requires PHP ^8.1. - -## Installation - -```sh -composer require seed/seed -``` - -## Usage - -Instantiate and use the client with the following: - -```php -createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); - -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```php -The SDK defaults to the `Default_` environment. To use a different environment, pass it to the client constructor: - -```php -use Seed\SeedClient; -use Seed\Environments; - -$client = new SeedClient( - token: '', - options: [ - 'baseUrl' => Environments::Staging->value - ] -); -``` - -Available environments: -- `Environments::Default_` -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), an exception will be thrown. - -```php -use Seed\Exceptions\SeedApiException; -use Seed\Exceptions\SeedException; - -try { - $response = $client->createRule(...); -} catch (SeedApiException $e) { - echo 'API Exception occurred: ' . $e->getMessage() . "\n"; - echo 'Status Code: ' . $e->getCode() . "\n"; - echo 'Response Body: ' . $e->getBody() . "\n"; - // Optionally, rethrow the exception or handle accordingly. -} -``` - -## Advanced - -### Custom Client - -This SDK is built to work with any HTTP client that implements the [PSR-18](https://www.php-fig.org/psr/psr-18/) `ClientInterface`. -By default, if no client is provided, the SDK will use `php-http/discovery` to find an installed HTTP client. -However, you can pass your own client that adheres to `ClientInterface`: - -```php -use Seed\SeedClient; - -// Pass any PSR-18 compatible HTTP client implementation. -// For example, using Guzzle: -$customClient = new \GuzzleHttp\Client([ - 'timeout' => 5.0, -]); - -$client = new SeedClient(options: [ - 'client' => $customClient -]); - -// Or using Symfony HttpClient: -// $customClient = (new \Symfony\Component\HttpClient\Psr18Client()) -// ->withOptions(['timeout' => 5.0]); -// -// $client = new SeedClient(options: [ -// 'client' => $customClient -// ]); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` request option to configure this behavior. - -```php -$response = $client->createRule( - ..., - options: [ - 'maxRetries' => 0 // Override maxRetries at the request level - ] -); -``` - -### Timeouts - -The SDK defaults to a 30 second timeout. Use the `timeout` option to configure this behavior. - -```php -$response = $client->createRule( - ..., - options: [ - 'timeout' => 3.0 // Override timeout at the request level - ] -); -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/php-sdk/allof/composer.json b/seed/php-sdk/allof/composer.json deleted file mode 100644 index ad30960a8764..000000000000 --- a/seed/php-sdk/allof/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "seed/seed", - "version": "0.0.1", - "description": "Seed PHP Library", - "keywords": [ - "seed", - "api", - "sdk" - ], - "license": [], - "require": { - "php": "^8.1", - "ext-json": "*", - "psr/http-client": "^1.0", - "psr/http-client-implementation": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-factory-implementation": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "php-http/discovery": "^1.0", - "php-http/multipart-stream-builder": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0", - "friendsofphp/php-cs-fixer": "3.5.0", - "phpstan/phpstan": "^1.12", - "guzzlehttp/guzzle": "^7.4" - }, - "autoload": { - "psr-4": { - "Seed\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Seed\\Tests\\": "tests/" - } - }, - "scripts": { - "build": [ - "@php -l src", - "@php -l tests" - ], - "test": "phpunit", - "analyze": "phpstan analyze src tests --memory-limit=1G" - } -} \ No newline at end of file diff --git a/seed/php-sdk/allof/phpstan.neon b/seed/php-sdk/allof/phpstan.neon deleted file mode 100644 index 780706b8f8a2..000000000000 --- a/seed/php-sdk/allof/phpstan.neon +++ /dev/null @@ -1,6 +0,0 @@ -parameters: - level: max - reportUnmatchedIgnoredErrors: false - paths: - - src - - tests \ No newline at end of file diff --git a/seed/php-sdk/allof/phpunit.xml b/seed/php-sdk/allof/phpunit.xml deleted file mode 100644 index 54630a51163c..000000000000 --- a/seed/php-sdk/allof/phpunit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - tests - - - \ No newline at end of file diff --git a/seed/php-sdk/allof/reference.md b/seed/php-sdk/allof/reference.md deleted file mode 100644 index b824179f7851..000000000000 --- a/seed/php-sdk/allof/reference.md +++ /dev/null @@ -1,171 +0,0 @@ -# Reference -
$client->searchRuleTypes($request) -> ?RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->searchRuleTypes( - new SearchRuleTypesRequest([]), -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**$query:** `?string` - -
-
-
-
- - -
-
-
- -
$client->createRule($request) -> ?RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**$name:** `string` - -
-
- -
-
- -**$executionContext:** `string` - -
-
-
-
- - -
-
-
- -
$client->listUsers() -> ?UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->listUsers(); -``` -
-
-
-
- - -
-
-
- -
$client->getEntity() -> ?CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->getEntity(); -``` -
-
-
-
- - -
-
-
- -
$client->getOrganization() -> ?Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```php -$client->getOrganization(); -``` -
-
-
-
- - -
-
-
- diff --git a/seed/php-sdk/allof/snippet.json b/seed/php-sdk/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php deleted file mode 100644 index 5e1283e2b6f6..000000000000 --- a/seed/php-sdk/allof/src/Core/Client/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php b/seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php deleted file mode 100644 index 8ac806af0325..000000000000 --- a/seed/php-sdk/allof/src/Core/Client/HttpClientBuilder.php +++ /dev/null @@ -1,56 +0,0 @@ - - */ - private array $responses = []; - - /** - * @var array - */ - private array $requests = []; - - /** - * @param ResponseInterface ...$responses - */ - public function append(ResponseInterface ...$responses): void - { - foreach ($responses as $response) { - $this->responses[] = $response; - } - } - - /** - * @param RequestInterface $request - * @return ResponseInterface - */ - public function sendRequest(RequestInterface $request): ResponseInterface - { - $this->requests[] = $request; - - if (empty($this->responses)) { - throw new RuntimeException('No more responses in the queue. Add responses using append().'); - } - - return array_shift($this->responses); - } - - /** - * @return ?RequestInterface - */ - public function getLastRequest(): ?RequestInterface - { - if (empty($this->requests)) { - return null; - } - return $this->requests[count($this->requests) - 1]; - } - - /** - * @return int - */ - public function getRequestCount(): int - { - return count($this->requests); - } - - /** - * Returns the number of remaining responses in the queue. - * - * @return int - */ - public function count(): int - { - return count($this->responses); - } -} diff --git a/seed/php-sdk/allof/src/Core/Client/RawClient.php b/seed/php-sdk/allof/src/Core/Client/RawClient.php deleted file mode 100644 index 14716c7d678b..000000000000 --- a/seed/php-sdk/allof/src/Core/Client/RawClient.php +++ /dev/null @@ -1,310 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @var ?(callable(): array) $getAuthHeaders - */ - private $getAuthHeaders; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * getAuthHeaders?: callable(): array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = HttpClientBuilder::build( - $this->options['client'] ?? null, - $this->options['maxRetries'] ?? 2, - ); - $this->requestFactory = HttpClientBuilder::requestFactory(); - $this->streamFactory = HttpClientBuilder::streamFactory(); - $this->headers = $this->options['headers'] ?? []; - $this->getAuthHeaders = $this->options['getAuthHeaders'] ?? null; - } - - /** - * @param BaseApiRequest $request - * @param ?array{ - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ?array $options = null, - ): ResponseInterface { - $opts = $options ?? []; - $httpRequest = $this->buildRequest($request, $opts); - - $timeout = $opts['timeout'] ?? $this->options['timeout'] ?? null; - $maxRetries = $opts['maxRetries'] ?? null; - - return $this->client->send($httpRequest, $timeout, $maxRetries); - } - - /** - * @param BaseApiRequest $request - * @param array{ - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return RequestInterface - */ - private function buildRequest( - BaseApiRequest $request, - array $options - ): RequestInterface { - $url = $this->buildUrl($request, $options); - $headers = $this->encodeHeaders($request, $options); - - $httpRequest = $this->requestFactory->createRequest( - $request->method->name, - $url, - ); - - // Encode body and, for multipart, capture the Content-Type with boundary. - if ($request instanceof MultipartApiRequest && $request->body !== null) { - $builder = new MultipartStreamBuilder($this->streamFactory); - $request->body->addToBuilder($builder); - $httpRequest = $httpRequest->withBody($builder->build()); - $headers['Content-Type'] = "multipart/form-data; boundary={$builder->getBoundary()}"; - } else { - $body = $this->encodeRequestBody($request, $options); - if ($body !== null) { - $httpRequest = $httpRequest->withBody($body); - } - } - - foreach ($headers as $name => $value) { - $httpRequest = $httpRequest->withHeader($name, $value); - } - - return $httpRequest; - } - - /** - * @param BaseApiRequest $request - * @param array{ - * headers?: array, - * } $options - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request, - array $options, - ): array { - $authHeaders = $this->getAuthHeaders !== null ? ($this->getAuthHeaders)() : []; - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - [ - "Content-Type" => "application/json", - "Accept" => "*/*", - ], - $this->headers, - $authHeaders, - $request->headers, - $options['headers'] ?? [], - ), - MultipartApiRequest::class => array_merge( - $this->headers, - $authHeaders, - $request->headers, - $options['headers'] ?? [], - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - /** - * @param BaseApiRequest $request - * @param array{ - * bodyProperties?: array, - * } $options - * @return ?StreamInterface - */ - private function encodeRequestBody( - BaseApiRequest $request, - array $options, - ): ?StreamInterface { - if ($request instanceof JsonApiRequest) { - return $request->body === null ? null : $this->streamFactory->createStream( - JsonEncoder::encode( - $this->buildJsonBody( - $request->body, - $options, - ), - ) - ); - } - - if ($request instanceof MultipartApiRequest) { - return null; - } - - throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)); - } - - /** - * @param mixed $body - * @param array{ - * bodyProperties?: array, - * } $options - * @return mixed - */ - private function buildJsonBody( - mixed $body, - array $options, - ): mixed { - $overrideProperties = $options['bodyProperties'] ?? []; - if (is_array($body) && (empty($body) || self::isSequential($body))) { - return array_merge($body, $overrideProperties); - } - - if ($body instanceof JsonSerializable) { - $result = $body->jsonSerialize(); - } else { - $result = $body; - } - if (is_array($result)) { - $result = array_merge($result, $overrideProperties); - if (empty($result)) { - // force to be serialized as {} instead of [] - return (object)($result); - } - } - - return $result; - } - - /** - * @param BaseApiRequest $request - * @param array{ - * queryParameters?: array, - * } $options - * @return string - */ - private function buildUrl( - BaseApiRequest $request, - array $options, - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - $query = array_merge( - $request->query, - $options['queryParameters'] ?? [], - ); - if (!empty($query)) { - $url .= '?' . $this->encodeQuery($query); - } - return $url; - } - - /** - * @param array $query - * @return string - */ - private function encodeQuery(array $query): string - { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key) . '=' . $this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue(mixed $value): string - { - if (is_string($value)) { - return urlencode($value); - } - if (is_bool($value)) { - return $value ? 'true' : 'false'; - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(JsonEncoder::encode($value)); - } - - /** - * Check if an array is sequential, not associative. - * @param mixed[] $arr - * @return bool - */ - private static function isSequential(array $arr): bool - { - if (empty($arr)) { - return false; - } - $length = count($arr); - $keys = array_keys($arr); - for ($i = 0; $i < $length; $i++) { - if ($keys[$i] !== $i) { - return false; - } - } - return true; - } -} diff --git a/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php deleted file mode 100644 index b16170cf2805..000000000000 --- a/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php +++ /dev/null @@ -1,241 +0,0 @@ -client = $client; - $this->maxRetries = $maxRetries; - $this->baseDelay = $baseDelay; - $this->sleepFunction = $sleepFunction ?? 'usleep'; - } - - /** - * @param RequestInterface $request - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - public function sendRequest(RequestInterface $request): ResponseInterface - { - return $this->send($request); - } - - /** - * Sends a request with optional per-request timeout and retry overrides. - * - * When a Guzzle or Symfony PSR-18 client is detected, the timeout is - * forwarded via the client's native API. For other PSR-18 clients the - * timeout value is silently ignored. - * - * @param RequestInterface $request - * @param ?float $timeout Timeout in seconds, or null to use the client default. - * @param ?int $maxRetries Maximum retry attempts, or null to use the client default. - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - public function send( - RequestInterface $request, - ?float $timeout = null, - ?int $maxRetries = null, - ): ResponseInterface { - $maxRetries = $maxRetries ?? $this->maxRetries; - $retryAttempt = 0; - $lastResponse = null; - - while (true) { - try { - $lastResponse = $this->doSend($request, $timeout); - if (!$this->shouldRetry($retryAttempt, $maxRetries, $lastResponse)) { - return $lastResponse; - } - } catch (ClientExceptionInterface $e) { - if ($retryAttempt >= $maxRetries) { - throw $e; - } - } - - $retryAttempt++; - $delay = $this->getRetryDelay($retryAttempt, $lastResponse); - ($this->sleepFunction)($delay * 1000); // Convert milliseconds to microseconds - - // Rewind the request body so retries don't send an empty body. - $request->getBody()->rewind(); - } - } - - /** - * Dispatches the request to the underlying client, forwarding the timeout - * option to Guzzle or Symfony when available. - * - * @param RequestInterface $request - * @param ?float $timeout - * @return ResponseInterface - * @throws ClientExceptionInterface - */ - private function doSend(RequestInterface $request, ?float $timeout): ResponseInterface - { - static $warned = false; - - if ($timeout === null) { - return $this->client->sendRequest($request); - } - - if (class_exists('GuzzleHttp\ClientInterface') - && $this->client instanceof \GuzzleHttp\ClientInterface - ) { - return $this->client->send($request, ['timeout' => $timeout]); - } - if (class_exists('Symfony\Component\HttpClient\Psr18Client') - && $this->client instanceof \Symfony\Component\HttpClient\Psr18Client - ) { - /** @var ClientInterface $clientWithTimeout */ - $clientWithTimeout = $this->client->withOptions(['timeout' => $timeout]); - return $clientWithTimeout->sendRequest($request); - } - - if ($warned) { - return $this->client->sendRequest($request); - } - $warned = true; - trigger_error( - 'Timeout option is not supported for the current PSR-18 client (' - . get_class($this->client) - . '). Use Guzzle or Symfony HttpClient for timeout support.', - E_USER_WARNING, - ); - return $this->client->sendRequest($request); - } - - /** - * @param int $retryAttempt - * @param int $maxRetries - * @param ?ResponseInterface $response - * @return bool - */ - private function shouldRetry( - int $retryAttempt, - int $maxRetries, - ?ResponseInterface $response = null, - ): bool { - if ($retryAttempt >= $maxRetries) { - return false; - } - - if ($response !== null) { - return $response->getStatusCode() >= 500 || - in_array($response->getStatusCode(), self::RETRY_STATUS_CODES); - } - - return false; - } - - /** - * Calculate the retry delay based on response headers or exponential backoff. - * - * @param int $retryAttempt - * @param ?ResponseInterface $response - * @return int milliseconds - */ - private function getRetryDelay(int $retryAttempt, ?ResponseInterface $response): int - { - if ($response !== null) { - // Check Retry-After header - $retryAfter = $response->getHeaderLine('Retry-After'); - if ($retryAfter !== '') { - // Try parsing as integer (seconds) - if (is_numeric($retryAfter)) { - $retryAfterSeconds = (int)$retryAfter; - if ($retryAfterSeconds > 0) { - return min($retryAfterSeconds * 1000, self::MAX_RETRY_DELAY); - } - } - - // Try parsing as HTTP date - $retryAfterDate = strtotime($retryAfter); - if ($retryAfterDate !== false) { - $delay = ($retryAfterDate - time()) * 1000; - if ($delay > 0) { - return min(max($delay, 0), self::MAX_RETRY_DELAY); - } - } - } - - // Check X-RateLimit-Reset header - $rateLimitReset = $response->getHeaderLine('X-RateLimit-Reset'); - if ($rateLimitReset !== '' && is_numeric($rateLimitReset)) { - $resetTime = (int)$rateLimitReset; - $delay = ($resetTime * 1000) - (int)(microtime(true) * 1000); - if ($delay > 0) { - return $this->addPositiveJitter(min($delay, self::MAX_RETRY_DELAY)); - } - } - } - - // Fall back to exponential backoff with symmetric jitter - return $this->addSymmetricJitter( - min($this->exponentialDelay($retryAttempt), self::MAX_RETRY_DELAY) - ); - } - - /** - * Add positive jitter (0% to +20%) to the delay. - * - * @param int $delay - * @return int - */ - private function addPositiveJitter(int $delay): int - { - $jitterMultiplier = 1 + (mt_rand() / mt_getrandmax()) * self::JITTER_FACTOR; - return (int)($delay * $jitterMultiplier); - } - - /** - * Add symmetric jitter (-10% to +10%) to the delay. - * - * @param int $delay - * @return int - */ - private function addSymmetricJitter(int $delay): int - { - $jitterMultiplier = 1 + ((mt_rand() / mt_getrandmax()) - 0.5) * self::JITTER_FACTOR; - return (int)($delay * $jitterMultiplier); - } - - /** - * Default exponential backoff delay function. - * - * @return int milliseconds. - */ - private function exponentialDelay(int $retryAttempt): int - { - return 2 ** ($retryAttempt - 1) * $this->baseDelay; - } -} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php b/seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php deleted file mode 100644 index 8fdf493606e6..000000000000 --- a/seed/php-sdk/allof/src/Core/Json/JsonApiRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonDecoder.php b/seed/php-sdk/allof/src/Core/Json/JsonDecoder.php deleted file mode 100644 index 2da34087c644..000000000000 --- a/seed/php-sdk/allof/src/Core/Json/JsonDecoder.php +++ /dev/null @@ -1,161 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: $json"); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php deleted file mode 100644 index 1a250c614e45..000000000000 --- a/seed/php-sdk/allof/src/Core/Json/JsonDeserializer.php +++ /dev/null @@ -1,218 +0,0 @@ - $data The array to be deserialized. - * @param array $type The type definition from the annotation. - * @return array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (\Throwable) { - // Catching Throwable instead of Exception to handle TypeError - // that occurs when assigning null to non-nullable typed properties - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: $type" - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - /** @var array $data */ - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement JsonSerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, JsonSerializableType::class)) { - throw new JsonException("$type is not a subclass of JsonSerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonEncoder.php b/seed/php-sdk/allof/src/Core/Json/JsonEncoder.php deleted file mode 100644 index 0dbf3fcc9948..000000000000 --- a/seed/php-sdk/allof/src/Core/Json/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ - Extra properties from JSON that don't map to class properties */ - private array $__additionalProperties = []; - - /** @var array Properties that have been explicitly set via setter methods */ - private array $__explicitlySetProperties = []; - - /** - * Serializes the object to a JSON string. - * - * @return string JSON-encoded string representation of the object. - * @throws Exception If encoding fails. - */ - public function toJson(): string - { - $serializedObject = $this->jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey === null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === Date::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - // Include the value if it's not null, OR if it was explicitly set (even to null) - if ($value !== null || array_key_exists($property->getName(), $this->__explicitlySetProperties)) { - $result[$jsonKey] = $value; - } - } - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - /** @var array $decodedJson */ - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - $properties = []; - $additionalProperties = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - $properties[$jsonKey] = $property; - } - - foreach ($data as $jsonKey => $value) { - if (!isset($properties[$jsonKey])) { - // This JSON key doesn't map to any class property - add it to additionalProperties - $additionalProperties[$jsonKey] = $value; - continue; - } - - $property = $properties[$jsonKey]; - - // Handle Date annotation - $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === Date::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle Array annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - /** @var array $arrayValue */ - $arrayValue = $value; - $value = JsonDeserializer::deserializeObject($arrayValue, $type->getName()); - } - - $args[$property->getName()] = $value; - } - - // Fill in any missing properties with defaults - foreach ($properties as $property) { - if (!isset($args[$property->getName()])) { - $args[$property->getName()] = $property->hasDefaultValue() ? $property->getDefaultValue() : null; - } - } - - // @phpstan-ignore-next-line - $result = new static($args); - $result->__additionalProperties = $additionalProperties; - return $result; - } - - /** - * Get properties from JSON that weren't mapped to class fields - * @return array - */ - public function getAdditionalProperties(): array - { - return $this->__additionalProperties; - } - - /** - * Mark a property as explicitly set. - * This ensures the property will be included in JSON serialization even if null. - * - * @param string $propertyName The name of the property to mark as explicitly set. - */ - protected function _setField(string $propertyName): void - { - $this->__explicitlySetProperties[$propertyName] = true; - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/allof/src/Core/Json/JsonSerializer.php b/seed/php-sdk/allof/src/Core/Json/JsonSerializer.php deleted file mode 100644 index f7d80ed5e8f3..000000000000 --- a/seed/php-sdk/allof/src/Core/Json/JsonSerializer.php +++ /dev/null @@ -1,205 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - $formatted = $date->format(Constant::DateTimeFormat); - if (str_ends_with($formatted, '+00:00')) { - return substr($formatted, 0, -6) . 'Z'; - } - return $formatted; - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param array $data The array to be serialized. - * @param array $type The type definition from the annotation. - * @return array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) !== "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: $unionType" - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - // Handle bools as a special case since gettype($data) returns "boolean" for bool values in PHP. - if ($type === 'bool' && is_bool($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $keyType = (string) $keyType; - $valueType = $type[$keyType]; - /** @var array $result */ - $result = []; - - foreach ($data as $key => $item) { - $key = (string) Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - /** @var array */ - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/allof/src/Core/Json/Utils.php b/seed/php-sdk/allof/src/Core/Json/Utils.php deleted file mode 100644 index 4099b8253005..000000000000 --- a/seed/php-sdk/allof/src/Core/Json/Utils.php +++ /dev/null @@ -1,62 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return int|string The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): int|string - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - // PHP arrays don't support float keys; truncate to int - 'float' => (int)$key, - 'string' => (string)$key, - default => is_int($key) ? $key : (string)$key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php b/seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php deleted file mode 100644 index 7760366456c8..000000000000 --- a/seed/php-sdk/allof/src/Core/Multipart/MultipartApiRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param ?MultipartFormData $body The multipart form data for the request (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly ?MultipartFormData $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php b/seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php deleted file mode 100644 index 911a28b6ad64..000000000000 --- a/seed/php-sdk/allof/src/Core/Multipart/MultipartFormData.php +++ /dev/null @@ -1,58 +0,0 @@ - - */ - private array $parts = []; - - /** - * Adds a new part to the multipart form data. - * - * @param string $name - * @param string|int|bool|float|StreamInterface $value - * @param ?string $contentType - */ - public function add( - string $name, - string|int|bool|float|StreamInterface $value, - ?string $contentType = null, - ): void { - $headers = $contentType !== null ? ['Content-Type' => $contentType] : null; - $this->addPart( - new MultipartFormDataPart( - name: $name, - value: $value, - headers: $headers, - ) - ); - } - - /** - * Adds a new part to the multipart form data. - * - * @param MultipartFormDataPart $part - */ - public function addPart(MultipartFormDataPart $part): void - { - $this->parts[] = $part; - } - - /** - * Adds all parts to a MultipartStreamBuilder. - * - * @param MultipartStreamBuilder $builder - */ - public function addToBuilder(MultipartStreamBuilder $builder): void - { - foreach ($this->parts as $part) { - $part->addToBuilder($builder); - } - } -} diff --git a/seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php b/seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php deleted file mode 100644 index 4db35e58ae37..000000000000 --- a/seed/php-sdk/allof/src/Core/Multipart/MultipartFormDataPart.php +++ /dev/null @@ -1,62 +0,0 @@ - - */ - private ?array $headers; - - /** - * @param string $name - * @param string|bool|float|int|StreamInterface $value - * @param ?string $filename - * @param ?array $headers - */ - public function __construct( - string $name, - string|bool|float|int|StreamInterface $value, - ?string $filename = null, - ?array $headers = null - ) { - $this->name = $name; - $this->contents = $value instanceof StreamInterface ? $value : (string)$value; - $this->filename = $filename; - $this->headers = $headers; - } - - /** - * Adds this part to a MultipartStreamBuilder. - * - * @param MultipartStreamBuilder $builder - */ - public function addToBuilder(MultipartStreamBuilder $builder): void - { - $options = array_filter([ - 'filename' => $this->filename, - 'headers' => $this->headers, - ], fn ($value) => $value !== null); - - $builder->addResource($this->name, $this->contents, $options); - } -} diff --git a/seed/php-sdk/allof/src/Core/Types/ArrayType.php b/seed/php-sdk/allof/src/Core/Types/ArrayType.php deleted file mode 100644 index a26d29008ec3..000000000000 --- a/seed/php-sdk/allof/src/Core/Types/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/allof/src/Core/Types/Constant.php b/seed/php-sdk/allof/src/Core/Types/Constant.php deleted file mode 100644 index 5ac4518cc6d6..000000000000 --- a/seed/php-sdk/allof/src/Core/Types/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/allof/src/Environments.php b/seed/php-sdk/allof/src/Environments.php deleted file mode 100644 index 43d64cdb5e37..000000000000 --- a/seed/php-sdk/allof/src/Environments.php +++ /dev/null @@ -1,8 +0,0 @@ -body = $body; - parent::__construct($message, $statusCode, $previous); - } - - /** - * Returns the body of the response that triggered the exception. - * - * @return mixed - */ - public function getBody(): mixed - { - return $this->body; - } - - /** - * @return string - */ - public function __toString(): string - { - if (empty($this->body)) { - return $this->message . '; Status Code: ' . $this->getCode() . "\n"; - } - return $this->message . '; Status Code: ' . $this->getCode() . '; Body: ' . print_r($this->body, true) . "\n"; - } -} diff --git a/seed/php-sdk/allof/src/Exceptions/SeedException.php b/seed/php-sdk/allof/src/Exceptions/SeedException.php deleted file mode 100644 index 457035276737..000000000000 --- a/seed/php-sdk/allof/src/Exceptions/SeedException.php +++ /dev/null @@ -1,12 +0,0 @@ - $executionContext - */ - #[JsonProperty('executionContext')] - public string $executionContext; - - /** - * @param array{ - * name: string, - * executionContext: value-of, - * } $values - */ - public function __construct( - array $values, - ) { - $this->name = $values['name']; - $this->executionContext = $values['executionContext']; - } -} diff --git a/seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php b/seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php deleted file mode 100644 index bd49b88a3872..000000000000 --- a/seed/php-sdk/allof/src/Requests/SearchRuleTypesRequest.php +++ /dev/null @@ -1,24 +0,0 @@ -query = $values['query'] ?? null; - } -} diff --git a/seed/php-sdk/allof/src/SeedClient.php b/seed/php-sdk/allof/src/SeedClient.php deleted file mode 100644 index 63b560f9b6c7..000000000000 --- a/seed/php-sdk/allof/src/SeedClient.php +++ /dev/null @@ -1,302 +0,0 @@ -, - * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator - */ - private array $options; - - /** - * @var RawClient $client - */ - private RawClient $client; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * } $options - */ - public function __construct( - ?array $options = null, - ) { - $defaultHeaders = [ - 'X-Fern-Language' => 'PHP', - 'X-Fern-SDK-Name' => 'Seed', - 'X-Fern-SDK-Version' => '0.0.1', - 'User-Agent' => 'seed/seed/0.0.1', - ]; - - $this->options = $options ?? []; - - $this->options['headers'] = array_merge( - $defaultHeaders, - $this->options['headers'] ?? [], - ); - - $this->client = new RawClient( - options: $this->options, - ); - } - - /** - * @param SearchRuleTypesRequest $request - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?RuleTypeSearchResponse - * @throws SeedException - * @throws SeedApiException - */ - public function searchRuleTypes(SearchRuleTypesRequest $request = new SearchRuleTypesRequest(), ?array $options = null): ?RuleTypeSearchResponse - { - $options = array_merge($this->options, $options ?? []); - $query = []; - if ($request->query != null) { - $query['query'] = $request->query; - } - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "rule-types", - method: HttpMethod::GET, - query: $query, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return RuleTypeSearchResponse::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param RuleCreateRequest $request - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?RuleResponse - * @throws SeedException - * @throws SeedApiException - */ - public function createRule(RuleCreateRequest $request, ?array $options = null): ?RuleResponse - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "rules", - method: HttpMethod::POST, - body: $request, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return RuleResponse::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?UserSearchResponse - * @throws SeedException - * @throws SeedApiException - */ - public function listUsers(?array $options = null): ?UserSearchResponse - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "users", - method: HttpMethod::GET, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return UserSearchResponse::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?CombinedEntity - * @throws SeedException - * @throws SeedApiException - */ - public function getEntity(?array $options = null): ?CombinedEntity - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "entities", - method: HttpMethod::GET, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return CombinedEntity::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } - - /** - * @param ?array{ - * baseUrl?: string, - * maxRetries?: int, - * timeout?: float, - * headers?: array, - * queryParameters?: array, - * bodyProperties?: array, - * } $options - * @return ?Organization - * @throws SeedException - * @throws SeedApiException - */ - public function getOrganization(?array $options = null): ?Organization - { - $options = array_merge($this->options, $options ?? []); - try { - $response = $this->client->sendRequest( - new JsonApiRequest( - baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Default_->value, - path: "organizations", - method: HttpMethod::GET, - ), - $options, - ); - $statusCode = $response->getStatusCode(); - if ($statusCode >= 200 && $statusCode < 400) { - $json = $response->getBody()->getContents(); - if (empty($json)) { - return null; - } - return Organization::fromJson($json); - } - } catch (JsonException $e) { - throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); - } catch (ClientExceptionInterface $e) { - throw new SeedException(message: $e->getMessage(), previous: $e); - } - throw new SeedApiException( - message: 'API request failed', - statusCode: $statusCode, - body: $response->getBody()->getContents(), - ); - } -} diff --git a/seed/php-sdk/allof/src/Traits/AuditInfo.php b/seed/php-sdk/allof/src/Traits/AuditInfo.php deleted file mode 100644 index 2c2ce1e5f033..000000000000 --- a/seed/php-sdk/allof/src/Traits/AuditInfo.php +++ /dev/null @@ -1,42 +0,0 @@ -createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/BaseOrg.php b/seed/php-sdk/allof/src/Types/BaseOrg.php deleted file mode 100644 index 5d403a06b445..000000000000 --- a/seed/php-sdk/allof/src/Types/BaseOrg.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/BaseOrgMetadata.php b/seed/php-sdk/allof/src/Types/BaseOrgMetadata.php deleted file mode 100644 index e974fa97bb1a..000000000000 --- a/seed/php-sdk/allof/src/Types/BaseOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->tier = $values['tier'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/CombinedEntity.php b/seed/php-sdk/allof/src/Types/CombinedEntity.php deleted file mode 100644 index ae8be69937ad..000000000000 --- a/seed/php-sdk/allof/src/Types/CombinedEntity.php +++ /dev/null @@ -1,58 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @var string $id Unique identifier. - */ - #[JsonProperty('id')] - public string $id; - - /** - * @var ?string $name Display name from Identifiable. - */ - #[JsonProperty('name')] - public ?string $name; - - /** - * @var ?string $summary A short summary. - */ - #[JsonProperty('summary')] - public ?string $summary; - - /** - * @param array{ - * status: value-of, - * id: string, - * name?: ?string, - * summary?: ?string, - * } $values - */ - public function __construct( - array $values, - ) { - $this->status = $values['status']; - $this->id = $values['id']; - $this->name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/CombinedEntityStatus.php b/seed/php-sdk/allof/src/Types/CombinedEntityStatus.php deleted file mode 100644 index 63aba6a34b11..000000000000 --- a/seed/php-sdk/allof/src/Types/CombinedEntityStatus.php +++ /dev/null @@ -1,9 +0,0 @@ -name = $values['name'] ?? null; - $this->summary = $values['summary'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/DetailedOrg.php b/seed/php-sdk/allof/src/Types/DetailedOrg.php deleted file mode 100644 index c53ac92b926a..000000000000 --- a/seed/php-sdk/allof/src/Types/DetailedOrg.php +++ /dev/null @@ -1,34 +0,0 @@ -metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php b/seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php deleted file mode 100644 index ea98caf5f68a..000000000000 --- a/seed/php-sdk/allof/src/Types/DetailedOrgMetadata.php +++ /dev/null @@ -1,42 +0,0 @@ -region = $values['region']; - $this->domain = $values['domain'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/Identifiable.php b/seed/php-sdk/allof/src/Types/Identifiable.php deleted file mode 100644 index 83c1714a7eb8..000000000000 --- a/seed/php-sdk/allof/src/Types/Identifiable.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->name = $values['name'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/Organization.php b/seed/php-sdk/allof/src/Types/Organization.php deleted file mode 100644 index a03834027cfb..000000000000 --- a/seed/php-sdk/allof/src/Types/Organization.php +++ /dev/null @@ -1,50 +0,0 @@ -name = $values['name']; - $this->id = $values['id']; - $this->metadata = $values['metadata'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/PaginatedResult.php b/seed/php-sdk/allof/src/Types/PaginatedResult.php deleted file mode 100644 index c24634881db3..000000000000 --- a/seed/php-sdk/allof/src/Types/PaginatedResult.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType(['mixed'])] - public array $results; - - /** - * @param array{ - * paging: PagingCursors, - * results: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->paging = $values['paging']; - $this->results = $values['results']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/PagingCursors.php b/seed/php-sdk/allof/src/Types/PagingCursors.php deleted file mode 100644 index b4a3809a1dbb..000000000000 --- a/seed/php-sdk/allof/src/Types/PagingCursors.php +++ /dev/null @@ -1,42 +0,0 @@ -next = $values['next']; - $this->previous = $values['previous'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/RuleExecutionContext.php b/seed/php-sdk/allof/src/Types/RuleExecutionContext.php deleted file mode 100644 index bc98b8b52369..000000000000 --- a/seed/php-sdk/allof/src/Types/RuleExecutionContext.php +++ /dev/null @@ -1,10 +0,0 @@ - $status - */ - #[JsonProperty('status')] - public string $status; - - /** - * @var ?value-of $executionContext - */ - #[JsonProperty('executionContext')] - public ?string $executionContext; - - /** - * @param array{ - * id: string, - * name: string, - * status: value-of, - * createdBy?: ?string, - * createdDateTime?: ?DateTime, - * modifiedBy?: ?string, - * modifiedDateTime?: ?DateTime, - * executionContext?: ?value-of, - * } $values - */ - public function __construct( - array $values, - ) { - $this->createdBy = $values['createdBy'] ?? null; - $this->createdDateTime = $values['createdDateTime'] ?? null; - $this->modifiedBy = $values['modifiedBy'] ?? null; - $this->modifiedDateTime = $values['modifiedDateTime'] ?? null; - $this->id = $values['id']; - $this->name = $values['name']; - $this->status = $values['status']; - $this->executionContext = $values['executionContext'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/RuleResponseStatus.php b/seed/php-sdk/allof/src/Types/RuleResponseStatus.php deleted file mode 100644 index cef363294474..000000000000 --- a/seed/php-sdk/allof/src/Types/RuleResponseStatus.php +++ /dev/null @@ -1,10 +0,0 @@ -id = $values['id']; - $this->name = $values['name']; - $this->description = $values['description'] ?? null; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php b/seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php deleted file mode 100644 index 6272d4420ec7..000000000000 --- a/seed/php-sdk/allof/src/Types/RuleTypeSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([RuleType::class])] - public ?array $results; - - /** - * @var PagingCursors $paging - */ - #[JsonProperty('paging')] - public PagingCursors $paging; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->results = $values['results'] ?? null; - $this->paging = $values['paging']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/User.php b/seed/php-sdk/allof/src/Types/User.php deleted file mode 100644 index aab3174ca08c..000000000000 --- a/seed/php-sdk/allof/src/Types/User.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $values['id']; - $this->email = $values['email']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Types/UserSearchResponse.php b/seed/php-sdk/allof/src/Types/UserSearchResponse.php deleted file mode 100644 index 4944636dfa78..000000000000 --- a/seed/php-sdk/allof/src/Types/UserSearchResponse.php +++ /dev/null @@ -1,43 +0,0 @@ - $results Current page of results from the requested resource. - */ - #[JsonProperty('results'), ArrayType([User::class])] - public ?array $results; - - /** - * @var PagingCursors $paging - */ - #[JsonProperty('paging')] - public PagingCursors $paging; - - /** - * @param array{ - * paging: PagingCursors, - * results?: ?array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->results = $values['results'] ?? null; - $this->paging = $values['paging']; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->toJson(); - } -} diff --git a/seed/php-sdk/allof/src/Utils/File.php b/seed/php-sdk/allof/src/Utils/File.php deleted file mode 100644 index ee2af27b8909..000000000000 --- a/seed/php-sdk/allof/src/Utils/File.php +++ /dev/null @@ -1,129 +0,0 @@ -filename = $filename; - $this->contentType = $contentType; - $this->stream = $stream; - } - - /** - * Creates a File instance from a filepath. - * - * @param string $filepath - * @param ?string $filename - * @param ?string $contentType - * @return File - * @throws Exception - */ - public static function createFromFilepath( - string $filepath, - ?string $filename = null, - ?string $contentType = null, - ): File { - $resource = @fopen($filepath, 'r'); - if (!$resource) { - throw new Exception("Unable to open file $filepath"); - } - $stream = Psr17FactoryDiscovery::findStreamFactory()->createStreamFromResource($resource); - if (!$stream->isReadable()) { - throw new Exception("File $filepath is not readable"); - } - return new self( - stream: $stream, - filename: $filename ?? basename($filepath), - contentType: $contentType, - ); - } - - /** - * Creates a File instance from a string. - * - * @param string $content - * @param ?string $filename - * @param ?string $contentType - * @return File - */ - public static function createFromString( - string $content, - ?string $filename, - ?string $contentType = null, - ): File { - return new self( - stream: Psr17FactoryDiscovery::findStreamFactory()->createStream($content), - filename: $filename, - contentType: $contentType, - ); - } - - /** - * Maps this File into a multipart form data part. - * - * @param string $name The name of the multipart form data part. - * @param ?string $contentType Overrides the Content-Type associated with the file, if any. - * @return MultipartFormDataPart - */ - public function toMultipartFormDataPart(string $name, ?string $contentType = null): MultipartFormDataPart - { - $contentType ??= $this->contentType; - $headers = $contentType !== null - ? ['Content-Type' => $contentType] - : null; - - return new MultipartFormDataPart( - name: $name, - value: $this->stream, - filename: $this->filename, - headers: $headers, - ); - } - - /** - * Closes the file stream. - */ - public function close(): void - { - $this->stream->close(); - } - - /** - * Destructor to ensure stream is closed. - */ - public function __destruct() - { - try { - $this->close(); - } catch (\Throwable) { - // Swallow errors during garbage collection to avoid fatal errors. - } - } -} diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php deleted file mode 100644 index 1e384a760b83..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example0/snippet.php +++ /dev/null @@ -1,15 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->searchRuleTypes( - new SearchRuleTypesRequest([]), -); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php deleted file mode 100644 index 826582f7bc27..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example1/snippet.php +++ /dev/null @@ -1,17 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->searchRuleTypes( - new SearchRuleTypesRequest([ - 'query' => 'query', - ]), -); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php deleted file mode 100644 index 5539210a6505..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example2/snippet.php +++ /dev/null @@ -1,19 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php deleted file mode 100644 index 5539210a6505..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example3/snippet.php +++ /dev/null @@ -1,19 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->createRule( - new RuleCreateRequest([ - 'name' => 'name', - 'executionContext' => RuleExecutionContext::Prod->value, - ]), -); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php deleted file mode 100644 index 477f8f61c34c..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example4/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->listUsers(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php deleted file mode 100644 index 477f8f61c34c..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example5/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->listUsers(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php deleted file mode 100644 index 4b4021abb4a6..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example6/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getEntity(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php deleted file mode 100644 index 4b4021abb4a6..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example7/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getEntity(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php deleted file mode 100644 index bcf929bea40c..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example8/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getOrganization(); diff --git a/seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php b/seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php deleted file mode 100644 index bcf929bea40c..000000000000 --- a/seed/php-sdk/allof/src/dynamic-snippets/example9/snippet.php +++ /dev/null @@ -1,12 +0,0 @@ - 'https://api.fern.com', - ], -); -$client->getOrganization(); diff --git a/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php b/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php deleted file mode 100644 index df36dc918894..000000000000 --- a/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php +++ /dev/null @@ -1,1074 +0,0 @@ -name = $values['name']; - } - - /** - * @return string - */ - public function getName(): ?string - { - return $this->name; - } -} - -class RawClientTest extends TestCase -{ - private string $baseUrl = 'https://api.example.com'; - private MockHttpClient $mockClient; - private RawClient $rawClient; - - protected function setUp(): void - { - $this->mockClient = new MockHttpClient(); - $this->rawClient = new RawClient(['client' => $this->mockClient, 'maxRetries' => 0]); - } - - /** - * @throws ClientExceptionInterface - */ - public function testHeaders(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - /** - * @throws ClientExceptionInterface - */ - public function testQueryParameters(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - /** - * @throws ClientExceptionInterface - */ - public function testJsonBody(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(JsonEncoder::encode($body), (string)$lastRequest->getBody()); - } - - public function testAdditionalHeaders(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = new JsonRequest([ - 'name' => 'john.doe' - ]); - $headers = [ - 'X-API-Version' => '1.0.0', - ]; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - $headers, - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'headers' => [ - 'X-Tenancy' => 'test' - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('1.0.0', $lastRequest->getHeaderLine('X-API-Version')); - $this->assertEquals('test', $lastRequest->getHeaderLine('X-Tenancy')); - } - - public function testOverrideAdditionalHeaders(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = new JsonRequest([ - 'name' => 'john.doe' - ]); - $headers = [ - 'X-API-Version' => '1.0.0', - ]; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - $headers, - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'headers' => [ - 'X-API-Version' => '2.0.0' - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('2.0.0', $lastRequest->getHeaderLine('X-API-Version')); - } - - public function testAdditionalBodyProperties(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = new JsonRequest([ - 'name' => 'john.doe' - ]); - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'bodyProperties' => [ - 'age' => 42 - ] - ] - ); - - $expectedJson = [ - 'name' => 'john.doe', - 'age' => 42 - ]; - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); - } - - public function testOverrideAdditionalBodyProperties(): void - { - $this->mockClient->append(self::createResponse(200)); - - $body = [ - 'name' => 'john.doe' - ]; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'bodyProperties' => [ - 'name' => 'jane.doe' - ] - ] - ); - - $expectedJson = [ - 'name' => 'jane.doe', - ]; - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(JsonEncoder::encode($expectedJson), (string)$lastRequest->getBody()); - } - - public function testAdditionalQueryParameters(): void - { - $this->mockClient->append(self::createResponse(200)); - - $query = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - $query, - [] - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'queryParameters' => [ - 'extra' => 42 - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('key=value&extra=42', $lastRequest->getUri()->getQuery()); - } - - public function testOverrideQueryParameters(): void - { - $this->mockClient->append(self::createResponse(200)); - - $query = ['key' => 'invalid']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - $query, - [] - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'queryParameters' => [ - 'key' => 'value' - ] - ] - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('key=value', $lastRequest->getUri()->getQuery()); - } - - public function testDefaultRetries(): void - { - $this->mockClient->append(self::createResponse(500)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET - ); - - $response = $this->rawClient->sendRequest($request); - $this->assertEquals(500, $response->getStatusCode()); - $this->assertEquals(0, $this->mockClient->count()); - } - - /** - * @throws ClientExceptionInterface - */ - public function testExplicitRetriesSuccess(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(200)); - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(0, $mockClient->count()); - } - - public function testExplicitRetriesFailure(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(500), self::createResponse(500), self::createResponse(500)); - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(500, $response->getStatusCode()); - $this->assertEquals(0, $mockClient->count()); - } - - /** - * @throws ClientExceptionInterface - */ - public function testShouldRetryOnStatusCodes(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(408), - self::createResponse(429), - self::createResponse(500), - self::createResponse(501), - self::createResponse(502), - self::createResponse(503), - self::createResponse(504), - self::createResponse(505), - self::createResponse(599), - self::createResponse(200), - ); - $countOfErrorRequests = $mockClient->count() - 1; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: $countOfErrorRequests, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(0, $mockClient->count()); - } - - public function testShouldFailOn400Response(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(400), self::createResponse(200)); - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - sleepFunction: function (int $_microseconds): void { - }, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $response = $retryClient->sendRequest($request); - - $this->assertEquals(400, $response->getStatusCode()); - $this->assertEquals(1, $mockClient->count()); - } - - public function testRetryAfterSecondsHeaderControlsDelay(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, ['Retry-After' => '10']), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); // Convert microseconds to milliseconds - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(10000, $capturedDelays[0]); - $this->assertLessThanOrEqual(12000, $capturedDelays[0]); - } - - public function testRetryAfterHttpDateHeaderIsHandled(): void - { - $retryAfterDate = gmdate('D, d M Y H:i:s \G\M\T', time() + 5); - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, ['Retry-After' => $retryAfterDate]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThan(0, $capturedDelays[0]); - $this->assertLessThanOrEqual(60000, $capturedDelays[0]); - } - - public function testRateLimitResetHeaderControlsDelay(): void - { - $resetTime = (int) floor(microtime(true)) + 5; - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThan(0, $capturedDelays[0]); - $this->assertLessThanOrEqual(60000, $capturedDelays[0]); - } - - public function testRateLimitResetHeaderRespectsMaxDelayAndPositiveJitter(): void - { - $resetTime = (int) floor(microtime(true)) + 1000; - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(429, ['X-RateLimit-Reset' => (string) $resetTime]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 1, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); - $this->assertLessThanOrEqual(72000, $capturedDelays[0]); - } - - public function testExponentialBackoffWithSymmetricJitterWhenNoHeaders(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 1, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(900, $capturedDelays[0]); - $this->assertLessThanOrEqual(1100, $capturedDelays[0]); - } - - public function testRetryAfterHeaderTakesPrecedenceOverRateLimitReset(): void - { - $resetTime = (int) floor(microtime(true)) + 30; - - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, [ - 'Retry-After' => '5', - 'X-RateLimit-Reset' => (string) $resetTime, - ]), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(5000, $capturedDelays[0]); - $this->assertLessThanOrEqual(6000, $capturedDelays[0]); - } - - public function testMaxDelayCapIsApplied(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append( - self::createResponse(503, ['Retry-After' => '120']), - self::createResponse(200), - ); - - $capturedDelays = []; - $sleepFunction = function (int $microseconds) use (&$capturedDelays): void { - $capturedDelays[] = (int) ($microseconds / 1000); - }; - - $retryClient = new RetryDecoratingClient( - $mockClient, - maxRetries: 2, - baseDelay: 1000, - sleepFunction: $sleepFunction, - ); - - $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); - $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); - - $retryClient->sendRequest($request); - - $this->assertCount(1, $capturedDelays); - $this->assertGreaterThanOrEqual(60000, $capturedDelays[0]); - $this->assertLessThanOrEqual(72000, $capturedDelays[0]); - } - - public function testMultipartContentTypeIncludesBoundary(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->add('field', 'value'); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $contentType = $lastRequest->getHeaderLine('Content-Type'); - $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); - - $boundary = substr($contentType, strlen('multipart/form-data; boundary=')); - $body = (string) $lastRequest->getBody(); - $this->assertStringContainsString("--{$boundary}\r\n", $body); - $this->assertStringContainsString("Content-Disposition: form-data; name=\"field\"\r\n", $body); - $this->assertStringContainsString("value", $body); - $this->assertStringContainsString("--{$boundary}--\r\n", $body); - } - - public function testMultipartWithFilename(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->addPart(new MultipartFormDataPart( - name: 'document', - value: 'file-contents', - filename: 'report.pdf', - headers: ['Content-Type' => 'application/pdf'], - )); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $body = (string) $lastRequest->getBody(); - $this->assertStringContainsString( - 'Content-Disposition: form-data; name="document"; filename="report.pdf"', - $body, - ); - $this->assertStringContainsString('Content-Type: application/pdf', $body); - $this->assertStringContainsString('file-contents', $body); - } - - public function testMultipartWithMultipleParts(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->add('name', 'John'); - $formData->add('age', 30); - $formData->addPart(new MultipartFormDataPart( - name: 'avatar', - value: 'image-data', - filename: 'avatar.png', - )); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/profile', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $body = (string) $lastRequest->getBody(); - $this->assertStringContainsString('name="name"', $body); - $this->assertStringContainsString('John', $body); - $this->assertStringContainsString('name="age"', $body); - $this->assertStringContainsString('30', $body); - $this->assertStringContainsString('name="avatar"; filename="avatar.png"', $body); - $this->assertStringContainsString('image-data', $body); - } - - public function testMultipartDoesNotIncludeJsonContentType(): void - { - $this->mockClient->append(self::createResponse(200)); - - $formData = new MultipartFormData(); - $formData->add('field', 'value'); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $contentType = $lastRequest->getHeaderLine('Content-Type'); - $this->assertStringStartsWith('multipart/form-data; boundary=', $contentType); - $this->assertStringNotContainsString('application/json', $contentType); - } - - public function testMultipartNullBodySendsNoBody(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('', (string) $lastRequest->getBody()); - $this->assertStringNotContainsString('multipart/form-data', $lastRequest->getHeaderLine('Content-Type')); - } - - public function testJsonNullBodySendsNoBody(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - ); - - $this->rawClient->sendRequest($request); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('', (string) $lastRequest->getBody()); - } - - public function testEmptyJsonBodySerializesAsObject(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - ['key' => 'value'], - ); - - $this->rawClient->sendRequest( - $request, - options: [ - 'bodyProperties' => [ - 'key' => 'value', - ], - ], - ); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - // When bodyProperties override all keys, the merged result should still - // serialize as a JSON object {}, not an array []. - $decoded = json_decode((string) $lastRequest->getBody(), true); - $this->assertIsArray($decoded); - $this->assertEquals('value', $decoded['key']); - } - - public function testAuthHeadersAreIncluded(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], - ]); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - $rawClient->sendRequest($request); - - $lastRequest = $mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); - } - - public function testAuthHeadersAreIncludedInMultipart(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'getAuthHeaders' => fn () => ['Authorization' => 'Bearer test-token'], - ]); - - $formData = new MultipartFormData(); - $formData->add('field', 'value'); - - $request = new MultipartApiRequest( - $this->baseUrl, - '/upload', - HttpMethod::POST, - [], - [], - $formData, - ); - - $rawClient->sendRequest($request); - - $lastRequest = $mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - - $this->assertEquals('Bearer test-token', $lastRequest->getHeaderLine('Authorization')); - $this->assertStringStartsWith('multipart/form-data; boundary=', $lastRequest->getHeaderLine('Content-Type')); - } - - /** - * Creates a PSR-7 response using discovery, without depending on any specific implementation. - * - * @param int $statusCode - * @param array $headers - * @param string $body - * @return ResponseInterface - */ - private static function createResponse( - int $statusCode = 200, - array $headers = [], - string $body = '', - ): ResponseInterface { - $response = \Http\Discovery\Psr17FactoryDiscovery::findResponseFactory() - ->createResponse($statusCode); - foreach ($headers as $name => $value) { - $response = $response->withHeader($name, $value); - } - if ($body !== '') { - $response = $response->withBody( - \Http\Discovery\Psr17FactoryDiscovery::findStreamFactory() - ->createStream($body), - ); - } - return $response; - } - - - public function testTimeoutOptionIsAccepted(): void - { - $this->mockClient->append(self::createResponse(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - // MockHttpClient is not Guzzle/Symfony, so a warning is triggered once. - set_error_handler(static function (int $errno, string $errstr): bool { - return $errno === E_USER_WARNING - && str_contains($errstr, 'Timeout option is not supported'); - }); - - try { - $response = $this->rawClient->sendRequest( - $request, - options: [ - 'timeout' => 3.0 - ] - ); - - $this->assertEquals(200, $response->getStatusCode()); - - $lastRequest = $this->mockClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $lastRequest); - } finally { - restore_error_handler(); - } - } - - public function testClientLevelTimeoutIsAccepted(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'timeout' => 5.0, - ]); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - set_error_handler(static function (int $errno, string $errstr): bool { - return $errno === E_USER_WARNING - && str_contains($errstr, 'Timeout option is not supported'); - }); - - try { - $response = $rawClient->sendRequest($request); - $this->assertEquals(200, $response->getStatusCode()); - } finally { - restore_error_handler(); - } - } - - public function testPerRequestTimeoutOverridesClientTimeout(): void - { - $mockClient = new MockHttpClient(); - $mockClient->append(self::createResponse(200)); - - $rawClient = new RawClient([ - 'client' => $mockClient, - 'maxRetries' => 0, - 'timeout' => 5.0, - ]); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ); - - set_error_handler(static function (int $errno, string $errstr): bool { - return $errno === E_USER_WARNING - && str_contains($errstr, 'Timeout option is not supported'); - }); - - try { - $response = $rawClient->sendRequest( - $request, - options: [ - 'timeout' => 1.0 - ] - ); - - $this->assertEquals(200, $response->getStatusCode()); - } finally { - restore_error_handler(); - } - } - - public function testDiscoveryFindsHttpClient(): void - { - // HttpClientBuilder::build() with no client arg uses Psr18ClientDiscovery. - $client = HttpClientBuilder::build(); - $this->assertInstanceOf(\Psr\Http\Client\ClientInterface::class, $client); - } - - public function testDiscoveryFindsFactories(): void - { - $requestFactory = HttpClientBuilder::requestFactory(); - $this->assertInstanceOf(\Psr\Http\Message\RequestFactoryInterface::class, $requestFactory); - - $streamFactory = HttpClientBuilder::streamFactory(); - $this->assertInstanceOf(\Psr\Http\Message\StreamFactoryInterface::class, $streamFactory); - - // Verify they produce usable objects - $request = $requestFactory->createRequest('GET', 'https://example.com'); - $this->assertEquals('GET', $request->getMethod()); - - $stream = $streamFactory->createStream('hello'); - $this->assertEquals('hello', (string) $stream); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php b/seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php deleted file mode 100644 index 2c32002340e7..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/AdditionalPropertiesTest.php +++ /dev/null @@ -1,76 +0,0 @@ -name; - } - - /** - * @return string|null - */ - public function getEmail(): ?string - { - return $this->email; - } - - /** - * @param array{ - * name: string, - * email?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->name = $values['name']; - $this->email = $values['email'] ?? null; - } -} - -class AdditionalPropertiesTest extends TestCase -{ - public function testExtraProperties(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'name' => 'john.doe', - 'email' => 'john.doe@example.com', - 'age' => 42 - ], - ); - - $person = Person::fromJson($expectedJson); - $this->assertEquals('john.doe', $person->getName()); - $this->assertEquals('john.doe@example.com', $person->getEmail()); - $this->assertEquals( - [ - 'age' => 42 - ], - $person->getAdditionalProperties(), - ); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php deleted file mode 100644 index e7794d652432..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/DateArrayTest.php +++ /dev/null @@ -1,54 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTest extends TestCase -{ - public function testDateTimeInArrays(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ], - ); - - $object = DateArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php deleted file mode 100644 index b5f217e01f76..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/EmptyArrayTest.php +++ /dev/null @@ -1,71 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArrayTest extends TestCase -{ - public function testEmptyArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ], - ); - - $object = EmptyArray::fromJson($expectedJson); - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/EnumTest.php b/seed/php-sdk/allof/tests/Core/Json/EnumTest.php deleted file mode 100644 index 72dc6f2cfa00..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/EnumTest.php +++ /dev/null @@ -1,77 +0,0 @@ -value; - } -} - -class ShapeType extends JsonSerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = JsonEncoder::encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ]); - - $actualJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $actualJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php b/seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php deleted file mode 100644 index 4c288378b48b..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/ExhaustiveTest.php +++ /dev/null @@ -1,197 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class Type extends JsonSerializableType -{ - /** - * @var Nested nestedType - */ - #[JsonProperty('nested_type')] - public Nested $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[Date(Date::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[Date(Date::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(Nested::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: Nested, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class ExhaustiveTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in Type. - */ - public function testExhaustive(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // Omit 'nullable_property' to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56Z', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> - ], - ); - - $object = Type::fromJson($expectedJson); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/InvalidTest.php b/seed/php-sdk/allof/tests/Core/Json/InvalidTest.php deleted file mode 100644 index 9d845ea113b8..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/InvalidTest.php +++ /dev/null @@ -1,42 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTest extends TestCase -{ - public function testInvalidJsonThrowsException(): void - { - $this->expectException(\TypeError::class); - $json = JsonEncoder::encode( - [ - 'integer_property' => 'not_an_integer' - ], - ); - Invalid::fromJson($json); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php deleted file mode 100644 index 8fbbeb939f02..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/NestedUnionArrayTest.php +++ /dev/null @@ -1,89 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArray extends JsonSerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTest extends TestCase -{ - public function testNestedUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ], - ); - - $object = NestedUnionArray::fromJson($expectedJson); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php b/seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php deleted file mode 100644 index ce20a2442825..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/NullPropertyTest.php +++ /dev/null @@ -1,53 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullProperty( - [ - "nonNullProperty" => "Test String", - "nullProperty" => null - ] - ); - - $serialized = $object->jsonSerialize(); - $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); - $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php deleted file mode 100644 index d1749c434a4c..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/NullableArrayTest.php +++ /dev/null @@ -1,49 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTest extends TestCase -{ - public function testNullableArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'nullable_string_array' => ['one', null, 'three'] - ], - ); - - $object = NullableArray::fromJson($expectedJson); - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/ScalarTest.php b/seed/php-sdk/allof/tests/Core/Json/ScalarTest.php deleted file mode 100644 index ad4db0251bb5..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/ScalarTest.php +++ /dev/null @@ -1,116 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats - ], - ); - - $object = Scalar::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/TraitTest.php b/seed/php-sdk/allof/tests/Core/Json/TraitTest.php deleted file mode 100644 index e18f06d4191b..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/TraitTest.php +++ /dev/null @@ -1,60 +0,0 @@ -integerProperty = $values['integerProperty']; - $this->stringProperty = $values['stringProperty']; - } -} - -class TraitTest extends TestCase -{ - public function testTraitPropertyAndString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'integer_property' => 42, - 'string_property' => 'Hello, World!', - ], - ); - - $object = TypeWithTrait::fromJson($expectedJson); - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php b/seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php deleted file mode 100644 index de20cf9fde1b..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/UnionArrayTest.php +++ /dev/null @@ -1,57 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class UnionArrayTest extends TestCase -{ - public function testUnionArray(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00Z', - 2 => null, - 3 => 'Some String' - ] - ], - ); - - $object = UnionArray::fromJson($expectedJson); - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php b/seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php deleted file mode 100644 index f733062cfabc..000000000000 --- a/seed/php-sdk/allof/tests/Core/Json/UnionPropertyTest.php +++ /dev/null @@ -1,111 +0,0 @@ - 'integer'], UnionProperty::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionProperty - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => [1 => 100, 2 => 200] - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => new UnionProperty( - [ - 'complexUnion' => 'Nested String' - ] - ) - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $expectedJson = JsonEncoder::encode( - [], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 42 - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $expectedJson = JsonEncoder::encode( - [ - 'complexUnion' => 'Some String' - ], - ); - - $object = UnionProperty::fromJson($expectedJson); - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $actualJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/pydantic/allof-inline/.github/workflows/ci.yml b/seed/pydantic/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index fd1df043d08d..000000000000 --- a/seed/pydantic/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: ci -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Compile - run: poetry run mypy . - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - - name: Test - run: poetry run pytest -rP -n auto . - - - name: Install aiohttp extra - run: poetry install --extras aiohttp - - - name: Test (aiohttp) - run: poetry run pytest -rP -n auto -m aiohttp . - - publish: - needs: [compile, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Publish to pypi - run: | - poetry config repositories.remote - poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" - env: - PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} - PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/pydantic/allof-inline/.gitignore b/seed/pydantic/allof-inline/.gitignore deleted file mode 100644 index d2e4ca808d21..000000000000 --- a/seed/pydantic/allof-inline/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.mypy_cache/ -.ruff_cache/ -__pycache__/ -dist/ -poetry.toml diff --git a/seed/pydantic/allof-inline/README.md b/seed/pydantic/allof-inline/README.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/pydantic/allof-inline/poetry.lock b/seed/pydantic/allof-inline/poetry.lock deleted file mode 100644 index e1efb51734e4..000000000000 --- a/seed/pydantic/allof-inline/poetry.lock +++ /dev/null @@ -1,546 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." -optional = false -python-versions = "<3.11,>=3.8" -files = [ - {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, - {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, - {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.2" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, - {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "mypy" -version = "1.13.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "packaging" -version = "26.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "pydantic" -version = "2.12.5" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, - {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.41.5" -typing-extensions = ">=4.14.1" -typing-inspection = ">=0.4.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, - {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, -] - -[package.dependencies] -typing-extensions = ">=4.14.1" - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.4.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.10" -files = [ - {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, - {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, -] - -[package.dependencies] -backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} -pytest = ">=8.2,<10" -typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-xdist" -version = "3.8.0" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, - {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "ruff" -version = "0.11.5" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, - {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, - {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, - {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, - {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, - {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, - {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "tomli" -version = "2.4.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, - {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, - {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, - {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, - {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, - {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, - {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, - {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, - {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, - {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, - {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, - {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, - {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, - {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, - {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, - {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, - {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, - {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, - {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, -] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20260408" -description = "Typing stubs for python-dateutil" -optional = false -python-versions = ">=3.10" -files = [ - {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, - {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "283a69ea9a1eb309027a93a4e056bedb5cb3c6018ca2eec381af57784ad1eb58" diff --git a/seed/pydantic/allof-inline/pyproject.toml b/seed/pydantic/allof-inline/pyproject.toml deleted file mode 100644 index 2ce1377736c7..000000000000 --- a/seed/pydantic/allof-inline/pyproject.toml +++ /dev/null @@ -1,93 +0,0 @@ -[project] -name = "fern_allof-inline" -dynamic = ["version"] - -[tool.poetry] -name = "fern_allof-inline" -version = "0.0.1" -description = "" -readme = "README.md" -authors = [] -keywords = [ - "fern", - "test" -] - -classifiers = [ - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Programming Language :: Python :: 3.15", - "Operating System :: OS Independent", - "Operating System :: POSIX", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Topic :: Software Development :: Libraries :: Python Modules", - "Typing :: Typed" -] -packages = [ - { include = "seed/api", from = "src"} -] - -[tool.poetry.urls] -Documentation = 'https://buildwithfern.com/learn' -Homepage = 'https://buildwithfern.com/' -Repository = 'https://github.com/allof-inline/fern' - -[tool.poetry.dependencies] -python = "^3.10" -pydantic = ">= 1.9.2" -pydantic-core = ">=2.18.2,<2.44.0" - -[tool.poetry.group.dev.dependencies] -mypy = "==1.13.0" -pytest = "^8.2.0" -pytest-asyncio = "^1.0.0" -pytest-xdist = "^3.6.1" -python-dateutil = "^2.9.0" -types-python-dateutil = "^2.9.0.20240316" -ruff = "==0.11.5" - -[tool.pytest.ini_options] -testpaths = [ "tests" ] -asyncio_mode = "auto" -markers = [ - "aiohttp: tests that require httpx_aiohttp to be installed", -] - -[tool.mypy] -plugins = ["pydantic.mypy"] - -[tool.ruff] -line-length = 120 - -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "F", # pyflakes - "I", # isort -] -ignore = [ - "E402", # Module level import not at top of file - "E501", # Line too long - "E711", # Comparison to `None` should be `cond is not None` - "E712", # Avoid equality comparisons to `True`; use `if ...:` checks - "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks - "E722", # Do not use bare `except` - "E731", # Do not assign a `lambda` expression, use a `def` - "F821", # Undefined name - "F841" # Local variable ... is assigned to but never used -] - -[tool.ruff.lint.isort] -section-order = ["future", "standard-library", "third-party", "first-party"] - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/seed/pydantic/allof-inline/requirements.txt b/seed/pydantic/allof-inline/requirements.txt deleted file mode 100644 index e090fd9ba093..000000000000 --- a/seed/pydantic/allof-inline/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pydantic>= 1.9.2 -pydantic-core>=2.18.2,<2.44.0 diff --git a/seed/pydantic/allof-inline/snippet.json b/seed/pydantic/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/pydantic/allof-inline/src/seed/api/__init__.py b/seed/pydantic/allof-inline/src/seed/api/__init__.py deleted file mode 100644 index 881ca901e048..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/__init__.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -from .audit_info import AuditInfo -from .base_org import BaseOrg -from .base_org_metadata import BaseOrgMetadata -from .combined_entity import CombinedEntity -from .combined_entity_status import CombinedEntityStatus -from .describable import Describable -from .detailed_org import DetailedOrg -from .detailed_org_metadata import DetailedOrgMetadata -from .identifiable import Identifiable -from .organization import Organization -from .organization_metadata import OrganizationMetadata -from .paginated_result import PaginatedResult -from .paging_cursors import PagingCursors -from .rule_execution_context import RuleExecutionContext -from .rule_response import RuleResponse -from .rule_response_status import RuleResponseStatus -from .rule_type import RuleType -from .rule_type_search_response import RuleTypeSearchResponse -from .user import User -from .user_search_response import UserSearchResponse - -__all__ = [ - "AuditInfo", - "BaseOrg", - "BaseOrgMetadata", - "CombinedEntity", - "CombinedEntityStatus", - "Describable", - "DetailedOrg", - "DetailedOrgMetadata", - "Identifiable", - "Organization", - "OrganizationMetadata", - "PaginatedResult", - "PagingCursors", - "RuleExecutionContext", - "RuleResponse", - "RuleResponseStatus", - "RuleType", - "RuleTypeSearchResponse", - "User", - "UserSearchResponse", -] diff --git a/seed/pydantic/allof-inline/src/seed/api/audit_info.py b/seed/pydantic/allof-inline/src/seed/api/audit_info.py deleted file mode 100644 index 5b00ec223d32..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/audit_info.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -import pydantic -import typing_extensions -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .core.serialization import FieldMetadata - - -class AuditInfo(UniversalBaseModel): - """ - Common audit metadata. - """ - - created_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="createdBy"), - pydantic.Field(alias="createdBy", description="The user who created this resource."), - ] = None - created_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="createdDateTime"), - pydantic.Field(alias="createdDateTime", description="When this resource was created."), - ] = None - modified_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="modifiedBy"), - pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), - ] = None - modified_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="modifiedDateTime"), - pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/base_org.py b/seed/pydantic/allof-inline/src/seed/api/base_org.py deleted file mode 100644 index 512f3ed40d4e..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/base_org.py +++ /dev/null @@ -1,19 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .base_org_metadata import BaseOrgMetadata -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class BaseOrg(UniversalBaseModel): - id: str - metadata: typing.Optional[BaseOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py b/seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py deleted file mode 100644 index 8c58e204c200..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/base_org_metadata.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class BaseOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from BaseOrg. - """ - - tier: typing.Optional[str] = pydantic.Field(default=None) - """ - Subscription tier. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/combined_entity.py b/seed/pydantic/allof-inline/src/seed/api/combined_entity.py deleted file mode 100644 index a8e7f211dc26..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/combined_entity.py +++ /dev/null @@ -1,33 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .combined_entity_status import CombinedEntityStatus -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class CombinedEntity(UniversalBaseModel): - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Describable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - status: CombinedEntityStatus - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py b/seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py deleted file mode 100644 index 42ac60430cd7..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/combined_entity_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/pydantic/allof-inline/src/seed/api/core/__init__.py b/seed/pydantic/allof-inline/src/seed/api/core/__init__.py deleted file mode 100644 index f9bdc1729e86..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/core/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime -from .pydantic_utilities import ( - IS_PYDANTIC_V2, - UniversalBaseModel, - UniversalRootModel, - parse_obj_as, - universal_field_validator, - universal_root_validator, - update_forward_refs, -) -from .serialization import FieldMetadata - -__all__ = [ - "FieldMetadata", - "IS_PYDANTIC_V2", - "Rfc2822DateTime", - "UniversalBaseModel", - "UniversalRootModel", - "parse_obj_as", - "parse_rfc2822_datetime", - "serialize_datetime", - "universal_field_validator", - "universal_root_validator", - "update_forward_refs", -] diff --git a/seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py b/seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py deleted file mode 100644 index a12b2ad03c53..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/core/datetime_utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -from email.utils import parsedate_to_datetime -from typing import Any - -import pydantic - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - - -def parse_rfc2822_datetime(v: Any) -> dt.datetime: - """ - Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") - into a datetime object. If the value is already a datetime, return it as-is. - Falls back to ISO 8601 parsing if RFC 2822 parsing fails. - """ - if isinstance(v, dt.datetime): - return v - if isinstance(v, str): - try: - return parsedate_to_datetime(v) - except Exception: - pass - # Fallback to ISO 8601 parsing - return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) - raise ValueError(f"Expected str or datetime, got {type(v)}") - - -class Rfc2822DateTime(dt.datetime): - """A datetime subclass that parses RFC 2822 date strings. - - On Pydantic V1, uses __get_validators__ for pre-validation. - On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. - """ - - @classmethod - def __get_validators__(cls): # type: ignore[no-untyped-def] - yield parse_rfc2822_datetime - - @classmethod - def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] - from pydantic_core import core_schema - - return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) - - -def serialize_datetime(v: dt.datetime) -> str: - """ - Serialize a datetime including timezone info. - - Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. - - UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. - """ - - def _serialize_zoned_datetime(v: dt.datetime) -> str: - if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): - # UTC is a special case where we use "Z" at the end instead of "+00:00" - return v.isoformat().replace("+00:00", "Z") - else: - # Delegate to the typical +/- offset format - return v.isoformat() - - if v.tzinfo is not None: - return _serialize_zoned_datetime(v) - else: - local_tz = dt.datetime.now().astimezone().tzinfo - localized_dt = v.replace(tzinfo=local_tz) - return _serialize_zoned_datetime(localized_dt) diff --git a/seed/pydantic/allof-inline/src/seed/api/core/enum.py b/seed/pydantic/allof-inline/src/seed/api/core/enum.py deleted file mode 100644 index a3d17a67b128..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/core/enum.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -""" -Provides a StrEnum base class that works across Python versions. - -For Python >= 3.11, this re-exports the standard library enum.StrEnum. -For older Python versions, this defines a compatible StrEnum using the -(str, Enum) mixin pattern so that generated SDKs can use a single base -class in all supported Python versions. -""" - -import enum -import sys - -if sys.version_info >= (3, 11): - from enum import StrEnum -else: - - class StrEnum(str, enum.Enum): - pass diff --git a/seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py b/seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py deleted file mode 100644 index ed95da8bf8ce..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/core/pydantic_utilities.py +++ /dev/null @@ -1,515 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# nopycln: file -import datetime as dt -import inspect -import json -import logging -from collections import defaultdict -from dataclasses import asdict -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - List, - Mapping, - Optional, - Tuple, - Type, - TypeVar, - Union, - cast, -) - -import pydantic -import typing_extensions -from pydantic.fields import FieldInfo as _FieldInfo - -_logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - from .http_sse._models import ServerSentEvent - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - -if IS_PYDANTIC_V2: - _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] - _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] - - def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value - return _datetime_adapter.validate_python(value) - - def parse_date(value: Any) -> dt.date: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value.date() - if isinstance(value, dt.date): - return value - return _date_adapter.validate_python(value) - - # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. - from typing import get_args as get_args # type: ignore[assignment] - from typing import get_origin as get_origin # type: ignore[assignment] - - def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return typing_extensions.get_origin(tp) is typing_extensions.Literal - - def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] - - # Inline encoders_by_type to avoid importing from pydantic.v1.json - import re as _re - from collections import deque as _deque - from decimal import Decimal as _Decimal - from enum import Enum as _Enum - from ipaddress import ( - IPv4Address as _IPv4Address, - ) - from ipaddress import ( - IPv4Interface as _IPv4Interface, - ) - from ipaddress import ( - IPv4Network as _IPv4Network, - ) - from ipaddress import ( - IPv6Address as _IPv6Address, - ) - from ipaddress import ( - IPv6Interface as _IPv6Interface, - ) - from ipaddress import ( - IPv6Network as _IPv6Network, - ) - from pathlib import Path as _Path - from types import GeneratorType as _GeneratorType - from uuid import UUID as _UUID - - from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] - - def _decimal_encoder(dec_value: Any) -> Any: - if dec_value.as_tuple().exponent >= 0: - return int(dec_value) - return float(dec_value) - - encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] - bytes: lambda o: o.decode(), - dt.date: lambda o: o.isoformat(), - dt.datetime: lambda o: o.isoformat(), - dt.time: lambda o: o.isoformat(), - dt.timedelta: lambda td: td.total_seconds(), - _Decimal: _decimal_encoder, - _Enum: lambda o: o.value, - frozenset: list, - _deque: list, - _GeneratorType: list, - _IPv4Address: str, - _IPv4Interface: str, - _IPv4Network: str, - _IPv6Address: str, - _IPv6Interface: str, - _IPv6Network: str, - _Path: str, - _re.Pattern: lambda o: o.pattern, - set: list, - _UUID: str, - } -else: - from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] - from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] - from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] - from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] - from pydantic.typing import get_args as get_args # type: ignore[no-redef] - from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] - from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] - from pydantic.typing import is_union as is_union # type: ignore[no-redef] - -from .datetime_utils import serialize_datetime -from typing_extensions import TypeAlias - -T = TypeVar("T") -Model = TypeVar("Model", bound=pydantic.BaseModel) - - -def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: - """ - Extract the discriminator field name and union variants from a discriminated union type. - Supports Annotated[Union[...], Field(discriminator=...)] patterns. - Returns (discriminator, variants) or (None, None) if not a discriminated union. - """ - origin = typing_extensions.get_origin(type_) - - if origin is typing_extensions.Annotated: - args = typing_extensions.get_args(type_) - if len(args) >= 2: - inner_type = args[0] - # Check annotations for discriminator - discriminator = None - for annotation in args[1:]: - if hasattr(annotation, "discriminator"): - discriminator = getattr(annotation, "discriminator", None) - break - - if discriminator: - inner_origin = typing_extensions.get_origin(inner_type) - if inner_origin is Union: - variants = list(typing_extensions.get_args(inner_type)) - return discriminator, variants - return None, None - - -def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: - """Get the type annotation of a field from a Pydantic model.""" - if IS_PYDANTIC_V2: - fields = getattr(model, "model_fields", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.annotation) - else: - fields = getattr(model, "__fields__", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.outer_type_) - return None - - -def _find_variant_by_discriminator( - variants: List[Type[Any]], - discriminator: str, - discriminator_value: Any, -) -> Optional[Type[Any]]: - """Find the union variant that matches the discriminator value.""" - for variant in variants: - if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): - continue - - disc_annotation = _get_field_annotation(variant, discriminator) - if disc_annotation and is_literal_type(disc_annotation): - literal_args = get_args(disc_annotation) - if literal_args and literal_args[0] == discriminator_value: - return variant - return None - - -def _is_string_type(type_: Type[Any]) -> bool: - """Check if a type is str or Optional[str].""" - if type_ is str: - return True - - origin = typing_extensions.get_origin(type_) - if origin is Union: - args = typing_extensions.get_args(type_) - # Optional[str] = Union[str, None] - non_none_args = [a for a in args if a is not type(None)] - if len(non_none_args) == 1 and non_none_args[0] is str: - return True - - return False - - -def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: - """ - Parse a ServerSentEvent into the appropriate type. - - Handles two scenarios based on where the discriminator field is located: - - 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. - The union describes the data content, not the SSE envelope. - -> Returns: json.loads(data) parsed into the type - - 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. - The union describes the full SSE event structure. - -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string - - Args: - sse: The ServerSentEvent object to parse - type_: The target discriminated union type - - Returns: - The parsed object of type T - - Note: - This function is only available in SDK contexts where http_sse module exists. - """ - sse_event = asdict(sse) - discriminator, variants = _get_discriminator_and_variants(type_) - - if discriminator is None or variants is None: - # Not a discriminated union - parse the data field as JSON - data_value = sse_event.get("data") - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - data_value = sse_event.get("data") - - # Check if discriminator is at the top level (event-level discrimination) - if discriminator in sse_event: - # Case 2: Event-level discrimination - # Find the matching variant to check if 'data' field needs JSON parsing - disc_value = sse_event.get(discriminator) - matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) - - if matching_variant is not None: - # Check what type the variant expects for 'data' - data_type = _get_field_annotation(matching_variant, "data") - if data_type is not None and not _is_string_type(data_type): - # Variant expects non-string data - parse JSON - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - new_object = dict(sse_event) - new_object["data"] = parsed_data - return parse_obj_as(type_, new_object) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - # Either no matching variant, data is string type, or JSON parse failed - return parse_obj_as(type_, sse_event) - - else: - # Case 1: Data-level discrimination - # The discriminator is inside the data payload - extract and parse data only - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - -def parse_obj_as(type_: Type[T], object_: Any) -> T: - if IS_PYDANTIC_V2: - adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] - return adapter.validate_python(object_) - return pydantic.parse_obj_as(type_, object_) - - -def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: - if IS_PYDANTIC_V2: - from pydantic_core import to_jsonable_python - - return to_jsonable_python(obj, fallback=fallback_serializer) - return fallback_serializer(obj) - - -class UniversalBaseModel(pydantic.BaseModel): - if IS_PYDANTIC_V2: - model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] - validate_by_name=True, - validate_by_alias=True, - # Allow fields beginning with `model_` to be used in the model - protected_namespaces=(), - ) - - @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] - def serialize_model(self) -> Any: # type: ignore[name-defined] - def _serialize_recursive(obj: Any) -> Any: - if isinstance(obj, dt.datetime): - return serialize_datetime(obj) - elif isinstance(obj, dict): - return {k: _serialize_recursive(v) for k, v in obj.items()} - elif isinstance(obj, list): - return [_serialize_recursive(item) for item in obj] - return obj - - serialized = self.model_dump() # type: ignore[attr-defined] - return _serialize_recursive(serialized) - - else: - - class Config: - populate_by_name = True - smart_union = True - allow_population_by_field_name = True - json_encoders = {dt.datetime: serialize_datetime} - # Allow fields beginning with `model_` to be used in the model - protected_namespaces = () - - def json(self, **kwargs: Any) -> str: - kwargs_with_defaults = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - if IS_PYDANTIC_V2: - return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: Any) -> Dict[str, Any]: - """ - Override the default dict method to `exclude_unset` by default. This function patches - `exclude_unset` to work include fields within non-None default values. - """ - # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 - # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. - # - # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models - # that we have less control over, and this is less intrusive than custom serializers for now. - if IS_PYDANTIC_V2: - kwargs_with_defaults_exclude_unset = { - **kwargs, - "by_alias": True, - "exclude_unset": True, - "exclude_none": False, - } - kwargs_with_defaults_exclude_none = { - **kwargs, - "by_alias": True, - "exclude_none": True, - "exclude_unset": False, - } - return deep_union_pydantic_dicts( - super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] - super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] - ) - - _fields_set = self.__fields_set__.copy() - - fields = _get_model_fields(self.__class__) - for name, field in fields.items(): - if name not in _fields_set: - default = _get_field_default(field) - - # If the default values are non-null act like they've been set - # This effectively allows exclude_unset to work like exclude_none where - # the latter passes through intentionally set none values. - if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): - _fields_set.add(name) - - if default is not None: - self.__fields_set__.add(name) - - kwargs_with_defaults_exclude_unset_include_fields = { - "by_alias": True, - "exclude_unset": True, - "include": _fields_set, - **kwargs, - } - - return super().dict(**kwargs_with_defaults_exclude_unset_include_fields) - - -def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: - converted_list: List[Any] = [] - for i, item in enumerate(source): - destination_value = destination[i] - if isinstance(item, dict): - converted_list.append(deep_union_pydantic_dicts(item, destination_value)) - elif isinstance(item, list): - converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) - else: - converted_list.append(item) - return converted_list - - -def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: - for key, value in source.items(): - node = destination.setdefault(key, {}) - if isinstance(value, dict): - deep_union_pydantic_dicts(value, node) - # Note: we do not do this same processing for sets given we do not have sets of models - # and given the sets are unordered, the processing of the set and matching objects would - # be non-trivial. - elif isinstance(value, list): - destination[key] = _union_list_of_pydantic_dicts(value, node) - else: - destination[key] = value - - return destination - - -if IS_PYDANTIC_V2: - - class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[name-defined, type-arg] - pass - - UniversalRootModel: TypeAlias = V2RootModel -else: - UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] - - -def encode_by_type(o: Any) -> Any: - encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) - for type_, encoder in encoders_by_type.items(): - encoders_by_class_tuples[encoder] += (type_,) - - if type(o) in encoders_by_type: - return encoders_by_type[type(o)](o) - for encoder, classes_tuple in encoders_by_class_tuples.items(): - if isinstance(o, classes_tuple): - return encoder(o) - - -def update_forward_refs(model: Type["Model"], **localns: Any) -> None: - if IS_PYDANTIC_V2: - model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] - else: - model.update_forward_refs(**localns) - - -# Mirrors Pydantic's internal typing -AnyCallable = Callable[..., Any] - - -def universal_root_validator( - pre: bool = False, -) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - # In Pydantic v2, for RootModel we always use "before" mode - # The custom validators transform the input value before the model is created - return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] - - return decorator - - -def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) - - return decorator - - -PydanticField = Union[ModelField, _FieldInfo] - - -def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: - if IS_PYDANTIC_V2: - return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] - return cast(Mapping[str, PydanticField], model.__fields__) - - -def _get_field_default(field: PydanticField) -> Any: - try: - value = field.get_default() # type: ignore[union-attr] - except: - value = field.default - if IS_PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None - return value - return value diff --git a/seed/pydantic/allof-inline/src/seed/api/core/serialization.py b/seed/pydantic/allof-inline/src/seed/api/core/serialization.py deleted file mode 100644 index c36e865cc729..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/core/serialization.py +++ /dev/null @@ -1,276 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import collections -import inspect -import typing - -import pydantic -import typing_extensions - - -class FieldMetadata: - """ - Metadata class used to annotate fields to provide additional information. - - Example: - class MyDict(TypedDict): - field: typing.Annotated[str, FieldMetadata(alias="field_name")] - - Will serialize: `{"field": "value"}` - To: `{"field_name": "value"}` - """ - - alias: str - - def __init__(self, *, alias: str) -> None: - self.alias = alias - - -def convert_and_respect_annotation_metadata( - *, - object_: typing.Any, - annotation: typing.Any, - inner_type: typing.Optional[typing.Any] = None, - direction: typing.Literal["read", "write"], -) -> typing.Any: - """ - Respect the metadata annotations on a field, such as aliasing. This function effectively - manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for - TypedDicts, which cannot support aliasing out of the box, and can be extended for additional - utilities, such as defaults. - - Parameters - ---------- - object_ : typing.Any - - annotation : type - The type we're looking to apply typing annotations from - - inner_type : typing.Optional[type] - - Returns - ------- - typing.Any - """ - - if object_ is None: - return None - if inner_type is None: - inner_type = annotation - - clean_type = _remove_annotations(inner_type) - # Pydantic models - if ( - inspect.isclass(clean_type) - and issubclass(clean_type, pydantic.BaseModel) - and isinstance(object_, typing.Mapping) - ): - return _convert_mapping(object_, clean_type, direction) - # TypedDicts - if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): - return _convert_mapping(object_, clean_type, direction) - - if ( - typing_extensions.get_origin(clean_type) == typing.Dict - or typing_extensions.get_origin(clean_type) == dict - or clean_type == typing.Dict - ) and isinstance(object_, typing.Dict): - key_type = typing_extensions.get_args(clean_type)[0] - value_type = typing_extensions.get_args(clean_type)[1] - - return { - key: convert_and_respect_annotation_metadata( - object_=value, - annotation=annotation, - inner_type=value_type, - direction=direction, - ) - for key, value in object_.items() - } - - # If you're iterating on a string, do not bother to coerce it to a sequence. - if not isinstance(object_, str): - if ( - typing_extensions.get_origin(clean_type) == typing.Set - or typing_extensions.get_origin(clean_type) == set - or clean_type == typing.Set - ) and isinstance(object_, typing.Set): - inner_type = typing_extensions.get_args(clean_type)[0] - return { - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - } - elif ( - ( - typing_extensions.get_origin(clean_type) == typing.List - or typing_extensions.get_origin(clean_type) == list - or clean_type == typing.List - ) - and isinstance(object_, typing.List) - ) or ( - ( - typing_extensions.get_origin(clean_type) == typing.Sequence - or typing_extensions.get_origin(clean_type) == collections.abc.Sequence - or clean_type == typing.Sequence - ) - and isinstance(object_, typing.Sequence) - ): - inner_type = typing_extensions.get_args(clean_type)[0] - return [ - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - ] - - if typing_extensions.get_origin(clean_type) == typing.Union: - # We should be able to ~relatively~ safely try to convert keys against all - # member types in the union, the edge case here is if one member aliases a field - # of the same name to a different name from another member - # Or if another member aliases a field of the same name that another member does not. - for member in typing_extensions.get_args(clean_type): - object_ = convert_and_respect_annotation_metadata( - object_=object_, - annotation=annotation, - inner_type=member, - direction=direction, - ) - return object_ - - annotated_type = _get_annotation(annotation) - if annotated_type is None: - return object_ - - # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) - # Then we can safely call it on the recursive conversion. - return object_ - - -def _convert_mapping( - object_: typing.Mapping[str, object], - expected_type: typing.Any, - direction: typing.Literal["read", "write"], -) -> typing.Mapping[str, object]: - converted_object: typing.Dict[str, object] = {} - try: - annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) - except NameError: - # The TypedDict contains a circular reference, so - # we use the __annotations__ attribute directly. - annotations = getattr(expected_type, "__annotations__", {}) - aliases_to_field_names = _get_alias_to_field_name(annotations) - for key, value in object_.items(): - if direction == "read" and key in aliases_to_field_names: - dealiased_key = aliases_to_field_names.get(key) - if dealiased_key is not None: - type_ = annotations.get(dealiased_key) - else: - type_ = annotations.get(key) - # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map - # - # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias - # then we can just pass the value through as is - if type_ is None: - converted_object[key] = value - elif direction == "read" and key not in aliases_to_field_names: - converted_object[key] = convert_and_respect_annotation_metadata( - object_=value, annotation=type_, direction=direction - ) - else: - converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( - convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) - ) - return converted_object - - -def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return None - - if maybe_annotated_type == typing_extensions.NotRequired: - type_ = typing_extensions.get_args(type_)[0] - maybe_annotated_type = typing_extensions.get_origin(type_) - - if maybe_annotated_type == typing_extensions.Annotated: - return type_ - - return None - - -def _remove_annotations(type_: typing.Any) -> typing.Any: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return type_ - - if maybe_annotated_type == typing_extensions.NotRequired: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - if maybe_annotated_type == typing_extensions.Annotated: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - return type_ - - -def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_alias_to_field_name(annotations) - - -def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_field_to_alias_name(annotations) - - -def _get_alias_to_field_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[maybe_alias] = field - return aliases - - -def _get_field_to_alias_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[field] = maybe_alias - return aliases - - -def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: - maybe_annotated_type = _get_annotation(type_) - - if maybe_annotated_type is not None: - # The actual annotations are 1 onward, the first is the annotated type - annotations = typing_extensions.get_args(maybe_annotated_type)[1:] - - for annotation in annotations: - if isinstance(annotation, FieldMetadata) and annotation.alias is not None: - return annotation.alias - return None - - -def _alias_key( - key: str, - type_: typing.Any, - direction: typing.Literal["read", "write"], - aliases_to_field_names: typing.Dict[str, str], -) -> str: - if direction == "read": - return aliases_to_field_names.get(key, key) - return _get_alias_from_type(type_=type_) or key diff --git a/seed/pydantic/allof-inline/src/seed/api/describable.py b/seed/pydantic/allof-inline/src/seed/api/describable.py deleted file mode 100644 index b22612d75808..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/describable.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Describable(UniversalBaseModel): - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Describable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/detailed_org.py b/seed/pydantic/allof-inline/src/seed/api/detailed_org.py deleted file mode 100644 index 0779d792bb37..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/detailed_org.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .detailed_org_metadata import DetailedOrgMetadata - - -class DetailedOrg(UniversalBaseModel): - metadata: typing.Optional[DetailedOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py b/seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py deleted file mode 100644 index fe3f63b969a5..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/detailed_org_metadata.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class DetailedOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from DetailedOrg. - """ - - domain: typing.Optional[str] = pydantic.Field(default=None) - """ - Custom domain name. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/identifiable.py b/seed/pydantic/allof-inline/src/seed/api/identifiable.py deleted file mode 100644 index 825da5f0d495..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/identifiable.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Identifiable(UniversalBaseModel): - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Identifiable. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/organization.py b/seed/pydantic/allof-inline/src/seed/api/organization.py deleted file mode 100644 index a446ab4bb313..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/organization.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .organization_metadata import OrganizationMetadata - - -class Organization(UniversalBaseModel): - id: str - metadata: typing.Optional[OrganizationMetadata] = None - name: str - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/organization_metadata.py b/seed/pydantic/allof-inline/src/seed/api/organization_metadata.py deleted file mode 100644 index 77f35c177d8b..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/organization_metadata.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class OrganizationMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from DetailedOrg. - """ - - domain: typing.Optional[str] = pydantic.Field(default=None) - """ - Custom domain name. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/paginated_result.py b/seed/pydantic/allof-inline/src/seed/api/paginated_result.py deleted file mode 100644 index 089091066549..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/paginated_result.py +++ /dev/null @@ -1,22 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors - - -class PaginatedResult(UniversalBaseModel): - paging: PagingCursors - results: typing.List[typing.Any] = pydantic.Field() - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/paging_cursors.py b/seed/pydantic/allof-inline/src/seed/api/paging_cursors.py deleted file mode 100644 index 9132a3ed2eaf..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/paging_cursors.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class PagingCursors(UniversalBaseModel): - next: str = pydantic.Field() - """ - Cursor for the next page of results. - """ - - previous: typing.Optional[str] = pydantic.Field(default=None) - """ - Cursor for the previous page of results. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/py.typed b/seed/pydantic/allof-inline/src/seed/api/py.typed deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py b/seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py deleted file mode 100644 index 1d22ed9cabf8..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/rule_execution_context.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_response.py b/seed/pydantic/allof-inline/src/seed/api/rule_response.py deleted file mode 100644 index f408b0ed40eb..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/rule_response.py +++ /dev/null @@ -1,49 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -import pydantic -import typing_extensions -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .core.serialization import FieldMetadata -from .rule_execution_context import RuleExecutionContext -from .rule_response_status import RuleResponseStatus - - -class RuleResponse(UniversalBaseModel): - created_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="createdBy"), - pydantic.Field(alias="createdBy", description="The user who created this resource."), - ] = None - created_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="createdDateTime"), - pydantic.Field(alias="createdDateTime", description="When this resource was created."), - ] = None - modified_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="modifiedBy"), - pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), - ] = None - modified_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="modifiedDateTime"), - pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), - ] = None - id: str - name: str - status: RuleResponseStatus - execution_context: typing_extensions.Annotated[ - typing.Optional[RuleExecutionContext], - FieldMetadata(alias="executionContext"), - pydantic.Field(alias="executionContext"), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_response_status.py b/seed/pydantic/allof-inline/src/seed/api/rule_response_status.py deleted file mode 100644 index 4cbd106638cb..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/rule_response_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_type.py b/seed/pydantic/allof-inline/src/seed/api/rule_type.py deleted file mode 100644 index 7f5646c5bc8a..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/rule_type.py +++ /dev/null @@ -1,19 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class RuleType(UniversalBaseModel): - id: str - name: str - description: typing.Optional[str] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py b/seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py deleted file mode 100644 index 3d4641d39dff..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/rule_type_search_response.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .rule_type import RuleType - - -class RuleTypeSearchResponse(UniversalBaseModel): - paging: PagingCursors - results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/user.py b/seed/pydantic/allof-inline/src/seed/api/user.py deleted file mode 100644 index b737a327a2ce..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/user.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class User(UniversalBaseModel): - id: str - email: str - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/src/seed/api/user_search_response.py b/seed/pydantic/allof-inline/src/seed/api/user_search_response.py deleted file mode 100644 index d71aa4274c66..000000000000 --- a/seed/pydantic/allof-inline/src/seed/api/user_search_response.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .user import User - - -class UserSearchResponse(UniversalBaseModel): - paging: PagingCursors - results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof-inline/tests/custom/test_client.py b/seed/pydantic/allof-inline/tests/custom/test_client.py deleted file mode 100644 index ab04ce6393ef..000000000000 --- a/seed/pydantic/allof-inline/tests/custom/test_client.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - - -# Get started with writing tests with pytest at https://docs.pytest.org -@pytest.mark.skip(reason="Unimplemented") -def test_client() -> None: - assert True diff --git a/seed/pydantic/allof/.github/workflows/ci.yml b/seed/pydantic/allof/.github/workflows/ci.yml deleted file mode 100644 index fd1df043d08d..000000000000 --- a/seed/pydantic/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: ci -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Compile - run: poetry run mypy . - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - - name: Test - run: poetry run pytest -rP -n auto . - - - name: Install aiohttp extra - run: poetry install --extras aiohttp - - - name: Test (aiohttp) - run: poetry run pytest -rP -n auto -m aiohttp . - - publish: - needs: [compile, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Publish to pypi - run: | - poetry config repositories.remote - poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" - env: - PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} - PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/pydantic/allof/.gitignore b/seed/pydantic/allof/.gitignore deleted file mode 100644 index d2e4ca808d21..000000000000 --- a/seed/pydantic/allof/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.mypy_cache/ -.ruff_cache/ -__pycache__/ -dist/ -poetry.toml diff --git a/seed/pydantic/allof/README.md b/seed/pydantic/allof/README.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/pydantic/allof/poetry.lock b/seed/pydantic/allof/poetry.lock deleted file mode 100644 index e1efb51734e4..000000000000 --- a/seed/pydantic/allof/poetry.lock +++ /dev/null @@ -1,546 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." -optional = false -python-versions = "<3.11,>=3.8" -files = [ - {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, - {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, - {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.2" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, - {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "mypy" -version = "1.13.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "packaging" -version = "26.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "pydantic" -version = "2.12.5" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, - {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.41.5" -typing-extensions = ">=4.14.1" -typing-inspection = ">=0.4.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, - {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, -] - -[package.dependencies] -typing-extensions = ">=4.14.1" - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.4.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.10" -files = [ - {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, - {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, -] - -[package.dependencies] -backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} -pytest = ">=8.2,<10" -typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-xdist" -version = "3.8.0" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, - {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "ruff" -version = "0.11.5" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, - {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, - {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, - {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, - {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, - {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, - {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "tomli" -version = "2.4.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, - {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, - {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, - {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, - {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, - {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, - {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, - {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, - {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, - {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, - {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, - {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, - {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, - {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, - {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, - {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, - {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, - {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, - {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, -] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20260408" -description = "Typing stubs for python-dateutil" -optional = false -python-versions = ">=3.10" -files = [ - {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, - {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "283a69ea9a1eb309027a93a4e056bedb5cb3c6018ca2eec381af57784ad1eb58" diff --git a/seed/pydantic/allof/pyproject.toml b/seed/pydantic/allof/pyproject.toml deleted file mode 100644 index fe46224af192..000000000000 --- a/seed/pydantic/allof/pyproject.toml +++ /dev/null @@ -1,93 +0,0 @@ -[project] -name = "fern_allof" -dynamic = ["version"] - -[tool.poetry] -name = "fern_allof" -version = "0.0.1" -description = "" -readme = "README.md" -authors = [] -keywords = [ - "fern", - "test" -] - -classifiers = [ - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Programming Language :: Python :: 3.15", - "Operating System :: OS Independent", - "Operating System :: POSIX", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Topic :: Software Development :: Libraries :: Python Modules", - "Typing :: Typed" -] -packages = [ - { include = "seed/api", from = "src"} -] - -[tool.poetry.urls] -Documentation = 'https://buildwithfern.com/learn' -Homepage = 'https://buildwithfern.com/' -Repository = 'https://github.com/allof/fern' - -[tool.poetry.dependencies] -python = "^3.10" -pydantic = ">= 1.9.2" -pydantic-core = ">=2.18.2,<2.44.0" - -[tool.poetry.group.dev.dependencies] -mypy = "==1.13.0" -pytest = "^8.2.0" -pytest-asyncio = "^1.0.0" -pytest-xdist = "^3.6.1" -python-dateutil = "^2.9.0" -types-python-dateutil = "^2.9.0.20240316" -ruff = "==0.11.5" - -[tool.pytest.ini_options] -testpaths = [ "tests" ] -asyncio_mode = "auto" -markers = [ - "aiohttp: tests that require httpx_aiohttp to be installed", -] - -[tool.mypy] -plugins = ["pydantic.mypy"] - -[tool.ruff] -line-length = 120 - -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "F", # pyflakes - "I", # isort -] -ignore = [ - "E402", # Module level import not at top of file - "E501", # Line too long - "E711", # Comparison to `None` should be `cond is not None` - "E712", # Avoid equality comparisons to `True`; use `if ...:` checks - "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks - "E722", # Do not use bare `except` - "E731", # Do not assign a `lambda` expression, use a `def` - "F821", # Undefined name - "F841" # Local variable ... is assigned to but never used -] - -[tool.ruff.lint.isort] -section-order = ["future", "standard-library", "third-party", "first-party"] - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/seed/pydantic/allof/requirements.txt b/seed/pydantic/allof/requirements.txt deleted file mode 100644 index e090fd9ba093..000000000000 --- a/seed/pydantic/allof/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pydantic>= 1.9.2 -pydantic-core>=2.18.2,<2.44.0 diff --git a/seed/pydantic/allof/snippet.json b/seed/pydantic/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/pydantic/allof/src/seed/api/__init__.py b/seed/pydantic/allof/src/seed/api/__init__.py deleted file mode 100644 index f6ca40254bd4..000000000000 --- a/seed/pydantic/allof/src/seed/api/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -from .audit_info import AuditInfo -from .base_org import BaseOrg -from .base_org_metadata import BaseOrgMetadata -from .combined_entity import CombinedEntity -from .combined_entity_status import CombinedEntityStatus -from .describable import Describable -from .detailed_org import DetailedOrg -from .detailed_org_metadata import DetailedOrgMetadata -from .identifiable import Identifiable -from .organization import Organization -from .paginated_result import PaginatedResult -from .paging_cursors import PagingCursors -from .rule_execution_context import RuleExecutionContext -from .rule_response import RuleResponse -from .rule_response_status import RuleResponseStatus -from .rule_type import RuleType -from .rule_type_search_response import RuleTypeSearchResponse -from .user import User -from .user_search_response import UserSearchResponse - -__all__ = [ - "AuditInfo", - "BaseOrg", - "BaseOrgMetadata", - "CombinedEntity", - "CombinedEntityStatus", - "Describable", - "DetailedOrg", - "DetailedOrgMetadata", - "Identifiable", - "Organization", - "PaginatedResult", - "PagingCursors", - "RuleExecutionContext", - "RuleResponse", - "RuleResponseStatus", - "RuleType", - "RuleTypeSearchResponse", - "User", - "UserSearchResponse", -] diff --git a/seed/pydantic/allof/src/seed/api/audit_info.py b/seed/pydantic/allof/src/seed/api/audit_info.py deleted file mode 100644 index 5b00ec223d32..000000000000 --- a/seed/pydantic/allof/src/seed/api/audit_info.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -import pydantic -import typing_extensions -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .core.serialization import FieldMetadata - - -class AuditInfo(UniversalBaseModel): - """ - Common audit metadata. - """ - - created_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="createdBy"), - pydantic.Field(alias="createdBy", description="The user who created this resource."), - ] = None - created_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="createdDateTime"), - pydantic.Field(alias="createdDateTime", description="When this resource was created."), - ] = None - modified_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="modifiedBy"), - pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), - ] = None - modified_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="modifiedDateTime"), - pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/base_org.py b/seed/pydantic/allof/src/seed/api/base_org.py deleted file mode 100644 index 512f3ed40d4e..000000000000 --- a/seed/pydantic/allof/src/seed/api/base_org.py +++ /dev/null @@ -1,19 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .base_org_metadata import BaseOrgMetadata -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class BaseOrg(UniversalBaseModel): - id: str - metadata: typing.Optional[BaseOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/base_org_metadata.py b/seed/pydantic/allof/src/seed/api/base_org_metadata.py deleted file mode 100644 index 8c58e204c200..000000000000 --- a/seed/pydantic/allof/src/seed/api/base_org_metadata.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class BaseOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from BaseOrg. - """ - - tier: typing.Optional[str] = pydantic.Field(default=None) - """ - Subscription tier. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/combined_entity.py b/seed/pydantic/allof/src/seed/api/combined_entity.py deleted file mode 100644 index c158e3278971..000000000000 --- a/seed/pydantic/allof/src/seed/api/combined_entity.py +++ /dev/null @@ -1,32 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .combined_entity_status import CombinedEntityStatus -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class CombinedEntity(UniversalBaseModel): - status: CombinedEntityStatus - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Identifiable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/combined_entity_status.py b/seed/pydantic/allof/src/seed/api/combined_entity_status.py deleted file mode 100644 index 42ac60430cd7..000000000000 --- a/seed/pydantic/allof/src/seed/api/combined_entity_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/pydantic/allof/src/seed/api/core/__init__.py b/seed/pydantic/allof/src/seed/api/core/__init__.py deleted file mode 100644 index f9bdc1729e86..000000000000 --- a/seed/pydantic/allof/src/seed/api/core/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime -from .pydantic_utilities import ( - IS_PYDANTIC_V2, - UniversalBaseModel, - UniversalRootModel, - parse_obj_as, - universal_field_validator, - universal_root_validator, - update_forward_refs, -) -from .serialization import FieldMetadata - -__all__ = [ - "FieldMetadata", - "IS_PYDANTIC_V2", - "Rfc2822DateTime", - "UniversalBaseModel", - "UniversalRootModel", - "parse_obj_as", - "parse_rfc2822_datetime", - "serialize_datetime", - "universal_field_validator", - "universal_root_validator", - "update_forward_refs", -] diff --git a/seed/pydantic/allof/src/seed/api/core/datetime_utils.py b/seed/pydantic/allof/src/seed/api/core/datetime_utils.py deleted file mode 100644 index a12b2ad03c53..000000000000 --- a/seed/pydantic/allof/src/seed/api/core/datetime_utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -from email.utils import parsedate_to_datetime -from typing import Any - -import pydantic - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - - -def parse_rfc2822_datetime(v: Any) -> dt.datetime: - """ - Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") - into a datetime object. If the value is already a datetime, return it as-is. - Falls back to ISO 8601 parsing if RFC 2822 parsing fails. - """ - if isinstance(v, dt.datetime): - return v - if isinstance(v, str): - try: - return parsedate_to_datetime(v) - except Exception: - pass - # Fallback to ISO 8601 parsing - return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) - raise ValueError(f"Expected str or datetime, got {type(v)}") - - -class Rfc2822DateTime(dt.datetime): - """A datetime subclass that parses RFC 2822 date strings. - - On Pydantic V1, uses __get_validators__ for pre-validation. - On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. - """ - - @classmethod - def __get_validators__(cls): # type: ignore[no-untyped-def] - yield parse_rfc2822_datetime - - @classmethod - def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] - from pydantic_core import core_schema - - return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) - - -def serialize_datetime(v: dt.datetime) -> str: - """ - Serialize a datetime including timezone info. - - Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. - - UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. - """ - - def _serialize_zoned_datetime(v: dt.datetime) -> str: - if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): - # UTC is a special case where we use "Z" at the end instead of "+00:00" - return v.isoformat().replace("+00:00", "Z") - else: - # Delegate to the typical +/- offset format - return v.isoformat() - - if v.tzinfo is not None: - return _serialize_zoned_datetime(v) - else: - local_tz = dt.datetime.now().astimezone().tzinfo - localized_dt = v.replace(tzinfo=local_tz) - return _serialize_zoned_datetime(localized_dt) diff --git a/seed/pydantic/allof/src/seed/api/core/enum.py b/seed/pydantic/allof/src/seed/api/core/enum.py deleted file mode 100644 index a3d17a67b128..000000000000 --- a/seed/pydantic/allof/src/seed/api/core/enum.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -""" -Provides a StrEnum base class that works across Python versions. - -For Python >= 3.11, this re-exports the standard library enum.StrEnum. -For older Python versions, this defines a compatible StrEnum using the -(str, Enum) mixin pattern so that generated SDKs can use a single base -class in all supported Python versions. -""" - -import enum -import sys - -if sys.version_info >= (3, 11): - from enum import StrEnum -else: - - class StrEnum(str, enum.Enum): - pass diff --git a/seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py b/seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py deleted file mode 100644 index ed95da8bf8ce..000000000000 --- a/seed/pydantic/allof/src/seed/api/core/pydantic_utilities.py +++ /dev/null @@ -1,515 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# nopycln: file -import datetime as dt -import inspect -import json -import logging -from collections import defaultdict -from dataclasses import asdict -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - List, - Mapping, - Optional, - Tuple, - Type, - TypeVar, - Union, - cast, -) - -import pydantic -import typing_extensions -from pydantic.fields import FieldInfo as _FieldInfo - -_logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - from .http_sse._models import ServerSentEvent - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - -if IS_PYDANTIC_V2: - _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] - _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] - - def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value - return _datetime_adapter.validate_python(value) - - def parse_date(value: Any) -> dt.date: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value.date() - if isinstance(value, dt.date): - return value - return _date_adapter.validate_python(value) - - # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. - from typing import get_args as get_args # type: ignore[assignment] - from typing import get_origin as get_origin # type: ignore[assignment] - - def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return typing_extensions.get_origin(tp) is typing_extensions.Literal - - def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] - - # Inline encoders_by_type to avoid importing from pydantic.v1.json - import re as _re - from collections import deque as _deque - from decimal import Decimal as _Decimal - from enum import Enum as _Enum - from ipaddress import ( - IPv4Address as _IPv4Address, - ) - from ipaddress import ( - IPv4Interface as _IPv4Interface, - ) - from ipaddress import ( - IPv4Network as _IPv4Network, - ) - from ipaddress import ( - IPv6Address as _IPv6Address, - ) - from ipaddress import ( - IPv6Interface as _IPv6Interface, - ) - from ipaddress import ( - IPv6Network as _IPv6Network, - ) - from pathlib import Path as _Path - from types import GeneratorType as _GeneratorType - from uuid import UUID as _UUID - - from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] - - def _decimal_encoder(dec_value: Any) -> Any: - if dec_value.as_tuple().exponent >= 0: - return int(dec_value) - return float(dec_value) - - encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] - bytes: lambda o: o.decode(), - dt.date: lambda o: o.isoformat(), - dt.datetime: lambda o: o.isoformat(), - dt.time: lambda o: o.isoformat(), - dt.timedelta: lambda td: td.total_seconds(), - _Decimal: _decimal_encoder, - _Enum: lambda o: o.value, - frozenset: list, - _deque: list, - _GeneratorType: list, - _IPv4Address: str, - _IPv4Interface: str, - _IPv4Network: str, - _IPv6Address: str, - _IPv6Interface: str, - _IPv6Network: str, - _Path: str, - _re.Pattern: lambda o: o.pattern, - set: list, - _UUID: str, - } -else: - from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] - from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] - from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] - from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] - from pydantic.typing import get_args as get_args # type: ignore[no-redef] - from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] - from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] - from pydantic.typing import is_union as is_union # type: ignore[no-redef] - -from .datetime_utils import serialize_datetime -from typing_extensions import TypeAlias - -T = TypeVar("T") -Model = TypeVar("Model", bound=pydantic.BaseModel) - - -def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: - """ - Extract the discriminator field name and union variants from a discriminated union type. - Supports Annotated[Union[...], Field(discriminator=...)] patterns. - Returns (discriminator, variants) or (None, None) if not a discriminated union. - """ - origin = typing_extensions.get_origin(type_) - - if origin is typing_extensions.Annotated: - args = typing_extensions.get_args(type_) - if len(args) >= 2: - inner_type = args[0] - # Check annotations for discriminator - discriminator = None - for annotation in args[1:]: - if hasattr(annotation, "discriminator"): - discriminator = getattr(annotation, "discriminator", None) - break - - if discriminator: - inner_origin = typing_extensions.get_origin(inner_type) - if inner_origin is Union: - variants = list(typing_extensions.get_args(inner_type)) - return discriminator, variants - return None, None - - -def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: - """Get the type annotation of a field from a Pydantic model.""" - if IS_PYDANTIC_V2: - fields = getattr(model, "model_fields", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.annotation) - else: - fields = getattr(model, "__fields__", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.outer_type_) - return None - - -def _find_variant_by_discriminator( - variants: List[Type[Any]], - discriminator: str, - discriminator_value: Any, -) -> Optional[Type[Any]]: - """Find the union variant that matches the discriminator value.""" - for variant in variants: - if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): - continue - - disc_annotation = _get_field_annotation(variant, discriminator) - if disc_annotation and is_literal_type(disc_annotation): - literal_args = get_args(disc_annotation) - if literal_args and literal_args[0] == discriminator_value: - return variant - return None - - -def _is_string_type(type_: Type[Any]) -> bool: - """Check if a type is str or Optional[str].""" - if type_ is str: - return True - - origin = typing_extensions.get_origin(type_) - if origin is Union: - args = typing_extensions.get_args(type_) - # Optional[str] = Union[str, None] - non_none_args = [a for a in args if a is not type(None)] - if len(non_none_args) == 1 and non_none_args[0] is str: - return True - - return False - - -def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: - """ - Parse a ServerSentEvent into the appropriate type. - - Handles two scenarios based on where the discriminator field is located: - - 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. - The union describes the data content, not the SSE envelope. - -> Returns: json.loads(data) parsed into the type - - 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. - The union describes the full SSE event structure. - -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string - - Args: - sse: The ServerSentEvent object to parse - type_: The target discriminated union type - - Returns: - The parsed object of type T - - Note: - This function is only available in SDK contexts where http_sse module exists. - """ - sse_event = asdict(sse) - discriminator, variants = _get_discriminator_and_variants(type_) - - if discriminator is None or variants is None: - # Not a discriminated union - parse the data field as JSON - data_value = sse_event.get("data") - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - data_value = sse_event.get("data") - - # Check if discriminator is at the top level (event-level discrimination) - if discriminator in sse_event: - # Case 2: Event-level discrimination - # Find the matching variant to check if 'data' field needs JSON parsing - disc_value = sse_event.get(discriminator) - matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) - - if matching_variant is not None: - # Check what type the variant expects for 'data' - data_type = _get_field_annotation(matching_variant, "data") - if data_type is not None and not _is_string_type(data_type): - # Variant expects non-string data - parse JSON - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - new_object = dict(sse_event) - new_object["data"] = parsed_data - return parse_obj_as(type_, new_object) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - # Either no matching variant, data is string type, or JSON parse failed - return parse_obj_as(type_, sse_event) - - else: - # Case 1: Data-level discrimination - # The discriminator is inside the data payload - extract and parse data only - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - -def parse_obj_as(type_: Type[T], object_: Any) -> T: - if IS_PYDANTIC_V2: - adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] - return adapter.validate_python(object_) - return pydantic.parse_obj_as(type_, object_) - - -def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: - if IS_PYDANTIC_V2: - from pydantic_core import to_jsonable_python - - return to_jsonable_python(obj, fallback=fallback_serializer) - return fallback_serializer(obj) - - -class UniversalBaseModel(pydantic.BaseModel): - if IS_PYDANTIC_V2: - model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] - validate_by_name=True, - validate_by_alias=True, - # Allow fields beginning with `model_` to be used in the model - protected_namespaces=(), - ) - - @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] - def serialize_model(self) -> Any: # type: ignore[name-defined] - def _serialize_recursive(obj: Any) -> Any: - if isinstance(obj, dt.datetime): - return serialize_datetime(obj) - elif isinstance(obj, dict): - return {k: _serialize_recursive(v) for k, v in obj.items()} - elif isinstance(obj, list): - return [_serialize_recursive(item) for item in obj] - return obj - - serialized = self.model_dump() # type: ignore[attr-defined] - return _serialize_recursive(serialized) - - else: - - class Config: - populate_by_name = True - smart_union = True - allow_population_by_field_name = True - json_encoders = {dt.datetime: serialize_datetime} - # Allow fields beginning with `model_` to be used in the model - protected_namespaces = () - - def json(self, **kwargs: Any) -> str: - kwargs_with_defaults = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - if IS_PYDANTIC_V2: - return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: Any) -> Dict[str, Any]: - """ - Override the default dict method to `exclude_unset` by default. This function patches - `exclude_unset` to work include fields within non-None default values. - """ - # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 - # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. - # - # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models - # that we have less control over, and this is less intrusive than custom serializers for now. - if IS_PYDANTIC_V2: - kwargs_with_defaults_exclude_unset = { - **kwargs, - "by_alias": True, - "exclude_unset": True, - "exclude_none": False, - } - kwargs_with_defaults_exclude_none = { - **kwargs, - "by_alias": True, - "exclude_none": True, - "exclude_unset": False, - } - return deep_union_pydantic_dicts( - super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] - super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] - ) - - _fields_set = self.__fields_set__.copy() - - fields = _get_model_fields(self.__class__) - for name, field in fields.items(): - if name not in _fields_set: - default = _get_field_default(field) - - # If the default values are non-null act like they've been set - # This effectively allows exclude_unset to work like exclude_none where - # the latter passes through intentionally set none values. - if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): - _fields_set.add(name) - - if default is not None: - self.__fields_set__.add(name) - - kwargs_with_defaults_exclude_unset_include_fields = { - "by_alias": True, - "exclude_unset": True, - "include": _fields_set, - **kwargs, - } - - return super().dict(**kwargs_with_defaults_exclude_unset_include_fields) - - -def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: - converted_list: List[Any] = [] - for i, item in enumerate(source): - destination_value = destination[i] - if isinstance(item, dict): - converted_list.append(deep_union_pydantic_dicts(item, destination_value)) - elif isinstance(item, list): - converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) - else: - converted_list.append(item) - return converted_list - - -def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: - for key, value in source.items(): - node = destination.setdefault(key, {}) - if isinstance(value, dict): - deep_union_pydantic_dicts(value, node) - # Note: we do not do this same processing for sets given we do not have sets of models - # and given the sets are unordered, the processing of the set and matching objects would - # be non-trivial. - elif isinstance(value, list): - destination[key] = _union_list_of_pydantic_dicts(value, node) - else: - destination[key] = value - - return destination - - -if IS_PYDANTIC_V2: - - class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[name-defined, type-arg] - pass - - UniversalRootModel: TypeAlias = V2RootModel -else: - UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] - - -def encode_by_type(o: Any) -> Any: - encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) - for type_, encoder in encoders_by_type.items(): - encoders_by_class_tuples[encoder] += (type_,) - - if type(o) in encoders_by_type: - return encoders_by_type[type(o)](o) - for encoder, classes_tuple in encoders_by_class_tuples.items(): - if isinstance(o, classes_tuple): - return encoder(o) - - -def update_forward_refs(model: Type["Model"], **localns: Any) -> None: - if IS_PYDANTIC_V2: - model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] - else: - model.update_forward_refs(**localns) - - -# Mirrors Pydantic's internal typing -AnyCallable = Callable[..., Any] - - -def universal_root_validator( - pre: bool = False, -) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - # In Pydantic v2, for RootModel we always use "before" mode - # The custom validators transform the input value before the model is created - return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] - - return decorator - - -def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) - - return decorator - - -PydanticField = Union[ModelField, _FieldInfo] - - -def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: - if IS_PYDANTIC_V2: - return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] - return cast(Mapping[str, PydanticField], model.__fields__) - - -def _get_field_default(field: PydanticField) -> Any: - try: - value = field.get_default() # type: ignore[union-attr] - except: - value = field.default - if IS_PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None - return value - return value diff --git a/seed/pydantic/allof/src/seed/api/core/serialization.py b/seed/pydantic/allof/src/seed/api/core/serialization.py deleted file mode 100644 index c36e865cc729..000000000000 --- a/seed/pydantic/allof/src/seed/api/core/serialization.py +++ /dev/null @@ -1,276 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import collections -import inspect -import typing - -import pydantic -import typing_extensions - - -class FieldMetadata: - """ - Metadata class used to annotate fields to provide additional information. - - Example: - class MyDict(TypedDict): - field: typing.Annotated[str, FieldMetadata(alias="field_name")] - - Will serialize: `{"field": "value"}` - To: `{"field_name": "value"}` - """ - - alias: str - - def __init__(self, *, alias: str) -> None: - self.alias = alias - - -def convert_and_respect_annotation_metadata( - *, - object_: typing.Any, - annotation: typing.Any, - inner_type: typing.Optional[typing.Any] = None, - direction: typing.Literal["read", "write"], -) -> typing.Any: - """ - Respect the metadata annotations on a field, such as aliasing. This function effectively - manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for - TypedDicts, which cannot support aliasing out of the box, and can be extended for additional - utilities, such as defaults. - - Parameters - ---------- - object_ : typing.Any - - annotation : type - The type we're looking to apply typing annotations from - - inner_type : typing.Optional[type] - - Returns - ------- - typing.Any - """ - - if object_ is None: - return None - if inner_type is None: - inner_type = annotation - - clean_type = _remove_annotations(inner_type) - # Pydantic models - if ( - inspect.isclass(clean_type) - and issubclass(clean_type, pydantic.BaseModel) - and isinstance(object_, typing.Mapping) - ): - return _convert_mapping(object_, clean_type, direction) - # TypedDicts - if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): - return _convert_mapping(object_, clean_type, direction) - - if ( - typing_extensions.get_origin(clean_type) == typing.Dict - or typing_extensions.get_origin(clean_type) == dict - or clean_type == typing.Dict - ) and isinstance(object_, typing.Dict): - key_type = typing_extensions.get_args(clean_type)[0] - value_type = typing_extensions.get_args(clean_type)[1] - - return { - key: convert_and_respect_annotation_metadata( - object_=value, - annotation=annotation, - inner_type=value_type, - direction=direction, - ) - for key, value in object_.items() - } - - # If you're iterating on a string, do not bother to coerce it to a sequence. - if not isinstance(object_, str): - if ( - typing_extensions.get_origin(clean_type) == typing.Set - or typing_extensions.get_origin(clean_type) == set - or clean_type == typing.Set - ) and isinstance(object_, typing.Set): - inner_type = typing_extensions.get_args(clean_type)[0] - return { - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - } - elif ( - ( - typing_extensions.get_origin(clean_type) == typing.List - or typing_extensions.get_origin(clean_type) == list - or clean_type == typing.List - ) - and isinstance(object_, typing.List) - ) or ( - ( - typing_extensions.get_origin(clean_type) == typing.Sequence - or typing_extensions.get_origin(clean_type) == collections.abc.Sequence - or clean_type == typing.Sequence - ) - and isinstance(object_, typing.Sequence) - ): - inner_type = typing_extensions.get_args(clean_type)[0] - return [ - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - ] - - if typing_extensions.get_origin(clean_type) == typing.Union: - # We should be able to ~relatively~ safely try to convert keys against all - # member types in the union, the edge case here is if one member aliases a field - # of the same name to a different name from another member - # Or if another member aliases a field of the same name that another member does not. - for member in typing_extensions.get_args(clean_type): - object_ = convert_and_respect_annotation_metadata( - object_=object_, - annotation=annotation, - inner_type=member, - direction=direction, - ) - return object_ - - annotated_type = _get_annotation(annotation) - if annotated_type is None: - return object_ - - # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) - # Then we can safely call it on the recursive conversion. - return object_ - - -def _convert_mapping( - object_: typing.Mapping[str, object], - expected_type: typing.Any, - direction: typing.Literal["read", "write"], -) -> typing.Mapping[str, object]: - converted_object: typing.Dict[str, object] = {} - try: - annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) - except NameError: - # The TypedDict contains a circular reference, so - # we use the __annotations__ attribute directly. - annotations = getattr(expected_type, "__annotations__", {}) - aliases_to_field_names = _get_alias_to_field_name(annotations) - for key, value in object_.items(): - if direction == "read" and key in aliases_to_field_names: - dealiased_key = aliases_to_field_names.get(key) - if dealiased_key is not None: - type_ = annotations.get(dealiased_key) - else: - type_ = annotations.get(key) - # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map - # - # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias - # then we can just pass the value through as is - if type_ is None: - converted_object[key] = value - elif direction == "read" and key not in aliases_to_field_names: - converted_object[key] = convert_and_respect_annotation_metadata( - object_=value, annotation=type_, direction=direction - ) - else: - converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( - convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) - ) - return converted_object - - -def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return None - - if maybe_annotated_type == typing_extensions.NotRequired: - type_ = typing_extensions.get_args(type_)[0] - maybe_annotated_type = typing_extensions.get_origin(type_) - - if maybe_annotated_type == typing_extensions.Annotated: - return type_ - - return None - - -def _remove_annotations(type_: typing.Any) -> typing.Any: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return type_ - - if maybe_annotated_type == typing_extensions.NotRequired: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - if maybe_annotated_type == typing_extensions.Annotated: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - return type_ - - -def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_alias_to_field_name(annotations) - - -def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_field_to_alias_name(annotations) - - -def _get_alias_to_field_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[maybe_alias] = field - return aliases - - -def _get_field_to_alias_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[field] = maybe_alias - return aliases - - -def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: - maybe_annotated_type = _get_annotation(type_) - - if maybe_annotated_type is not None: - # The actual annotations are 1 onward, the first is the annotated type - annotations = typing_extensions.get_args(maybe_annotated_type)[1:] - - for annotation in annotations: - if isinstance(annotation, FieldMetadata) and annotation.alias is not None: - return annotation.alias - return None - - -def _alias_key( - key: str, - type_: typing.Any, - direction: typing.Literal["read", "write"], - aliases_to_field_names: typing.Dict[str, str], -) -> str: - if direction == "read": - return aliases_to_field_names.get(key, key) - return _get_alias_from_type(type_=type_) or key diff --git a/seed/pydantic/allof/src/seed/api/describable.py b/seed/pydantic/allof/src/seed/api/describable.py deleted file mode 100644 index b22612d75808..000000000000 --- a/seed/pydantic/allof/src/seed/api/describable.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Describable(UniversalBaseModel): - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Describable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/detailed_org.py b/seed/pydantic/allof/src/seed/api/detailed_org.py deleted file mode 100644 index 0779d792bb37..000000000000 --- a/seed/pydantic/allof/src/seed/api/detailed_org.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .detailed_org_metadata import DetailedOrgMetadata - - -class DetailedOrg(UniversalBaseModel): - metadata: typing.Optional[DetailedOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/detailed_org_metadata.py b/seed/pydantic/allof/src/seed/api/detailed_org_metadata.py deleted file mode 100644 index fe3f63b969a5..000000000000 --- a/seed/pydantic/allof/src/seed/api/detailed_org_metadata.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class DetailedOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from DetailedOrg. - """ - - domain: typing.Optional[str] = pydantic.Field(default=None) - """ - Custom domain name. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/identifiable.py b/seed/pydantic/allof/src/seed/api/identifiable.py deleted file mode 100644 index 825da5f0d495..000000000000 --- a/seed/pydantic/allof/src/seed/api/identifiable.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Identifiable(UniversalBaseModel): - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Identifiable. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/organization.py b/seed/pydantic/allof/src/seed/api/organization.py deleted file mode 100644 index c997aafe249b..000000000000 --- a/seed/pydantic/allof/src/seed/api/organization.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .base_org_metadata import BaseOrgMetadata -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Organization(UniversalBaseModel): - name: str - id: str - metadata: typing.Optional[BaseOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/paginated_result.py b/seed/pydantic/allof/src/seed/api/paginated_result.py deleted file mode 100644 index 089091066549..000000000000 --- a/seed/pydantic/allof/src/seed/api/paginated_result.py +++ /dev/null @@ -1,22 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors - - -class PaginatedResult(UniversalBaseModel): - paging: PagingCursors - results: typing.List[typing.Any] = pydantic.Field() - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/paging_cursors.py b/seed/pydantic/allof/src/seed/api/paging_cursors.py deleted file mode 100644 index 9132a3ed2eaf..000000000000 --- a/seed/pydantic/allof/src/seed/api/paging_cursors.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class PagingCursors(UniversalBaseModel): - next: str = pydantic.Field() - """ - Cursor for the next page of results. - """ - - previous: typing.Optional[str] = pydantic.Field(default=None) - """ - Cursor for the previous page of results. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/py.typed b/seed/pydantic/allof/src/seed/api/py.typed deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/pydantic/allof/src/seed/api/rule_execution_context.py b/seed/pydantic/allof/src/seed/api/rule_execution_context.py deleted file mode 100644 index 1d22ed9cabf8..000000000000 --- a/seed/pydantic/allof/src/seed/api/rule_execution_context.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/pydantic/allof/src/seed/api/rule_response.py b/seed/pydantic/allof/src/seed/api/rule_response.py deleted file mode 100644 index fb3fbc248532..000000000000 --- a/seed/pydantic/allof/src/seed/api/rule_response.py +++ /dev/null @@ -1,29 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -import typing_extensions -from .audit_info import AuditInfo -from .core.pydantic_utilities import IS_PYDANTIC_V2 -from .core.serialization import FieldMetadata -from .rule_execution_context import RuleExecutionContext -from .rule_response_status import RuleResponseStatus - - -class RuleResponse(AuditInfo): - id: str - name: str - status: RuleResponseStatus - execution_context: typing_extensions.Annotated[ - typing.Optional[RuleExecutionContext], - FieldMetadata(alias="executionContext"), - pydantic.Field(alias="executionContext"), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/rule_response_status.py b/seed/pydantic/allof/src/seed/api/rule_response_status.py deleted file mode 100644 index 4cbd106638cb..000000000000 --- a/seed/pydantic/allof/src/seed/api/rule_response_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/pydantic/allof/src/seed/api/rule_type.py b/seed/pydantic/allof/src/seed/api/rule_type.py deleted file mode 100644 index 7f5646c5bc8a..000000000000 --- a/seed/pydantic/allof/src/seed/api/rule_type.py +++ /dev/null @@ -1,19 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class RuleType(UniversalBaseModel): - id: str - name: str - description: typing.Optional[str] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/rule_type_search_response.py b/seed/pydantic/allof/src/seed/api/rule_type_search_response.py deleted file mode 100644 index 375bcd2c1d28..000000000000 --- a/seed/pydantic/allof/src/seed/api/rule_type_search_response.py +++ /dev/null @@ -1,24 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .rule_type import RuleType - - -class RuleTypeSearchResponse(UniversalBaseModel): - results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - paging: PagingCursors - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/user.py b/seed/pydantic/allof/src/seed/api/user.py deleted file mode 100644 index b737a327a2ce..000000000000 --- a/seed/pydantic/allof/src/seed/api/user.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class User(UniversalBaseModel): - id: str - email: str - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/src/seed/api/user_search_response.py b/seed/pydantic/allof/src/seed/api/user_search_response.py deleted file mode 100644 index f2433ca6d99f..000000000000 --- a/seed/pydantic/allof/src/seed/api/user_search_response.py +++ /dev/null @@ -1,24 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .user import User - - -class UserSearchResponse(UniversalBaseModel): - results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - paging: PagingCursors - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 - else: - - class Config: - extra = pydantic.Extra.allow diff --git a/seed/pydantic/allof/tests/custom/test_client.py b/seed/pydantic/allof/tests/custom/test_client.py deleted file mode 100644 index ab04ce6393ef..000000000000 --- a/seed/pydantic/allof/tests/custom/test_client.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - - -# Get started with writing tests with pytest at https://docs.pytest.org -@pytest.mark.skip(reason="Unimplemented") -def test_client() -> None: - assert True diff --git a/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json b/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json deleted file mode 100644 index d3303ad03b0b..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/.fern/metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "local", - "generatorConfig": { - "enable_wire_tests": true - }, - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml b/seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml deleted file mode 100644 index fd1df043d08d..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/.github/workflows/ci.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: ci -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Compile - run: poetry run mypy . - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - - name: Test - run: poetry run pytest -rP -n auto . - - - name: Install aiohttp extra - run: poetry install --extras aiohttp - - - name: Test (aiohttp) - run: poetry run pytest -rP -n auto -m aiohttp . - - publish: - needs: [compile, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Publish to pypi - run: | - poetry config repositories.remote - poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" - env: - PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} - PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/python-sdk/allof-inline/no-custom-config/.gitignore b/seed/python-sdk/allof-inline/no-custom-config/.gitignore deleted file mode 100644 index d2e4ca808d21..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.mypy_cache/ -.ruff_cache/ -__pycache__/ -dist/ -poetry.toml diff --git a/seed/python-sdk/allof-inline/no-custom-config/README.md b/seed/python-sdk/allof-inline/no-custom-config/README.md deleted file mode 100644 index 327e202173c9..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/README.md +++ /dev/null @@ -1,176 +0,0 @@ -# Seed Python Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPython) -[![pypi](https://img.shields.io/pypi/v/fern_allof-inline)](https://pypi.python.org/pypi/fern_allof-inline) - -The Seed Python library provides convenient access to the Seed APIs from Python. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Async Client](#async-client) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Access Raw Response Data](#access-raw-response-data) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Custom Client](#custom-client) -- [Contributing](#contributing) - -## Installation - -```sh -pip install fern_allof-inline -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```python -from seed import SeedApi - -client = SeedApi() - -client.create_rule( - name="name", - execution_context="prod", -) -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) -``` - -## Async Client - -The SDK also exports an `async` client so that you can make non-blocking calls to our API. Note that if you are constructing an Async httpx client class to pass into this client, use `httpx.AsyncClient()` instead of `httpx.Client()` (e.g. for the `httpx_client` parameter of this client). - -```python -import asyncio - -from seed import AsyncSeedApi - -client = AsyncSeedApi() - - -async def main() -> None: - await client.create_rule( - name="name", - execution_context="prod", - ) - - -asyncio.run(main()) -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```python -from seed.core.api_error import ApiError - -try: - client.create_rule(...) -except ApiError as e: - print(e.status_code) - print(e.body) -``` - -## Advanced - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.with_raw_response` property. -The `.with_raw_response` property returns a "raw" client that can be used to access the `.headers` and `.data` attributes. - -```python -from seed import SeedApi - -client = SeedApi(...) -response = client.with_raw_response.create_rule(...) -print(response.headers) # access the response headers -print(response.status_code) # access the response status code -print(response.data) # access the underlying object -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `max_retries` request option to configure this behavior. - -```python -client.create_rule(..., request_options={ - "max_retries": 1 -}) -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. - -```python -from seed import SeedApi - -client = SeedApi(..., timeout=20.0) - -# Override timeout for a specific method -client.create_rule(..., request_options={ - "timeout_in_seconds": 1 -}) -``` - -### Custom Client - -You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies -and transports. - -```python -import httpx -from seed import SeedApi - -client = SeedApi( - ..., - httpx_client=httpx.Client( - proxy="http://my.test.proxy.example.com", - transport=httpx.HTTPTransport(local_address="0.0.0.0"), - ), -) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/allof-inline/no-custom-config/poetry.lock b/seed/python-sdk/allof-inline/no-custom-config/poetry.lock deleted file mode 100644 index 613c65b508b1..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/poetry.lock +++ /dev/null @@ -1,1614 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -description = "Happy Eyeballs for asyncio" -optional = true -python-versions = ">=3.9" -files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, -] - -[[package]] -name = "aiohttp" -version = "3.13.5" -description = "Async http client/server framework (asyncio)" -optional = true -python-versions = ">=3.9" -files = [ - {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, - {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, - {file = "aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7"}, - {file = "aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9"}, - {file = "aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3"}, - {file = "aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06"}, - {file = "aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14"}, - {file = "aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3"}, - {file = "aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c"}, - {file = "aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc"}, - {file = "aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b"}, - {file = "aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3"}, - {file = "aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9"}, - {file = "aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8"}, - {file = "aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9"}, - {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:347542f0ea3f95b2a955ee6656461fa1c776e401ac50ebce055a6c38454a0adf"}, - {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:178c7b5e62b454c2bc790786e6058c3cc968613b4419251b478c153a4aec32b1"}, - {file = "aiohttp-3.13.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af545c2cffdb0967a96b6249e6f5f7b0d92cdfd267f9d5238d5b9ca63e8edb10"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:206b7b3ef96e4ce211754f0cd003feb28b7d81f0ad26b8d077a5d5161436067f"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee5e86776273de1795947d17bddd6bb19e0365fd2af4289c0d2c5454b6b1d36b"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95d14ca7abefde230f7639ec136ade282655431fd5db03c343b19dda72dd1643"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:912d4b6af530ddb1338a66229dac3a25ff11d4448be3ec3d6340583995f56031"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e999f0c88a458c836d5fb521814e92ed2172c649200336a6df514987c1488258"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39380e12bd1f2fdab4285b6e055ad48efbaed5c836433b142ed4f5b9be71036a"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9efcc0f11d850cefcafdd9275b9576ad3bfb539bed96807663b32ad99c4d4b88"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:147b4f501d0292077f29d5268c16bb7c864a1f054d7001c4c1812c0421ea1ed0"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d147004fede1b12f6013a6dbb2a26a986a671a03c6ea740ddc76500e5f1c399f"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9277145d36a01653863899c665243871434694bcc3431922c3b35c978061bdb8"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4e704c52438f66fdd89588346183d898bb42167cf88f8b7ff1c0f9fc957c348f"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8a4d3427e8de1312ddf309cc482186466c79895b3a139fed3259fc01dfa9a5b"}, - {file = "aiohttp-3.13.5-cp39-cp39-win32.whl", hash = "sha256:6f497a6876aa4b1a102b04996ce4c1170c7040d83faa9387dd921c16e30d5c83"}, - {file = "aiohttp-3.13.5-cp39-cp39-win_amd64.whl", hash = "sha256:cb979826071c0986a5f08333a36104153478ce6018c58cba7f9caddaf63d5d67"}, - {file = "aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.5.0" -aiosignal = ">=1.4.0" -async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -propcache = ">=0.2.0" -yarl = ">=1.17.0,<2.0" - -[package.extras] -speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] - -[[package]] -name = "aiosignal" -version = "1.4.0" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = true -python-versions = ">=3.9" -files = [ - {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, - {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" -typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.13.0" -description = "High-level concurrency and networking framework on top of asyncio or Trio" -optional = false -python-versions = ">=3.10" -files = [ - {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, - {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -trio = ["trio (>=0.32.0)"] - -[[package]] -name = "async-timeout" -version = "5.0.1" -description = "Timeout context manager for asyncio programs" -optional = true -python-versions = ">=3.8" -files = [ - {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, - {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, -] - -[[package]] -name = "attrs" -version = "26.1.0" -description = "Classes Without Boilerplate" -optional = true -python-versions = ">=3.9" -files = [ - {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, - {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." -optional = false -python-versions = "<3.11,>=3.8" -files = [ - {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, - {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, -] - -[[package]] -name = "certifi" -version = "2026.2.25" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -files = [ - {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, - {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.7" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -files = [ - {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, - {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.2" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, - {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "frozenlist" -version = "1.8.0" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = true -python-versions = ">=3.9" -files = [ - {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, - {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, - {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, - {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, - {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, - {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, - {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, - {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, - {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, - {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, - {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, - {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, - {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, - {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, - {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, - {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, - {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, - {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, - {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, - {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, - {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, - {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, - {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, - {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, - {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, - {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, - {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, - {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, - {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, - {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, - {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, - {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, - {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, - {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, - {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, - {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, - {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, - {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, - {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, - {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, - {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, - {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, - {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, - {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, - {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, - {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, - {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, - {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, - {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, - {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, -] - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, - {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.16" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "httpx-aiohttp" -version = "0.1.8" -description = "Aiohttp transport for HTTPX" -optional = true -python-versions = ">=3.8" -files = [ - {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, - {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, -] - -[package.dependencies] -aiohttp = ">=3.10.0,<4" -httpx = ">=0.27.0" - -[[package]] -name = "idna" -version = "3.11" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "multidict" -version = "6.7.1" -description = "multidict implementation" -optional = true -python-versions = ">=3.9" -files = [ - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, - {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, - {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, - {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, - {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, - {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, - {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, - {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, - {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, - {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, - {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, - {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, - {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, - {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, - {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, - {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, - {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, - {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, - {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, - {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, - {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, - {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, - {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, - {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, - {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, - {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, - {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "mypy" -version = "1.13.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "packaging" -version = "26.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "propcache" -version = "0.4.1" -description = "Accelerated property cache" -optional = true -python-versions = ">=3.9" -files = [ - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, - {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, - {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, - {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, - {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, - {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, - {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, - {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, - {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, - {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, - {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, - {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, - {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, - {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, - {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, - {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, - {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, - {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, - {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, - {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, - {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, - {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, - {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, - {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, - {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, - {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, - {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, - {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.41.5" -typing-extensions = ">=4.14.1" -typing-inspection = ">=0.4.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, - {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, -] - -[package.dependencies] -typing-extensions = ">=4.14.1" - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.4.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.10" -files = [ - {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, - {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, -] - -[package.dependencies] -backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} -pytest = ">=8.2,<10" -typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-xdist" -version = "3.8.0" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, - {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "requests" -version = "2.33.1" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.10" -files = [ - {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, - {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, -] - -[package.dependencies] -certifi = ">=2023.5.7" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.26,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] - -[[package]] -name = "ruff" -version = "0.11.5" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, - {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, - {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, - {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, - {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, - {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, - {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "tomli" -version = "2.4.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, - {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, - {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, - {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, - {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, - {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, - {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, - {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, - {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, - {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, - {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, - {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, - {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, - {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, - {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, - {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, - {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, - {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, - {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, -] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20260408" -description = "Typing stubs for python-dateutil" -optional = false -python-versions = ">=3.10" -files = [ - {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, - {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, -] - -[[package]] -name = "types-requests" -version = "2.33.0.20260408" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.10" -files = [ - {file = "types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f"}, - {file = "types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b"}, -] - -[package.dependencies] -urllib3 = ">=2" - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[[package]] -name = "urllib3" -version = "2.6.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, -] - -[package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] - -[[package]] -name = "yarl" -version = "1.23.0" -description = "Yet another URL library" -optional = true -python-versions = ">=3.10" -files = [ - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, - {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, - {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, - {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, - {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, - {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, - {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, - {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, - {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, - {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, - {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, - {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, - {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, - {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, - {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, - {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, - {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, - {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, - {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, - {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, - {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, - {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, - {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, - {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.1" - -[extras] -aiohttp = ["aiohttp", "httpx-aiohttp"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "7ac12b5d251e81a278fba2c051cbf4f768fb951f006f23ad0faedb1289539c1a" diff --git a/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml b/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml deleted file mode 100644 index dc12b84c3a93..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/pyproject.toml +++ /dev/null @@ -1,102 +0,0 @@ -[project] -name = "fern_allof-inline" -dynamic = ["version"] - -[tool.poetry] -name = "fern_allof-inline" -version = "0.0.1" -description = "" -readme = "README.md" -authors = [] -keywords = [ - "fern", - "test" -] - -classifiers = [ - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Programming Language :: Python :: 3.15", - "Operating System :: OS Independent", - "Operating System :: POSIX", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Topic :: Software Development :: Libraries :: Python Modules", - "Typing :: Typed" -] -packages = [ - { include = "seed", from = "src"} -] - -[tool.poetry.urls] -Documentation = 'https://buildwithfern.com/learn' -Homepage = 'https://buildwithfern.com/' -Repository = 'https://github.com/allof-inline/fern' - -[tool.poetry.dependencies] -python = "^3.10" -aiohttp = { version = ">=3.10.0,<4", optional = true} -httpx = ">=0.21.2" -httpx-aiohttp = { version = "0.1.8", optional = true} -pydantic = ">= 1.9.2" -pydantic-core = ">=2.18.2,<2.44.0" -typing_extensions = ">= 4.0.0" - -[tool.poetry.group.dev.dependencies] -mypy = "==1.13.0" -pytest = "^8.2.0" -pytest-asyncio = "^1.0.0" -pytest-xdist = "^3.6.1" -python-dateutil = "^2.9.0" -types-python-dateutil = "^2.9.0.20240316" -requests = "^2.31.0" -types-requests = "^2.31.0" -ruff = "==0.11.5" - -[tool.pytest.ini_options] -testpaths = [ "tests" ] -asyncio_mode = "auto" -markers = [ - "aiohttp: tests that require httpx_aiohttp to be installed", -] - -[tool.mypy] -plugins = ["pydantic.mypy"] - -[tool.ruff] -line-length = 120 - -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "F", # pyflakes - "I", # isort -] -ignore = [ - "E402", # Module level import not at top of file - "E501", # Line too long - "E711", # Comparison to `None` should be `cond is not None` - "E712", # Avoid equality comparisons to `True`; use `if ...:` checks - "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks - "E722", # Do not use bare `except` - "E731", # Do not assign a `lambda` expression, use a `def` - "F821", # Undefined name - "F841" # Local variable ... is assigned to but never used -] - -[tool.ruff.lint.isort] -section-order = ["future", "standard-library", "third-party", "first-party"] - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry.extras] -aiohttp=["aiohttp", "httpx-aiohttp"] diff --git a/seed/python-sdk/allof-inline/no-custom-config/reference.md b/seed/python-sdk/allof-inline/no-custom-config/reference.md deleted file mode 100644 index e92bd3a6925d..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/reference.md +++ /dev/null @@ -1,268 +0,0 @@ -# Reference -
client.search_rule_types(...) -> RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.search_rule_types() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `typing.Optional[str]` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.create_rule(...) -> RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.create_rule( - name="name", - execution_context="prod", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `str` - -
-
- -
-
- -**execution_context:** `RuleExecutionContext` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.list_users() -> UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.list_users() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.get_entity() -> CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.get_entity() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.get_organization() -> Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.get_organization() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- diff --git a/seed/python-sdk/allof-inline/no-custom-config/requirements.txt b/seed/python-sdk/allof-inline/no-custom-config/requirements.txt deleted file mode 100644 index 0141a1a5014b..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -httpx>=0.21.2 -pydantic>= 1.9.2 -pydantic-core>=2.18.2,<2.44.0 -typing_extensions>= 4.0.0 diff --git a/seed/python-sdk/allof-inline/no-custom-config/snippet.json b/seed/python-sdk/allof-inline/no-custom-config/snippet.json deleted file mode 100644 index f000c2ddf786..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/snippet.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "types": {}, - "endpoints": [ - { - "example_identifier": "default", - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.search_rule_types()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.search_rule_types()\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n)\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n )\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.list_users()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.list_users()\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_entity()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_entity()\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_organization()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_organization()\n\n\nasyncio.run(main())\n", - "type": "python" - } - } - ] -} \ No newline at end of file diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py deleted file mode 100644 index fce5a035510d..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from .types import ( - AuditInfo, - BaseOrg, - BaseOrgMetadata, - CombinedEntity, - CombinedEntityStatus, - Describable, - DetailedOrg, - DetailedOrgMetadata, - Identifiable, - Organization, - OrganizationMetadata, - PaginatedResult, - PagingCursors, - RuleExecutionContext, - RuleResponse, - RuleResponseStatus, - RuleType, - RuleTypeSearchResponse, - User, - UserSearchResponse, - ) - from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient - from .client import AsyncSeedApi, SeedApi - from .environment import SeedApiEnvironment - from .version import __version__ -_dynamic_imports: typing.Dict[str, str] = { - "AsyncSeedApi": ".client", - "AuditInfo": ".types", - "BaseOrg": ".types", - "BaseOrgMetadata": ".types", - "CombinedEntity": ".types", - "CombinedEntityStatus": ".types", - "DefaultAioHttpClient": "._default_clients", - "DefaultAsyncHttpxClient": "._default_clients", - "Describable": ".types", - "DetailedOrg": ".types", - "DetailedOrgMetadata": ".types", - "Identifiable": ".types", - "Organization": ".types", - "OrganizationMetadata": ".types", - "PaginatedResult": ".types", - "PagingCursors": ".types", - "RuleExecutionContext": ".types", - "RuleResponse": ".types", - "RuleResponseStatus": ".types", - "RuleType": ".types", - "RuleTypeSearchResponse": ".types", - "SeedApi": ".client", - "SeedApiEnvironment": ".environment", - "User": ".types", - "UserSearchResponse": ".types", - "__version__": ".version", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = [ - "AsyncSeedApi", - "AuditInfo", - "BaseOrg", - "BaseOrgMetadata", - "CombinedEntity", - "CombinedEntityStatus", - "DefaultAioHttpClient", - "DefaultAsyncHttpxClient", - "Describable", - "DetailedOrg", - "DetailedOrgMetadata", - "Identifiable", - "Organization", - "OrganizationMetadata", - "PaginatedResult", - "PagingCursors", - "RuleExecutionContext", - "RuleResponse", - "RuleResponseStatus", - "RuleType", - "RuleTypeSearchResponse", - "SeedApi", - "SeedApiEnvironment", - "User", - "UserSearchResponse", - "__version__", -] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py deleted file mode 100644 index 61f3fedfc0ce..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/_default_clients.py +++ /dev/null @@ -1,32 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import httpx - -SDK_DEFAULT_TIMEOUT = 60 - -try: - import httpx_aiohttp # type: ignore[import-not-found] -except ImportError: - - class DefaultAioHttpClient(httpx.AsyncClient): # type: ignore - def __init__(self, **kwargs: typing.Any) -> None: - raise RuntimeError( - "To use the aiohttp client, install the aiohttp extra: pip install fern_allof-inline[aiohttp]" - ) - -else: - - class DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore - def __init__(self, **kwargs: typing.Any) -> None: - kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) - kwargs.setdefault("follow_redirects", True) - super().__init__(**kwargs) - - -class DefaultAsyncHttpxClient(httpx.AsyncClient): - def __init__(self, **kwargs: typing.Any) -> None: - kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) - kwargs.setdefault("follow_redirects", True) - super().__init__(**kwargs) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py deleted file mode 100644 index 65adf5ca16bc..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/client.py +++ /dev/null @@ -1,500 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import httpx -from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .core.logging import LogConfig, Logger -from .core.request_options import RequestOptions -from .environment import SeedApiEnvironment -from .raw_client import AsyncRawSeedApi, RawSeedApi -from .types.combined_entity import CombinedEntity -from .types.organization import Organization -from .types.rule_execution_context import RuleExecutionContext -from .types.rule_response import RuleResponse -from .types.rule_type_search_response import RuleTypeSearchResponse -from .types.user_search_response import UserSearchResponse - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class SeedApi: - """ - Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. - - Parameters - ---------- - base_url : typing.Optional[str] - The base url to use for requests from the client. - - environment : SeedApiEnvironment - The environment to use for requests from the client. from .environment import SeedApiEnvironment - - - - Defaults to SeedApiEnvironment.DEFAULT - - - - headers : typing.Optional[typing.Dict[str, str]] - Additional headers to send with every request. - - timeout : typing.Optional[float] - The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. - - follow_redirects : typing.Optional[bool] - Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. - - httpx_client : typing.Optional[httpx.Client] - The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. - - logging : typing.Optional[typing.Union[LogConfig, Logger]] - Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - """ - - def __init__( - self, - *, - base_url: typing.Optional[str] = None, - environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read - ) - self._client_wrapper = SyncClientWrapper( - base_url=_get_base_url(base_url=base_url, environment=environment), - headers=headers, - httpx_client=httpx_client - if httpx_client is not None - else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) - if follow_redirects is not None - else httpx.Client(timeout=_defaulted_timeout), - timeout=_defaulted_timeout, - logging=logging, - ) - self._raw_client = RawSeedApi(client_wrapper=self._client_wrapper) - - @property - def with_raw_response(self) -> RawSeedApi: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawSeedApi - """ - return self._raw_client - - def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> RuleTypeSearchResponse: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleTypeSearchResponse - Paginated list of rule types - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.search_rule_types() - """ - _response = self._raw_client.search_rule_types(query=query, request_options=request_options) - return _response.data - - def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> RuleResponse: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleResponse - Created rule - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.create_rule( - name="name", - execution_context="prod", - ) - """ - _response = self._raw_client.create_rule( - name=name, execution_context=execution_context, request_options=request_options - ) - return _response.data - - def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - UserSearchResponse - Paginated list of users - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.list_users() - """ - _response = self._raw_client.list_users(request_options=request_options) - return _response.data - - def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CombinedEntity - An entity with properties from multiple parents - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.get_entity() - """ - _response = self._raw_client.get_entity(request_options=request_options) - return _response.data - - def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Organization - An organization whose metadata is merged from two parents - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.get_organization() - """ - _response = self._raw_client.get_organization(request_options=request_options) - return _response.data - - -def _make_default_async_client( - timeout: typing.Optional[float], - follow_redirects: typing.Optional[bool], -) -> httpx.AsyncClient: - try: - import httpx_aiohttp # type: ignore[import-not-found] - except ImportError: - pass - else: - if follow_redirects is not None: - return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects) - return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout) - - if follow_redirects is not None: - return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) - return httpx.AsyncClient(timeout=timeout) - - -class AsyncSeedApi: - """ - Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. - - Parameters - ---------- - base_url : typing.Optional[str] - The base url to use for requests from the client. - - environment : SeedApiEnvironment - The environment to use for requests from the client. from .environment import SeedApiEnvironment - - - - Defaults to SeedApiEnvironment.DEFAULT - - - - headers : typing.Optional[typing.Dict[str, str]] - Additional headers to send with every request. - - timeout : typing.Optional[float] - The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. - - follow_redirects : typing.Optional[bool] - Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. - - httpx_client : typing.Optional[httpx.AsyncClient] - The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. - - logging : typing.Optional[typing.Union[LogConfig, Logger]] - Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. - - Examples - -------- - from seed import AsyncSeedApi - - client = AsyncSeedApi() - """ - - def __init__( - self, - *, - base_url: typing.Optional[str] = None, - environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read - ) - self._client_wrapper = AsyncClientWrapper( - base_url=_get_base_url(base_url=base_url, environment=environment), - headers=headers, - httpx_client=httpx_client - if httpx_client is not None - else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), - timeout=_defaulted_timeout, - logging=logging, - ) - self._raw_client = AsyncRawSeedApi(client_wrapper=self._client_wrapper) - - @property - def with_raw_response(self) -> AsyncRawSeedApi: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawSeedApi - """ - return self._raw_client - - async def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> RuleTypeSearchResponse: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleTypeSearchResponse - Paginated list of rule types - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.search_rule_types() - - - asyncio.run(main()) - """ - _response = await self._raw_client.search_rule_types(query=query, request_options=request_options) - return _response.data - - async def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> RuleResponse: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleResponse - Created rule - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.create_rule( - name="name", - execution_context="prod", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.create_rule( - name=name, execution_context=execution_context, request_options=request_options - ) - return _response.data - - async def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - UserSearchResponse - Paginated list of users - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.list_users() - - - asyncio.run(main()) - """ - _response = await self._raw_client.list_users(request_options=request_options) - return _response.data - - async def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CombinedEntity - An entity with properties from multiple parents - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.get_entity() - - - asyncio.run(main()) - """ - _response = await self._raw_client.get_entity(request_options=request_options) - return _response.data - - async def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Organization - An organization whose metadata is merged from two parents - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.get_organization() - - - asyncio.run(main()) - """ - _response = await self._raw_client.get_organization(request_options=request_options) - return _response.data - - -def _get_base_url(*, base_url: typing.Optional[str] = None, environment: SeedApiEnvironment) -> str: - if base_url is not None: - return base_url - elif environment is not None: - return environment.value - else: - raise Exception("Please pass in either base_url or environment to construct the client") diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py deleted file mode 100644 index 5bc159a110f2..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/__init__.py +++ /dev/null @@ -1,127 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from .api_error import ApiError - from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper - from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime - from .file import File, convert_file_dict_to_httpx_tuples, with_content_type - from .http_client import AsyncHttpClient, HttpClient - from .http_response import AsyncHttpResponse, HttpResponse - from .jsonable_encoder import encode_path_param, jsonable_encoder - from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger - from .parse_error import ParsingError - from .pydantic_utilities import ( - IS_PYDANTIC_V2, - UniversalBaseModel, - UniversalRootModel, - parse_obj_as, - universal_field_validator, - universal_root_validator, - update_forward_refs, - ) - from .query_encoder import encode_query - from .remove_none_from_dict import remove_none_from_dict - from .request_options import RequestOptions - from .serialization import FieldMetadata, convert_and_respect_annotation_metadata -_dynamic_imports: typing.Dict[str, str] = { - "ApiError": ".api_error", - "AsyncClientWrapper": ".client_wrapper", - "AsyncHttpClient": ".http_client", - "AsyncHttpResponse": ".http_response", - "BaseClientWrapper": ".client_wrapper", - "ConsoleLogger": ".logging", - "FieldMetadata": ".serialization", - "File": ".file", - "HttpClient": ".http_client", - "HttpResponse": ".http_response", - "ILogger": ".logging", - "IS_PYDANTIC_V2": ".pydantic_utilities", - "LogConfig": ".logging", - "LogLevel": ".logging", - "Logger": ".logging", - "ParsingError": ".parse_error", - "RequestOptions": ".request_options", - "Rfc2822DateTime": ".datetime_utils", - "SyncClientWrapper": ".client_wrapper", - "UniversalBaseModel": ".pydantic_utilities", - "UniversalRootModel": ".pydantic_utilities", - "convert_and_respect_annotation_metadata": ".serialization", - "convert_file_dict_to_httpx_tuples": ".file", - "create_logger": ".logging", - "encode_path_param": ".jsonable_encoder", - "encode_query": ".query_encoder", - "jsonable_encoder": ".jsonable_encoder", - "parse_obj_as": ".pydantic_utilities", - "parse_rfc2822_datetime": ".datetime_utils", - "remove_none_from_dict": ".remove_none_from_dict", - "serialize_datetime": ".datetime_utils", - "universal_field_validator": ".pydantic_utilities", - "universal_root_validator": ".pydantic_utilities", - "update_forward_refs": ".pydantic_utilities", - "with_content_type": ".file", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = [ - "ApiError", - "AsyncClientWrapper", - "AsyncHttpClient", - "AsyncHttpResponse", - "BaseClientWrapper", - "ConsoleLogger", - "FieldMetadata", - "File", - "HttpClient", - "HttpResponse", - "ILogger", - "IS_PYDANTIC_V2", - "LogConfig", - "LogLevel", - "Logger", - "ParsingError", - "RequestOptions", - "Rfc2822DateTime", - "SyncClientWrapper", - "UniversalBaseModel", - "UniversalRootModel", - "convert_and_respect_annotation_metadata", - "convert_file_dict_to_httpx_tuples", - "create_logger", - "encode_path_param", - "encode_query", - "jsonable_encoder", - "parse_obj_as", - "parse_rfc2822_datetime", - "remove_none_from_dict", - "serialize_datetime", - "universal_field_validator", - "universal_root_validator", - "update_forward_refs", - "with_content_type", -] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py deleted file mode 100644 index 6f850a60cba3..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/api_error.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Optional - - -class ApiError(Exception): - headers: Optional[Dict[str, str]] - status_code: Optional[int] - body: Any - - def __init__( - self, - *, - headers: Optional[Dict[str, str]] = None, - status_code: Optional[int] = None, - body: Any = None, - ) -> None: - self.headers = headers - self.status_code = status_code - self.body = body - - def __str__(self) -> str: - return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py deleted file mode 100644 index 381e10b9c2ea..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/client_wrapper.py +++ /dev/null @@ -1,95 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import httpx -from .http_client import AsyncHttpClient, HttpClient -from .logging import LogConfig, Logger - - -class BaseClientWrapper: - def __init__( - self, - *, - headers: typing.Optional[typing.Dict[str, str]] = None, - base_url: str, - timeout: typing.Optional[float] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - self._headers = headers - self._base_url = base_url - self._timeout = timeout - self._logging = logging - - def get_headers(self) -> typing.Dict[str, str]: - import platform - - headers: typing.Dict[str, str] = { - "User-Agent": "fern_allof-inline/0.0.1", - "X-Fern-Language": "Python", - "X-Fern-Runtime": f"python/{platform.python_version()}", - "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", - "X-Fern-SDK-Name": "fern_allof-inline", - "X-Fern-SDK-Version": "0.0.1", - **(self.get_custom_headers() or {}), - } - return headers - - def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]: - return self._headers - - def get_base_url(self) -> str: - return self._base_url - - def get_timeout(self) -> typing.Optional[float]: - return self._timeout - - -class SyncClientWrapper(BaseClientWrapper): - def __init__( - self, - *, - headers: typing.Optional[typing.Dict[str, str]] = None, - base_url: str, - timeout: typing.Optional[float] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - httpx_client: httpx.Client, - ): - super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) - self.httpx_client = HttpClient( - httpx_client=httpx_client, - base_headers=self.get_headers, - base_timeout=self.get_timeout, - base_url=self.get_base_url, - logging_config=self._logging, - ) - - -class AsyncClientWrapper(BaseClientWrapper): - def __init__( - self, - *, - headers: typing.Optional[typing.Dict[str, str]] = None, - base_url: str, - timeout: typing.Optional[float] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, - httpx_client: httpx.AsyncClient, - ): - super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) - self._async_token = async_token - self.httpx_client = AsyncHttpClient( - httpx_client=httpx_client, - base_headers=self.get_headers, - base_timeout=self.get_timeout, - base_url=self.get_base_url, - async_base_headers=self.async_get_headers, - logging_config=self._logging, - ) - - async def async_get_headers(self) -> typing.Dict[str, str]: - headers = self.get_headers() - if self._async_token is not None: - token = await self._async_token() - headers["Authorization"] = f"Bearer {token}" - return headers diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py deleted file mode 100644 index a12b2ad03c53..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/datetime_utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -from email.utils import parsedate_to_datetime -from typing import Any - -import pydantic - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - - -def parse_rfc2822_datetime(v: Any) -> dt.datetime: - """ - Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") - into a datetime object. If the value is already a datetime, return it as-is. - Falls back to ISO 8601 parsing if RFC 2822 parsing fails. - """ - if isinstance(v, dt.datetime): - return v - if isinstance(v, str): - try: - return parsedate_to_datetime(v) - except Exception: - pass - # Fallback to ISO 8601 parsing - return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) - raise ValueError(f"Expected str or datetime, got {type(v)}") - - -class Rfc2822DateTime(dt.datetime): - """A datetime subclass that parses RFC 2822 date strings. - - On Pydantic V1, uses __get_validators__ for pre-validation. - On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. - """ - - @classmethod - def __get_validators__(cls): # type: ignore[no-untyped-def] - yield parse_rfc2822_datetime - - @classmethod - def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] - from pydantic_core import core_schema - - return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) - - -def serialize_datetime(v: dt.datetime) -> str: - """ - Serialize a datetime including timezone info. - - Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. - - UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. - """ - - def _serialize_zoned_datetime(v: dt.datetime) -> str: - if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): - # UTC is a special case where we use "Z" at the end instead of "+00:00" - return v.isoformat().replace("+00:00", "Z") - else: - # Delegate to the typical +/- offset format - return v.isoformat() - - if v.tzinfo is not None: - return _serialize_zoned_datetime(v) - else: - local_tz = dt.datetime.now().astimezone().tzinfo - localized_dt = v.replace(tzinfo=local_tz) - return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py deleted file mode 100644 index 44b0d27c0895..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/file.py +++ /dev/null @@ -1,67 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast - -# File typing inspired by the flexibility of types within the httpx library -# https://github.com/encode/httpx/blob/master/httpx/_types.py -FileContent = Union[IO[bytes], bytes, str] -File = Union[ - # file (or bytes) - FileContent, - # (filename, file (or bytes)) - Tuple[Optional[str], FileContent], - # (filename, file (or bytes), content_type) - Tuple[Optional[str], FileContent, Optional[str]], - # (filename, file (or bytes), content_type, headers) - Tuple[ - Optional[str], - FileContent, - Optional[str], - Mapping[str, str], - ], -] - - -def convert_file_dict_to_httpx_tuples( - d: Dict[str, Union[File, List[File]]], -) -> List[Tuple[str, File]]: - """ - The format we use is a list of tuples, where the first element is the - name of the file and the second is the file object. Typically HTTPX wants - a dict, but to be able to send lists of files, you have to use the list - approach (which also works for non-lists) - https://github.com/encode/httpx/pull/1032 - """ - - httpx_tuples = [] - for key, file_like in d.items(): - if isinstance(file_like, list): - for file_like_item in file_like: - httpx_tuples.append((key, file_like_item)) - else: - httpx_tuples.append((key, file_like)) - return httpx_tuples - - -def with_content_type(*, file: File, default_content_type: str) -> File: - """ - This function resolves to the file's content type, if provided, and defaults - to the default_content_type value if not. - """ - if isinstance(file, tuple): - if len(file) == 2: - filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, default_content_type) - elif len(file) == 3: - filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - out_content_type = file_content_type or default_content_type - return (filename, content, out_content_type) - elif len(file) == 4: - filename, content, file_content_type, headers = cast( # type: ignore - Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file - ) - out_content_type = file_content_type or default_content_type - return (filename, content, out_content_type, headers) - else: - raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, default_content_type) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py deleted file mode 100644 index 5440913fd4bc..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/force_multipart.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict - - -class ForceMultipartDict(Dict[str, Any]): - """ - A dictionary subclass that always evaluates to True in boolean contexts. - - This is used to force multipart/form-data encoding in HTTP requests even when - the dictionary is empty, which would normally evaluate to False. - """ - - def __bool__(self) -> bool: - return True - - -FORCE_MULTIPART = ForceMultipartDict() diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py deleted file mode 100644 index f0a39ca8243a..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_client.py +++ /dev/null @@ -1,840 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import asyncio -import email.utils -import re -import time -import typing -from contextlib import asynccontextmanager, contextmanager -from random import random - -import httpx -from .file import File, convert_file_dict_to_httpx_tuples -from .force_multipart import FORCE_MULTIPART -from .jsonable_encoder import jsonable_encoder -from .logging import LogConfig, Logger, create_logger -from .query_encoder import encode_query -from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict -from .request_options import RequestOptions -from httpx._types import RequestFiles - -INITIAL_RETRY_DELAY_SECONDS = 1.0 -MAX_RETRY_DELAY_SECONDS = 60.0 -JITTER_FACTOR = 0.2 # 20% random jitter - - -def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: - """ - This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. - - Inspired by the urllib3 retry implementation. - """ - retry_after_ms = response_headers.get("retry-after-ms") - if retry_after_ms is not None: - try: - return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 - except Exception: - pass - - retry_after = response_headers.get("retry-after") - if retry_after is None: - return None - - # Attempt to parse the header as an int. - if re.match(r"^\s*[0-9]+\s*$", retry_after): - seconds = float(retry_after) - # Fallback to parsing it as a date. - else: - retry_date_tuple = email.utils.parsedate_tz(retry_after) - if retry_date_tuple is None: - return None - if retry_date_tuple[9] is None: # Python 2 - # Assume UTC if no timezone was specified - # On Python2.7, parsedate_tz returns None for a timezone offset - # instead of 0 if no timezone is given, where mktime_tz treats - # a None timezone offset as local time. - retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] - - retry_date = email.utils.mktime_tz(retry_date_tuple) - seconds = retry_date - time.time() - - if seconds < 0: - seconds = 0 - - return seconds - - -def _add_positive_jitter(delay: float) -> float: - """Add positive jitter (0-20%) to prevent thundering herd.""" - jitter_multiplier = 1 + random() * JITTER_FACTOR - return delay * jitter_multiplier - - -def _add_symmetric_jitter(delay: float) -> float: - """Add symmetric jitter (±10%) for exponential backoff.""" - jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR - return delay * jitter_multiplier - - -def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]: - """ - Parse the X-RateLimit-Reset header (Unix timestamp in seconds). - Returns seconds to wait, or None if header is missing/invalid. - """ - reset_time_str = response_headers.get("x-ratelimit-reset") - if reset_time_str is None: - return None - - try: - reset_time = int(reset_time_str) - delay = reset_time - time.time() - if delay > 0: - return delay - except (ValueError, TypeError): - pass - - return None - - -def _retry_timeout(response: httpx.Response, retries: int) -> float: - """ - Determine the amount of time to wait before retrying a request. - This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff - with a jitter to determine the number of seconds to wait. - """ - - # 1. Check Retry-After header first - retry_after = _parse_retry_after(response.headers) - if retry_after is not None and retry_after > 0: - return min(retry_after, MAX_RETRY_DELAY_SECONDS) - - # 2. Check X-RateLimit-Reset header (with positive jitter) - ratelimit_reset = _parse_x_ratelimit_reset(response.headers) - if ratelimit_reset is not None: - return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS)) - - # 3. Fall back to exponential backoff (with symmetric jitter) - backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) - return _add_symmetric_jitter(backoff) - - -def _retry_timeout_from_retries(retries: int) -> float: - """Determine retry timeout using exponential backoff when no response is available.""" - backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) - return _add_symmetric_jitter(backoff) - - -def _should_retry(response: httpx.Response) -> bool: - retryable_400s = [429, 408, 409] - return response.status_code >= 500 or response.status_code in retryable_400s - - -_SENSITIVE_HEADERS = frozenset( - { - "authorization", - "www-authenticate", - "x-api-key", - "api-key", - "apikey", - "x-api-token", - "x-auth-token", - "auth-token", - "cookie", - "set-cookie", - "proxy-authorization", - "proxy-authenticate", - "x-csrf-token", - "x-xsrf-token", - "x-session-token", - "x-access-token", - } -) - - -def _redact_headers(headers: typing.Dict[str, str]) -> typing.Dict[str, str]: - return {k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()} - - -def _build_url(base_url: str, path: typing.Optional[str]) -> str: - """ - Build a full URL by joining a base URL with a path. - - This function correctly handles base URLs that contain path prefixes (e.g., tenant-based URLs) - by using string concatenation instead of urllib.parse.urljoin(), which would incorrectly - strip path components when the path starts with '/'. - - Example: - >>> _build_url("https://cloud.example.com/org/tenant/api", "/users") - 'https://cloud.example.com/org/tenant/api/users' - - Args: - base_url: The base URL, which may contain path prefixes. - path: The path to append. Can be None or empty string. - - Returns: - The full URL with base_url and path properly joined. - """ - if not path: - return base_url - return f"{base_url.rstrip('/')}/{path.lstrip('/')}" - - -def _maybe_filter_none_from_multipart_data( - data: typing.Optional[typing.Any], - request_files: typing.Optional[RequestFiles], - force_multipart: typing.Optional[bool], -) -> typing.Optional[typing.Any]: - """ - Filter None values from data body for multipart/form requests. - This prevents httpx from converting None to empty strings in multipart encoding. - Only applies when files are present or force_multipart is True. - """ - if data is not None and isinstance(data, typing.Mapping) and (request_files or force_multipart): - return remove_none_from_dict(data) - return data - - -def remove_omit_from_dict( - original: typing.Dict[str, typing.Optional[typing.Any]], - omit: typing.Optional[typing.Any], -) -> typing.Dict[str, typing.Any]: - if omit is None: - return original - new: typing.Dict[str, typing.Any] = {} - for key, value in original.items(): - if value is not omit: - new[key] = value - return new - - -def maybe_filter_request_body( - data: typing.Optional[typing.Any], - request_options: typing.Optional[RequestOptions], - omit: typing.Optional[typing.Any], -) -> typing.Optional[typing.Any]: - if data is None: - return ( - jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} - if request_options is not None - else None - ) - elif not isinstance(data, typing.Mapping): - data_content = jsonable_encoder(data) - else: - data_content = { - **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore - **( - jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} - if request_options is not None - else {} - ), - } - return data_content - - -# Abstracted out for testing purposes -def get_request_body( - *, - json: typing.Optional[typing.Any], - data: typing.Optional[typing.Any], - request_options: typing.Optional[RequestOptions], - omit: typing.Optional[typing.Any], -) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: - json_body = None - data_body = None - if data is not None: - data_body = maybe_filter_request_body(data, request_options, omit) - else: - # If both data and json are None, we send json data in the event extra properties are specified - json_body = maybe_filter_request_body(json, request_options, omit) - - has_additional_body_parameters = bool( - request_options is not None and request_options.get("additional_body_parameters") - ) - - # Only collapse empty dict to None when the body was not explicitly provided - # and there are no additional body parameters. This preserves explicit empty - # bodies (e.g., when an endpoint has a request body type but all fields are optional). - if json_body == {} and json is None and not has_additional_body_parameters: - json_body = None - if data_body == {} and data is None and not has_additional_body_parameters: - data_body = None - - return json_body, data_body - - -class HttpClient: - def __init__( - self, - *, - httpx_client: httpx.Client, - base_timeout: typing.Callable[[], typing.Optional[float]], - base_headers: typing.Callable[[], typing.Dict[str, str]], - base_url: typing.Optional[typing.Callable[[], str]] = None, - base_max_retries: int = 2, - logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - self.base_url = base_url - self.base_timeout = base_timeout - self.base_headers = base_headers - self.base_max_retries = base_max_retries - self.httpx_client = httpx_client - self.logger = create_logger(logging_config) - - def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: - base_url = maybe_base_url - if self.base_url is not None and base_url is None: - base_url = self.base_url() - - if base_url is None: - raise ValueError("A base_url is required to make this request, please provide one and try again.") - return base_url - - def request( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> httpx.Response: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) or {} - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **self.base_headers(), - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - has_body=json_body is not None or data_body is not None, - ) - - max_retries: int = ( - request_options.get("max_retries", self.base_max_retries) - if request_options is not None - else self.base_max_retries - ) - - try: - response = self.httpx_client.request( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) - except (httpx.ConnectError, httpx.RemoteProtocolError): - if retries < max_retries: - time.sleep(_retry_timeout_from_retries(retries=retries)) - return self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - raise - - if _should_retry(response=response): - if retries < max_retries: - time.sleep(_retry_timeout(response=response, retries=retries)) - return self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - - if self.logger.is_debug(): - if 200 <= response.status_code < 400: - self.logger.debug( - "HTTP request succeeded", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - if self.logger.is_error(): - if response.status_code >= 400: - self.logger.error( - "HTTP request failed with error status", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - return response - - @contextmanager - def stream( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> typing.Iterator[httpx.Response]: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **self.base_headers(), - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making streaming HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - ) - - with self.httpx_client.stream( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) as stream: - yield stream - - -class AsyncHttpClient: - def __init__( - self, - *, - httpx_client: httpx.AsyncClient, - base_timeout: typing.Callable[[], typing.Optional[float]], - base_headers: typing.Callable[[], typing.Dict[str, str]], - base_url: typing.Optional[typing.Callable[[], str]] = None, - base_max_retries: int = 2, - async_base_headers: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]]] = None, - logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - self.base_url = base_url - self.base_timeout = base_timeout - self.base_headers = base_headers - self.base_max_retries = base_max_retries - self.async_base_headers = async_base_headers - self.httpx_client = httpx_client - self.logger = create_logger(logging_config) - - async def _get_headers(self) -> typing.Dict[str, str]: - if self.async_base_headers is not None: - return await self.async_base_headers() - return self.base_headers() - - def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: - base_url = maybe_base_url - if self.base_url is not None and base_url is None: - base_url = self.base_url() - - if base_url is None: - raise ValueError("A base_url is required to make this request, please provide one and try again.") - return base_url - - async def request( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> httpx.Response: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Get headers (supports async token providers) - _headers = await self._get_headers() - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) or {} - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **_headers, - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - has_body=json_body is not None or data_body is not None, - ) - - max_retries: int = ( - request_options.get("max_retries", self.base_max_retries) - if request_options is not None - else self.base_max_retries - ) - - try: - response = await self.httpx_client.request( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) - except (httpx.ConnectError, httpx.RemoteProtocolError): - if retries < max_retries: - await asyncio.sleep(_retry_timeout_from_retries(retries=retries)) - return await self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - raise - - if _should_retry(response=response): - if retries < max_retries: - await asyncio.sleep(_retry_timeout(response=response, retries=retries)) - return await self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - - if self.logger.is_debug(): - if 200 <= response.status_code < 400: - self.logger.debug( - "HTTP request succeeded", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - if self.logger.is_error(): - if response.status_code >= 400: - self.logger.error( - "HTTP request failed with error status", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - return response - - @asynccontextmanager - async def stream( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> typing.AsyncIterator[httpx.Response]: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Get headers (supports async token providers) - _headers = await self._get_headers() - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) - if request_options is not None - else {} - ), - }, - omit=omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **_headers, - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making streaming HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - ) - - async with self.httpx_client.stream( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) as stream: - yield stream diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py deleted file mode 100644 index 00bb1096d2d0..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_response.py +++ /dev/null @@ -1,59 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Dict, Generic, TypeVar - -import httpx - -# Generic to represent the underlying type of the data wrapped by the HTTP response. -T = TypeVar("T") - - -class BaseHttpResponse: - """Minimalist HTTP response wrapper that exposes response headers and status code.""" - - _response: httpx.Response - - def __init__(self, response: httpx.Response): - self._response = response - - @property - def headers(self) -> Dict[str, str]: - return dict(self._response.headers) - - @property - def status_code(self) -> int: - return self._response.status_code - - -class HttpResponse(Generic[T], BaseHttpResponse): - """HTTP response wrapper that exposes response headers and data.""" - - _data: T - - def __init__(self, response: httpx.Response, data: T): - super().__init__(response) - self._data = data - - @property - def data(self) -> T: - return self._data - - def close(self) -> None: - self._response.close() - - -class AsyncHttpResponse(Generic[T], BaseHttpResponse): - """HTTP response wrapper that exposes response headers and data.""" - - _data: T - - def __init__(self, response: httpx.Response, data: T): - super().__init__(response) - self._data = data - - @property - def data(self) -> T: - return self._data - - async def close(self) -> None: - await self._response.aclose() diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py deleted file mode 100644 index 730e5a3382eb..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from ._api import EventSource, aconnect_sse, connect_sse - from ._exceptions import SSEError - from ._models import ServerSentEvent -_dynamic_imports: typing.Dict[str, str] = { - "EventSource": "._api", - "SSEError": "._exceptions", - "ServerSentEvent": "._models", - "aconnect_sse": "._api", - "connect_sse": "._api", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py deleted file mode 100644 index f900b3b686de..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_api.py +++ /dev/null @@ -1,112 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import re -from contextlib import asynccontextmanager, contextmanager -from typing import Any, AsyncGenerator, AsyncIterator, Iterator, cast - -import httpx -from ._decoders import SSEDecoder -from ._exceptions import SSEError -from ._models import ServerSentEvent - - -class EventSource: - def __init__(self, response: httpx.Response) -> None: - self._response = response - - def _check_content_type(self) -> None: - content_type = self._response.headers.get("content-type", "").partition(";")[0] - if "text/event-stream" not in content_type: - raise SSEError( - f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}" - ) - - def _get_charset(self) -> str: - """Extract charset from Content-Type header, fallback to UTF-8.""" - content_type = self._response.headers.get("content-type", "") - - # Parse charset parameter using regex - charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE) - if charset_match: - charset = charset_match.group(1).strip("\"'") - # Validate that it's a known encoding - try: - # Test if the charset is valid by trying to encode/decode - "test".encode(charset).decode(charset) - return charset - except (LookupError, UnicodeError): - # If charset is invalid, fall back to UTF-8 - pass - - # Default to UTF-8 if no charset specified or invalid charset - return "utf-8" - - @property - def response(self) -> httpx.Response: - return self._response - - def iter_sse(self) -> Iterator[ServerSentEvent]: - self._check_content_type() - decoder = SSEDecoder() - charset = self._get_charset() - - buffer = "" - for chunk in self._response.iter_bytes(): - # Decode chunk using detected charset - text_chunk = chunk.decode(charset, errors="replace") - buffer += text_chunk - - # Process complete lines - while "\n" in buffer: - line, buffer = buffer.split("\n", 1) - line = line.rstrip("\r") - sse = decoder.decode(line) - # when we reach a "\n\n" => line = '' - # => decoder will attempt to return an SSE Event - if sse is not None: - yield sse - - # Process any remaining data in buffer - if buffer.strip(): - line = buffer.rstrip("\r") - sse = decoder.decode(line) - if sse is not None: - yield sse - - async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: - self._check_content_type() - decoder = SSEDecoder() - lines = cast(AsyncGenerator[str, None], self._response.aiter_lines()) - try: - async for line in lines: - line = line.rstrip("\n") - sse = decoder.decode(line) - if sse is not None: - yield sse - finally: - await lines.aclose() - - -@contextmanager -def connect_sse(client: httpx.Client, method: str, url: str, **kwargs: Any) -> Iterator[EventSource]: - headers = kwargs.pop("headers", {}) - headers["Accept"] = "text/event-stream" - headers["Cache-Control"] = "no-store" - - with client.stream(method, url, headers=headers, **kwargs) as response: - yield EventSource(response) - - -@asynccontextmanager -async def aconnect_sse( - client: httpx.AsyncClient, - method: str, - url: str, - **kwargs: Any, -) -> AsyncIterator[EventSource]: - headers = kwargs.pop("headers", {}) - headers["Accept"] = "text/event-stream" - headers["Cache-Control"] = "no-store" - - async with client.stream(method, url, headers=headers, **kwargs) as response: - yield EventSource(response) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py deleted file mode 100644 index 339b08901381..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_decoders.py +++ /dev/null @@ -1,61 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import List, Optional - -from ._models import ServerSentEvent - - -class SSEDecoder: - def __init__(self) -> None: - self._event = "" - self._data: List[str] = [] - self._last_event_id = "" - self._retry: Optional[int] = None - - def decode(self, line: str) -> Optional[ServerSentEvent]: - # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 - - if not line: - if not self._event and not self._data and not self._last_event_id and self._retry is None: - return None - - sse = ServerSentEvent( - event=self._event, - data="\n".join(self._data), - id=self._last_event_id, - retry=self._retry, - ) - - # NOTE: as per the SSE spec, do not reset last_event_id. - self._event = "" - self._data = [] - self._retry = None - - return sse - - if line.startswith(":"): - return None - - fieldname, _, value = line.partition(":") - - if value.startswith(" "): - value = value[1:] - - if fieldname == "event": - self._event = value - elif fieldname == "data": - self._data.append(value) - elif fieldname == "id": - if "\0" in value: - pass - else: - self._last_event_id = value - elif fieldname == "retry": - try: - self._retry = int(value) - except (TypeError, ValueError): - pass - else: - pass # Field is ignored. - - return None diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py deleted file mode 100644 index 81605a8a65ed..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_exceptions.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import httpx - - -class SSEError(httpx.TransportError): - pass diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py deleted file mode 100644 index 1af57f8fd0d2..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/http_sse/_models.py +++ /dev/null @@ -1,17 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import json -from dataclasses import dataclass -from typing import Any, Optional - - -@dataclass(frozen=True) -class ServerSentEvent: - event: str = "message" - data: str = "" - id: str = "" - retry: Optional[int] = None - - def json(self) -> Any: - """Parse the data field as JSON.""" - return json.loads(self.data) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py deleted file mode 100644 index 5b0902ebcde3..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py +++ /dev/null @@ -1,120 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -""" -jsonable_encoder converts a Python object to a JSON-friendly dict -(e.g. datetimes to strings, Pydantic models to dicts). - -Taken from FastAPI, and made a bit simpler -https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py -""" - -import base64 -import dataclasses -import datetime as dt -from enum import Enum -from pathlib import PurePath -from types import GeneratorType -from typing import Any, Callable, Dict, List, Optional, Set, Union - -import pydantic -from .datetime_utils import serialize_datetime -from .pydantic_utilities import ( - IS_PYDANTIC_V2, - encode_by_type, - to_jsonable_with_fallback, -) - -SetIntStr = Set[Union[int, str]] -DictIntStrAny = Dict[Union[int, str], Any] - - -def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: - custom_encoder = custom_encoder or {} - # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT". - # OMIT values should be excluded from serialized payloads. - if obj is Ellipsis: - return None - if custom_encoder: - if type(obj) in custom_encoder: - return custom_encoder[type(obj)](obj) - else: - for encoder_type, encoder_instance in custom_encoder.items(): - if isinstance(obj, encoder_type): - return encoder_instance(obj) - if isinstance(obj, pydantic.BaseModel): - if IS_PYDANTIC_V2: - encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 - else: - encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 - if custom_encoder: - encoder.update(custom_encoder) - obj_dict = obj.dict(by_alias=True) - if "__root__" in obj_dict: - obj_dict = obj_dict["__root__"] - if "root" in obj_dict: - obj_dict = obj_dict["root"] - return jsonable_encoder(obj_dict, custom_encoder=encoder) - if dataclasses.is_dataclass(obj): - obj_dict = dataclasses.asdict(obj) # type: ignore - return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) - if isinstance(obj, bytes): - return base64.b64encode(obj).decode("utf-8") - if isinstance(obj, Enum): - return obj.value - if isinstance(obj, PurePath): - return str(obj) - if isinstance(obj, (str, int, float, type(None))): - return obj - if isinstance(obj, dt.datetime): - return serialize_datetime(obj) - if isinstance(obj, dt.date): - return str(obj) - if isinstance(obj, dict): - encoded_dict = {} - allowed_keys = set(obj.keys()) - for key, value in obj.items(): - if key in allowed_keys: - if value is Ellipsis: - continue - encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) - encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) - encoded_dict[encoded_key] = encoded_value - return encoded_dict - if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): - encoded_list = [] - for item in obj: - if item is Ellipsis: - continue - encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) - return encoded_list - - def fallback_serializer(o: Any) -> Any: - attempt_encode = encode_by_type(o) - if attempt_encode is not None: - return attempt_encode - - try: - data = dict(o) - except Exception as e: - errors: List[Exception] = [] - errors.append(e) - try: - data = vars(o) - except Exception as e: - errors.append(e) - raise ValueError(errors) from e - return jsonable_encoder(data, custom_encoder=custom_encoder) - - return to_jsonable_with_fallback(obj, fallback_serializer) - - -def encode_path_param(obj: Any) -> str: - """Encode a value for use in a URL path segment. - - Ensures proper string conversion for all types, including - booleans which need lowercase 'true'/'false' rather than - Python's 'True'/'False'. - """ - if isinstance(obj, bool): - return "true" if obj else "false" - return str(jsonable_encoder(obj)) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py deleted file mode 100644 index e5e572458bc8..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/logging.py +++ /dev/null @@ -1,107 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import logging -import typing - -LogLevel = typing.Literal["debug", "info", "warn", "error"] - -_LOG_LEVEL_MAP: typing.Dict[LogLevel, int] = { - "debug": 1, - "info": 2, - "warn": 3, - "error": 4, -} - - -class ILogger(typing.Protocol): - def debug(self, message: str, **kwargs: typing.Any) -> None: ... - def info(self, message: str, **kwargs: typing.Any) -> None: ... - def warn(self, message: str, **kwargs: typing.Any) -> None: ... - def error(self, message: str, **kwargs: typing.Any) -> None: ... - - -class ConsoleLogger: - _logger: logging.Logger - - def __init__(self) -> None: - self._logger = logging.getLogger("fern") - if not self._logger.handlers: - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s")) - self._logger.addHandler(handler) - self._logger.setLevel(logging.DEBUG) - - def debug(self, message: str, **kwargs: typing.Any) -> None: - self._logger.debug(message, extra=kwargs) - - def info(self, message: str, **kwargs: typing.Any) -> None: - self._logger.info(message, extra=kwargs) - - def warn(self, message: str, **kwargs: typing.Any) -> None: - self._logger.warning(message, extra=kwargs) - - def error(self, message: str, **kwargs: typing.Any) -> None: - self._logger.error(message, extra=kwargs) - - -class LogConfig(typing.TypedDict, total=False): - level: LogLevel - logger: ILogger - silent: bool - - -class Logger: - _level: int - _logger: ILogger - _silent: bool - - def __init__(self, *, level: LogLevel, logger: ILogger, silent: bool) -> None: - self._level = _LOG_LEVEL_MAP[level] - self._logger = logger - self._silent = silent - - def _should_log(self, level: LogLevel) -> bool: - return not self._silent and self._level <= _LOG_LEVEL_MAP[level] - - def is_debug(self) -> bool: - return self._should_log("debug") - - def is_info(self) -> bool: - return self._should_log("info") - - def is_warn(self) -> bool: - return self._should_log("warn") - - def is_error(self) -> bool: - return self._should_log("error") - - def debug(self, message: str, **kwargs: typing.Any) -> None: - if self.is_debug(): - self._logger.debug(message, **kwargs) - - def info(self, message: str, **kwargs: typing.Any) -> None: - if self.is_info(): - self._logger.info(message, **kwargs) - - def warn(self, message: str, **kwargs: typing.Any) -> None: - if self.is_warn(): - self._logger.warn(message, **kwargs) - - def error(self, message: str, **kwargs: typing.Any) -> None: - if self.is_error(): - self._logger.error(message, **kwargs) - - -_default_logger: Logger = Logger(level="info", logger=ConsoleLogger(), silent=True) - - -def create_logger(config: typing.Optional[typing.Union[LogConfig, Logger]] = None) -> Logger: - if config is None: - return _default_logger - if isinstance(config, Logger): - return config - return Logger( - level=config.get("level", "info"), - logger=config.get("logger", ConsoleLogger()), - silent=config.get("silent", True), - ) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py deleted file mode 100644 index 4527c6a8adec..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/parse_error.py +++ /dev/null @@ -1,36 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Optional - - -class ParsingError(Exception): - """ - Raised when the SDK fails to parse/validate a response from the server. - This typically indicates that the server returned a response whose shape - does not match the expected schema. - """ - - headers: Optional[Dict[str, str]] - status_code: Optional[int] - body: Any - cause: Optional[Exception] - - def __init__( - self, - *, - headers: Optional[Dict[str, str]] = None, - status_code: Optional[int] = None, - body: Any = None, - cause: Optional[Exception] = None, - ) -> None: - self.headers = headers - self.status_code = status_code - self.body = body - self.cause = cause - super().__init__() - if cause is not None: - self.__cause__ = cause - - def __str__(self) -> str: - cause_str = f", cause: {self.cause}" if self.cause is not None else "" - return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py deleted file mode 100644 index fea3a08d3268..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/pydantic_utilities.py +++ /dev/null @@ -1,634 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# nopycln: file -import datetime as dt -import inspect -import json -import logging -from collections import defaultdict -from dataclasses import asdict -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - List, - Mapping, - Optional, - Set, - Tuple, - Type, - TypeVar, - Union, - cast, -) - -import pydantic -import typing_extensions -from pydantic.fields import FieldInfo as _FieldInfo - -_logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - from .http_sse._models import ServerSentEvent - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - -if IS_PYDANTIC_V2: - _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] - _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] - - def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value - return _datetime_adapter.validate_python(value) - - def parse_date(value: Any) -> dt.date: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value.date() - if isinstance(value, dt.date): - return value - return _date_adapter.validate_python(value) - - # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. - from typing import get_args as get_args # type: ignore[assignment] - from typing import get_origin as get_origin # type: ignore[assignment] - - def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return typing_extensions.get_origin(tp) is typing_extensions.Literal - - def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] - - # Inline encoders_by_type to avoid importing from pydantic.v1.json - import re as _re - from collections import deque as _deque - from decimal import Decimal as _Decimal - from enum import Enum as _Enum - from ipaddress import ( - IPv4Address as _IPv4Address, - ) - from ipaddress import ( - IPv4Interface as _IPv4Interface, - ) - from ipaddress import ( - IPv4Network as _IPv4Network, - ) - from ipaddress import ( - IPv6Address as _IPv6Address, - ) - from ipaddress import ( - IPv6Interface as _IPv6Interface, - ) - from ipaddress import ( - IPv6Network as _IPv6Network, - ) - from pathlib import Path as _Path - from types import GeneratorType as _GeneratorType - from uuid import UUID as _UUID - - from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] - - def _decimal_encoder(dec_value: Any) -> Any: - if dec_value.as_tuple().exponent >= 0: - return int(dec_value) - return float(dec_value) - - encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] - bytes: lambda o: o.decode(), - dt.date: lambda o: o.isoformat(), - dt.datetime: lambda o: o.isoformat(), - dt.time: lambda o: o.isoformat(), - dt.timedelta: lambda td: td.total_seconds(), - _Decimal: _decimal_encoder, - _Enum: lambda o: o.value, - frozenset: list, - _deque: list, - _GeneratorType: list, - _IPv4Address: str, - _IPv4Interface: str, - _IPv4Network: str, - _IPv6Address: str, - _IPv6Interface: str, - _IPv6Network: str, - _Path: str, - _re.Pattern: lambda o: o.pattern, - set: list, - _UUID: str, - } -else: - from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] - from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] - from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] - from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] - from pydantic.typing import get_args as get_args # type: ignore[no-redef] - from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] - from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] - from pydantic.typing import is_union as is_union # type: ignore[no-redef] - -from .datetime_utils import serialize_datetime -from .serialization import convert_and_respect_annotation_metadata -from typing_extensions import TypeAlias - -T = TypeVar("T") -Model = TypeVar("Model", bound=pydantic.BaseModel) - - -def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: - """ - Extract the discriminator field name and union variants from a discriminated union type. - Supports Annotated[Union[...], Field(discriminator=...)] patterns. - Returns (discriminator, variants) or (None, None) if not a discriminated union. - """ - origin = typing_extensions.get_origin(type_) - - if origin is typing_extensions.Annotated: - args = typing_extensions.get_args(type_) - if len(args) >= 2: - inner_type = args[0] - # Check annotations for discriminator - discriminator = None - for annotation in args[1:]: - if hasattr(annotation, "discriminator"): - discriminator = getattr(annotation, "discriminator", None) - break - - if discriminator: - inner_origin = typing_extensions.get_origin(inner_type) - if inner_origin is Union: - variants = list(typing_extensions.get_args(inner_type)) - return discriminator, variants - return None, None - - -def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: - """Get the type annotation of a field from a Pydantic model.""" - if IS_PYDANTIC_V2: - fields = getattr(model, "model_fields", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.annotation) - else: - fields = getattr(model, "__fields__", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.outer_type_) - return None - - -def _find_variant_by_discriminator( - variants: List[Type[Any]], - discriminator: str, - discriminator_value: Any, -) -> Optional[Type[Any]]: - """Find the union variant that matches the discriminator value.""" - for variant in variants: - if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): - continue - - disc_annotation = _get_field_annotation(variant, discriminator) - if disc_annotation and is_literal_type(disc_annotation): - literal_args = get_args(disc_annotation) - if literal_args and literal_args[0] == discriminator_value: - return variant - return None - - -def _is_string_type(type_: Type[Any]) -> bool: - """Check if a type is str or Optional[str].""" - if type_ is str: - return True - - origin = typing_extensions.get_origin(type_) - if origin is Union: - args = typing_extensions.get_args(type_) - # Optional[str] = Union[str, None] - non_none_args = [a for a in args if a is not type(None)] - if len(non_none_args) == 1 and non_none_args[0] is str: - return True - - return False - - -def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: - """ - Parse a ServerSentEvent into the appropriate type. - - Handles two scenarios based on where the discriminator field is located: - - 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. - The union describes the data content, not the SSE envelope. - -> Returns: json.loads(data) parsed into the type - - Example: ChatStreamResponse with discriminator='type' - Input: ServerSentEvent(event="message", data='{"type": "content-delta", ...}', id="") - Output: ContentDeltaEvent (parsed from data, SSE envelope stripped) - - 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. - The union describes the full SSE event structure. - -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string - - Example: JobStreamResponse with discriminator='event' - Input: ServerSentEvent(event="ERROR", data='{"code": "FAILED", ...}', id="123") - Output: JobStreamResponse_Error with data as ErrorData object - - But for variants where data is str (like STATUS_UPDATE): - Input: ServerSentEvent(event="STATUS_UPDATE", data='{"status": "processing"}', id="1") - Output: JobStreamResponse_StatusUpdate with data as string (not parsed) - - Args: - sse: The ServerSentEvent object to parse - type_: The target discriminated union type - - Returns: - The parsed object of type T - - Note: - This function is only available in SDK contexts where http_sse module exists. - """ - sse_event = asdict(sse) - discriminator, variants = _get_discriminator_and_variants(type_) - - if discriminator is None or variants is None: - # Not a discriminated union - parse the data field as JSON - data_value = sse_event.get("data") - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - data_value = sse_event.get("data") - - # Check if discriminator is at the top level (event-level discrimination) - if discriminator in sse_event: - # Case 2: Event-level discrimination - # Find the matching variant to check if 'data' field needs JSON parsing - disc_value = sse_event.get(discriminator) - matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) - - if matching_variant is not None: - # Check what type the variant expects for 'data' - data_type = _get_field_annotation(matching_variant, "data") - if data_type is not None and not _is_string_type(data_type): - # Variant expects non-string data - parse JSON - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - new_object = dict(sse_event) - new_object["data"] = parsed_data - return parse_obj_as(type_, new_object) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - # Either no matching variant, data is string type, or JSON parse failed - return parse_obj_as(type_, sse_event) - - else: - # Case 1: Data-level discrimination - # The discriminator is inside the data payload - extract and parse data only - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - -def parse_obj_as(type_: Type[T], object_: Any) -> T: - # convert_and_respect_annotation_metadata is required for TypedDict aliasing. - # - # For Pydantic models, whether we should pre-dealias depends on how the model encodes aliasing: - # - If the model uses real Pydantic aliases (pydantic.Field(alias=...)), then we must pass wire keys through - # unchanged so Pydantic can validate them. - # - If the model encodes aliasing only via FieldMetadata annotations, then we MUST pre-dealias because Pydantic - # will not recognize those aliases during validation. - if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): - has_pydantic_aliases = False - if IS_PYDANTIC_V2: - for field_name, field_info in getattr(type_, "model_fields", {}).items(): # type: ignore[attr-defined] - alias = getattr(field_info, "alias", None) - if alias is not None and alias != field_name: - has_pydantic_aliases = True - break - else: - for field in getattr(type_, "__fields__", {}).values(): - alias = getattr(field, "alias", None) - name = getattr(field, "name", None) - if alias is not None and name is not None and alias != name: - has_pydantic_aliases = True - break - - dealiased_object = ( - object_ - if has_pydantic_aliases - else convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") - ) - else: - dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") - if IS_PYDANTIC_V2: - adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] - return adapter.validate_python(dealiased_object) - return pydantic.parse_obj_as(type_, dealiased_object) - - -def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: - if IS_PYDANTIC_V2: - from pydantic_core import to_jsonable_python - - return to_jsonable_python(obj, fallback=fallback_serializer) - return fallback_serializer(obj) - - -class UniversalBaseModel(pydantic.BaseModel): - if IS_PYDANTIC_V2: - model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] - # Allow fields beginning with `model_` to be used in the model - protected_namespaces=(), - ) - - @pydantic.model_validator(mode="before") # type: ignore[attr-defined] - @classmethod - def _coerce_field_names_to_aliases(cls, data: Any) -> Any: - """ - Accept Python field names in input by rewriting them to their Pydantic aliases, - while avoiding silent collisions when a key could refer to multiple fields. - """ - if not isinstance(data, Mapping): - return data - - fields = getattr(cls, "model_fields", {}) # type: ignore[attr-defined] - name_to_alias: Dict[str, str] = {} - alias_to_name: Dict[str, str] = {} - - for name, field_info in fields.items(): - alias = getattr(field_info, "alias", None) or name - name_to_alias[name] = alias - if alias != name: - alias_to_name[alias] = name - - # Detect ambiguous keys: a key that is an alias for one field and a name for another. - ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) - for key in ambiguous_keys: - if key in data and name_to_alias[key] not in data: - raise ValueError( - f"Ambiguous input key '{key}': it is both a field name and an alias. " - "Provide the explicit alias key to disambiguate." - ) - - original_keys = set(data.keys()) - rewritten: Dict[str, Any] = dict(data) - for name, alias in name_to_alias.items(): - if alias != name and name in original_keys and alias not in rewritten: - rewritten[alias] = rewritten.pop(name) - - return rewritten - - @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] - def serialize_model(self) -> Any: # type: ignore[name-defined] - serialized = self.dict() # type: ignore[attr-defined] - data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} - return data - - else: - - class Config: - smart_union = True - json_encoders = {dt.datetime: serialize_datetime} - - @pydantic.root_validator(pre=True) - def _coerce_field_names_to_aliases(cls, values: Any) -> Any: - """ - Pydantic v1 equivalent of _coerce_field_names_to_aliases. - """ - if not isinstance(values, Mapping): - return values - - fields = getattr(cls, "__fields__", {}) - name_to_alias: Dict[str, str] = {} - alias_to_name: Dict[str, str] = {} - - for name, field in fields.items(): - alias = getattr(field, "alias", None) or name - name_to_alias[name] = alias - if alias != name: - alias_to_name[alias] = name - - ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) - for key in ambiguous_keys: - if key in values and name_to_alias[key] not in values: - raise ValueError( - f"Ambiguous input key '{key}': it is both a field name and an alias. " - "Provide the explicit alias key to disambiguate." - ) - - original_keys = set(values.keys()) - rewritten: Dict[str, Any] = dict(values) - for name, alias in name_to_alias.items(): - if alias != name and name in original_keys and alias not in rewritten: - rewritten[alias] = rewritten.pop(name) - - return rewritten - - @classmethod - def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": - dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") - return cls.construct(_fields_set, **dealiased_object) - - @classmethod - def construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": - dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") - if IS_PYDANTIC_V2: - return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc] - return super().construct(_fields_set, **dealiased_object) - - def json(self, **kwargs: Any) -> str: - kwargs_with_defaults = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - if IS_PYDANTIC_V2: - return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: Any) -> Dict[str, Any]: - """ - Override the default dict method to `exclude_unset` by default. This function patches - `exclude_unset` to work include fields within non-None default values. - """ - # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 - # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. - # - # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models - # that we have less control over, and this is less intrusive than custom serializers for now. - if IS_PYDANTIC_V2: - kwargs_with_defaults_exclude_unset = { - **kwargs, - "by_alias": True, - "exclude_unset": True, - "exclude_none": False, - } - kwargs_with_defaults_exclude_none = { - **kwargs, - "by_alias": True, - "exclude_none": True, - "exclude_unset": False, - } - dict_dump = deep_union_pydantic_dicts( - super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] - super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] - ) - - else: - _fields_set = self.__fields_set__.copy() - - fields = _get_model_fields(self.__class__) - for name, field in fields.items(): - if name not in _fields_set: - default = _get_field_default(field) - - # If the default values are non-null act like they've been set - # This effectively allows exclude_unset to work like exclude_none where - # the latter passes through intentionally set none values. - if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): - _fields_set.add(name) - - if default is not None: - self.__fields_set__.add(name) - - kwargs_with_defaults_exclude_unset_include_fields = { - "by_alias": True, - "exclude_unset": True, - "include": _fields_set, - **kwargs, - } - - dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) - - return cast( - Dict[str, Any], - convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write"), - ) - - -def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: - converted_list: List[Any] = [] - for i, item in enumerate(source): - destination_value = destination[i] - if isinstance(item, dict): - converted_list.append(deep_union_pydantic_dicts(item, destination_value)) - elif isinstance(item, list): - converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) - else: - converted_list.append(item) - return converted_list - - -def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: - for key, value in source.items(): - node = destination.setdefault(key, {}) - if isinstance(value, dict): - deep_union_pydantic_dicts(value, node) - # Note: we do not do this same processing for sets given we do not have sets of models - # and given the sets are unordered, the processing of the set and matching objects would - # be non-trivial. - elif isinstance(value, list): - destination[key] = _union_list_of_pydantic_dicts(value, node) - else: - destination[key] = value - - return destination - - -if IS_PYDANTIC_V2: - - class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg] - pass - - UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc] -else: - UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] - - -def encode_by_type(o: Any) -> Any: - encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) - for type_, encoder in encoders_by_type.items(): - encoders_by_class_tuples[encoder] += (type_,) - - if type(o) in encoders_by_type: - return encoders_by_type[type(o)](o) - for encoder, classes_tuple in encoders_by_class_tuples.items(): - if isinstance(o, classes_tuple): - return encoder(o) - - -def update_forward_refs(model: Type["Model"], **localns: Any) -> None: - if IS_PYDANTIC_V2: - model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] - else: - model.update_forward_refs(**localns) - - -# Mirrors Pydantic's internal typing -AnyCallable = Callable[..., Any] - - -def universal_root_validator( - pre: bool = False, -) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - # In Pydantic v2, for RootModel we always use "before" mode - # The custom validators transform the input value before the model is created - return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] - - return decorator - - -def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) - - return decorator - - -PydanticField = Union[ModelField, _FieldInfo] - - -def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: - if IS_PYDANTIC_V2: - return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] - return cast(Mapping[str, PydanticField], model.__fields__) - - -def _get_field_default(field: PydanticField) -> Any: - try: - value = field.get_default() # type: ignore[union-attr] - except: - value = field.default - if IS_PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None - return value - return value diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py deleted file mode 100644 index 3183001d4046..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/query_encoder.py +++ /dev/null @@ -1,58 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, List, Optional, Tuple - -import pydantic - - -# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict -def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: - result = [] - for k, v in dict_flat.items(): - key = f"{key_prefix}[{k}]" if key_prefix is not None else k - if isinstance(v, dict): - result.extend(traverse_query_dict(v, key)) - elif isinstance(v, list): - for arr_v in v: - if isinstance(arr_v, dict): - result.extend(traverse_query_dict(arr_v, key)) - else: - result.append((key, arr_v)) - else: - result.append((key, v)) - return result - - -def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: - if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): - if isinstance(query_value, pydantic.BaseModel): - obj_dict = query_value.dict(by_alias=True) - else: - obj_dict = query_value - return traverse_query_dict(obj_dict, query_key) - elif isinstance(query_value, list): - encoded_values: List[Tuple[str, Any]] = [] - for value in query_value: - if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): - if isinstance(value, pydantic.BaseModel): - obj_dict = value.dict(by_alias=True) - elif isinstance(value, dict): - obj_dict = value - - encoded_values.extend(single_query_encoder(query_key, obj_dict)) - else: - encoded_values.append((query_key, value)) - - return encoded_values - - return [(query_key, query_value)] - - -def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: - if query is None: - return None - - encoded_query = [] - for k, v in query.items(): - encoded_query.extend(single_query_encoder(k, v)) - return encoded_query diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py deleted file mode 100644 index c2298143f14a..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/remove_none_from_dict.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Mapping, Optional - - -def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: - new: Dict[str, Any] = {} - for key, value in original.items(): - if value is not None: - new[key] = value - return new diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py deleted file mode 100644 index 1b38804432ba..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/request_options.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -try: - from typing import NotRequired # type: ignore -except ImportError: - from typing_extensions import NotRequired - - -class RequestOptions(typing.TypedDict, total=False): - """ - Additional options for request-specific configuration when calling APIs via the SDK. - This is used primarily as an optional final parameter for service functions. - - Attributes: - - timeout_in_seconds: int. The number of seconds to await an API call before timing out. - - - max_retries: int. The max number of retries to attempt if the API call fails. - - - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict - - - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict - - - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict - - - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. - """ - - timeout_in_seconds: NotRequired[int] - max_retries: NotRequired[int] - additional_headers: NotRequired[typing.Dict[str, typing.Any]] - additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] - additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] - chunk_size: NotRequired[int] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py deleted file mode 100644 index c36e865cc729..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/serialization.py +++ /dev/null @@ -1,276 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import collections -import inspect -import typing - -import pydantic -import typing_extensions - - -class FieldMetadata: - """ - Metadata class used to annotate fields to provide additional information. - - Example: - class MyDict(TypedDict): - field: typing.Annotated[str, FieldMetadata(alias="field_name")] - - Will serialize: `{"field": "value"}` - To: `{"field_name": "value"}` - """ - - alias: str - - def __init__(self, *, alias: str) -> None: - self.alias = alias - - -def convert_and_respect_annotation_metadata( - *, - object_: typing.Any, - annotation: typing.Any, - inner_type: typing.Optional[typing.Any] = None, - direction: typing.Literal["read", "write"], -) -> typing.Any: - """ - Respect the metadata annotations on a field, such as aliasing. This function effectively - manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for - TypedDicts, which cannot support aliasing out of the box, and can be extended for additional - utilities, such as defaults. - - Parameters - ---------- - object_ : typing.Any - - annotation : type - The type we're looking to apply typing annotations from - - inner_type : typing.Optional[type] - - Returns - ------- - typing.Any - """ - - if object_ is None: - return None - if inner_type is None: - inner_type = annotation - - clean_type = _remove_annotations(inner_type) - # Pydantic models - if ( - inspect.isclass(clean_type) - and issubclass(clean_type, pydantic.BaseModel) - and isinstance(object_, typing.Mapping) - ): - return _convert_mapping(object_, clean_type, direction) - # TypedDicts - if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): - return _convert_mapping(object_, clean_type, direction) - - if ( - typing_extensions.get_origin(clean_type) == typing.Dict - or typing_extensions.get_origin(clean_type) == dict - or clean_type == typing.Dict - ) and isinstance(object_, typing.Dict): - key_type = typing_extensions.get_args(clean_type)[0] - value_type = typing_extensions.get_args(clean_type)[1] - - return { - key: convert_and_respect_annotation_metadata( - object_=value, - annotation=annotation, - inner_type=value_type, - direction=direction, - ) - for key, value in object_.items() - } - - # If you're iterating on a string, do not bother to coerce it to a sequence. - if not isinstance(object_, str): - if ( - typing_extensions.get_origin(clean_type) == typing.Set - or typing_extensions.get_origin(clean_type) == set - or clean_type == typing.Set - ) and isinstance(object_, typing.Set): - inner_type = typing_extensions.get_args(clean_type)[0] - return { - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - } - elif ( - ( - typing_extensions.get_origin(clean_type) == typing.List - or typing_extensions.get_origin(clean_type) == list - or clean_type == typing.List - ) - and isinstance(object_, typing.List) - ) or ( - ( - typing_extensions.get_origin(clean_type) == typing.Sequence - or typing_extensions.get_origin(clean_type) == collections.abc.Sequence - or clean_type == typing.Sequence - ) - and isinstance(object_, typing.Sequence) - ): - inner_type = typing_extensions.get_args(clean_type)[0] - return [ - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - ] - - if typing_extensions.get_origin(clean_type) == typing.Union: - # We should be able to ~relatively~ safely try to convert keys against all - # member types in the union, the edge case here is if one member aliases a field - # of the same name to a different name from another member - # Or if another member aliases a field of the same name that another member does not. - for member in typing_extensions.get_args(clean_type): - object_ = convert_and_respect_annotation_metadata( - object_=object_, - annotation=annotation, - inner_type=member, - direction=direction, - ) - return object_ - - annotated_type = _get_annotation(annotation) - if annotated_type is None: - return object_ - - # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) - # Then we can safely call it on the recursive conversion. - return object_ - - -def _convert_mapping( - object_: typing.Mapping[str, object], - expected_type: typing.Any, - direction: typing.Literal["read", "write"], -) -> typing.Mapping[str, object]: - converted_object: typing.Dict[str, object] = {} - try: - annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) - except NameError: - # The TypedDict contains a circular reference, so - # we use the __annotations__ attribute directly. - annotations = getattr(expected_type, "__annotations__", {}) - aliases_to_field_names = _get_alias_to_field_name(annotations) - for key, value in object_.items(): - if direction == "read" and key in aliases_to_field_names: - dealiased_key = aliases_to_field_names.get(key) - if dealiased_key is not None: - type_ = annotations.get(dealiased_key) - else: - type_ = annotations.get(key) - # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map - # - # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias - # then we can just pass the value through as is - if type_ is None: - converted_object[key] = value - elif direction == "read" and key not in aliases_to_field_names: - converted_object[key] = convert_and_respect_annotation_metadata( - object_=value, annotation=type_, direction=direction - ) - else: - converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( - convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) - ) - return converted_object - - -def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return None - - if maybe_annotated_type == typing_extensions.NotRequired: - type_ = typing_extensions.get_args(type_)[0] - maybe_annotated_type = typing_extensions.get_origin(type_) - - if maybe_annotated_type == typing_extensions.Annotated: - return type_ - - return None - - -def _remove_annotations(type_: typing.Any) -> typing.Any: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return type_ - - if maybe_annotated_type == typing_extensions.NotRequired: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - if maybe_annotated_type == typing_extensions.Annotated: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - return type_ - - -def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_alias_to_field_name(annotations) - - -def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_field_to_alias_name(annotations) - - -def _get_alias_to_field_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[maybe_alias] = field - return aliases - - -def _get_field_to_alias_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[field] = maybe_alias - return aliases - - -def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: - maybe_annotated_type = _get_annotation(type_) - - if maybe_annotated_type is not None: - # The actual annotations are 1 onward, the first is the annotated type - annotations = typing_extensions.get_args(maybe_annotated_type)[1:] - - for annotation in annotations: - if isinstance(annotation, FieldMetadata) and annotation.alias is not None: - return annotation.alias - return None - - -def _alias_key( - key: str, - type_: typing.Any, - direction: typing.Literal["read", "write"], - aliases_to_field_names: typing.Dict[str, str], -) -> str: - if direction == "read": - return aliases_to_field_names.get(key, key) - return _get_alias_from_type(type_=type_) or key diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py deleted file mode 100644 index c64358bdc3c7..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/environment.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import enum - - -class SeedApiEnvironment(enum.Enum): - DEFAULT = "https://api.example.com" diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/py.typed b/seed/python-sdk/allof-inline/no-custom-config/src/seed/py.typed deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py deleted file mode 100644 index 2e2501a1fd92..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/raw_client.py +++ /dev/null @@ -1,451 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from .core.api_error import ApiError -from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .core.http_response import AsyncHttpResponse, HttpResponse -from .core.parse_error import ParsingError -from .core.pydantic_utilities import parse_obj_as -from .core.request_options import RequestOptions -from .types.combined_entity import CombinedEntity -from .types.organization import Organization -from .types.rule_execution_context import RuleExecutionContext -from .types.rule_response import RuleResponse -from .types.rule_type_search_response import RuleTypeSearchResponse -from .types.user_search_response import UserSearchResponse -from pydantic import ValidationError - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class RawSeedApi: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[RuleTypeSearchResponse]: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[RuleTypeSearchResponse] - Paginated list of rule types - """ - _response = self._client_wrapper.httpx_client.request( - "rule-types", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleTypeSearchResponse, - parse_obj_as( - type_=RuleTypeSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[RuleResponse]: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[RuleResponse] - Created rule - """ - _response = self._client_wrapper.httpx_client.request( - "rules", - method="POST", - json={ - "name": name, - "executionContext": execution_context, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleResponse, - parse_obj_as( - type_=RuleResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def list_users( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[UserSearchResponse]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[UserSearchResponse] - Paginated list of users - """ - _response = self._client_wrapper.httpx_client.request( - "users", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - UserSearchResponse, - parse_obj_as( - type_=UserSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[CombinedEntity]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[CombinedEntity] - An entity with properties from multiple parents - """ - _response = self._client_wrapper.httpx_client.request( - "entities", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - CombinedEntity, - parse_obj_as( - type_=CombinedEntity, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def get_organization( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[Organization]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[Organization] - An organization whose metadata is merged from two parents - """ - _response = self._client_wrapper.httpx_client.request( - "organizations", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - Organization, - parse_obj_as( - type_=Organization, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - -class AsyncRawSeedApi: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[RuleTypeSearchResponse]: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[RuleTypeSearchResponse] - Paginated list of rule types - """ - _response = await self._client_wrapper.httpx_client.request( - "rule-types", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleTypeSearchResponse, - parse_obj_as( - type_=RuleTypeSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[RuleResponse]: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[RuleResponse] - Created rule - """ - _response = await self._client_wrapper.httpx_client.request( - "rules", - method="POST", - json={ - "name": name, - "executionContext": execution_context, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleResponse, - parse_obj_as( - type_=RuleResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def list_users( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[UserSearchResponse]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[UserSearchResponse] - Paginated list of users - """ - _response = await self._client_wrapper.httpx_client.request( - "users", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - UserSearchResponse, - parse_obj_as( - type_=UserSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def get_entity( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[CombinedEntity]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[CombinedEntity] - An entity with properties from multiple parents - """ - _response = await self._client_wrapper.httpx_client.request( - "entities", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - CombinedEntity, - parse_obj_as( - type_=CombinedEntity, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def get_organization( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[Organization]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[Organization] - An organization whose metadata is merged from two parents - """ - _response = await self._client_wrapper.httpx_client.request( - "organizations", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - Organization, - parse_obj_as( - type_=Organization, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py deleted file mode 100644 index f0fa2b2add13..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/__init__.py +++ /dev/null @@ -1,95 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from .audit_info import AuditInfo - from .base_org import BaseOrg - from .base_org_metadata import BaseOrgMetadata - from .combined_entity import CombinedEntity - from .combined_entity_status import CombinedEntityStatus - from .describable import Describable - from .detailed_org import DetailedOrg - from .detailed_org_metadata import DetailedOrgMetadata - from .identifiable import Identifiable - from .organization import Organization - from .organization_metadata import OrganizationMetadata - from .paginated_result import PaginatedResult - from .paging_cursors import PagingCursors - from .rule_execution_context import RuleExecutionContext - from .rule_response import RuleResponse - from .rule_response_status import RuleResponseStatus - from .rule_type import RuleType - from .rule_type_search_response import RuleTypeSearchResponse - from .user import User - from .user_search_response import UserSearchResponse -_dynamic_imports: typing.Dict[str, str] = { - "AuditInfo": ".audit_info", - "BaseOrg": ".base_org", - "BaseOrgMetadata": ".base_org_metadata", - "CombinedEntity": ".combined_entity", - "CombinedEntityStatus": ".combined_entity_status", - "Describable": ".describable", - "DetailedOrg": ".detailed_org", - "DetailedOrgMetadata": ".detailed_org_metadata", - "Identifiable": ".identifiable", - "Organization": ".organization", - "OrganizationMetadata": ".organization_metadata", - "PaginatedResult": ".paginated_result", - "PagingCursors": ".paging_cursors", - "RuleExecutionContext": ".rule_execution_context", - "RuleResponse": ".rule_response", - "RuleResponseStatus": ".rule_response_status", - "RuleType": ".rule_type", - "RuleTypeSearchResponse": ".rule_type_search_response", - "User": ".user", - "UserSearchResponse": ".user_search_response", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = [ - "AuditInfo", - "BaseOrg", - "BaseOrgMetadata", - "CombinedEntity", - "CombinedEntityStatus", - "Describable", - "DetailedOrg", - "DetailedOrgMetadata", - "Identifiable", - "Organization", - "OrganizationMetadata", - "PaginatedResult", - "PagingCursors", - "RuleExecutionContext", - "RuleResponse", - "RuleResponseStatus", - "RuleType", - "RuleTypeSearchResponse", - "User", - "UserSearchResponse", -] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py deleted file mode 100644 index 49474bbcdeeb..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/audit_info.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -import pydantic -import typing_extensions -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata - - -class AuditInfo(UniversalBaseModel): - """ - Common audit metadata. - """ - - created_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="createdBy"), - pydantic.Field(alias="createdBy", description="The user who created this resource."), - ] = None - created_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="createdDateTime"), - pydantic.Field(alias="createdDateTime", description="When this resource was created."), - ] = None - modified_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="modifiedBy"), - pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), - ] = None - modified_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="modifiedDateTime"), - pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py deleted file mode 100644 index 9d77974841d4..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .base_org_metadata import BaseOrgMetadata - - -class BaseOrg(UniversalBaseModel): - id: str - metadata: typing.Optional[BaseOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py deleted file mode 100644 index 69449ce2a499..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/base_org_metadata.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class BaseOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from BaseOrg. - """ - - tier: typing.Optional[str] = pydantic.Field(default=None) - """ - Subscription tier. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py deleted file mode 100644 index 0626826b2f98..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .combined_entity_status import CombinedEntityStatus - - -class CombinedEntity(UniversalBaseModel): - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Describable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - status: CombinedEntityStatus - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py deleted file mode 100644 index 42ac60430cd7..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/combined_entity_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py deleted file mode 100644 index 2b6cafc913ee..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/describable.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Describable(UniversalBaseModel): - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Describable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py deleted file mode 100644 index 744634c71d46..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .detailed_org_metadata import DetailedOrgMetadata - - -class DetailedOrg(UniversalBaseModel): - metadata: typing.Optional[DetailedOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py deleted file mode 100644 index f0f2029eb21c..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/detailed_org_metadata.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class DetailedOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from DetailedOrg. - """ - - domain: typing.Optional[str] = pydantic.Field(default=None) - """ - Custom domain name. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py deleted file mode 100644 index b88d9d997229..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/identifiable.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Identifiable(UniversalBaseModel): - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Identifiable. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py deleted file mode 100644 index 6e412e61f110..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization.py +++ /dev/null @@ -1,22 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .organization_metadata import OrganizationMetadata - - -class Organization(UniversalBaseModel): - id: str - metadata: typing.Optional[OrganizationMetadata] = None - name: str - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py deleted file mode 100644 index 7d61623ad2e1..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/organization_metadata.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class OrganizationMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from DetailedOrg. - """ - - domain: typing.Optional[str] = pydantic.Field(default=None) - """ - Custom domain name. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py deleted file mode 100644 index 56a3858ffbde..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paginated_result.py +++ /dev/null @@ -1,24 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors - - -class PaginatedResult(UniversalBaseModel): - paging: PagingCursors - results: typing.List[typing.Any] = pydantic.Field() - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py deleted file mode 100644 index f786d5462fea..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/paging_cursors.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class PagingCursors(UniversalBaseModel): - next: str = pydantic.Field() - """ - Cursor for the next page of results. - """ - - previous: typing.Optional[str] = pydantic.Field(default=None) - """ - Cursor for the previous page of results. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py deleted file mode 100644 index 1d22ed9cabf8..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_execution_context.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py deleted file mode 100644 index 50ff8b6dea99..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response.py +++ /dev/null @@ -1,51 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -import pydantic -import typing_extensions -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata -from .rule_execution_context import RuleExecutionContext -from .rule_response_status import RuleResponseStatus - - -class RuleResponse(UniversalBaseModel): - created_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="createdBy"), - pydantic.Field(alias="createdBy", description="The user who created this resource."), - ] = None - created_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="createdDateTime"), - pydantic.Field(alias="createdDateTime", description="When this resource was created."), - ] = None - modified_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="modifiedBy"), - pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), - ] = None - modified_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="modifiedDateTime"), - pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), - ] = None - id: str - name: str - status: RuleResponseStatus - execution_context: typing_extensions.Annotated[ - typing.Optional[RuleExecutionContext], - FieldMetadata(alias="executionContext"), - pydantic.Field(alias="executionContext"), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py deleted file mode 100644 index 4cbd106638cb..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_response_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py deleted file mode 100644 index a5543e06211c..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class RuleType(UniversalBaseModel): - id: str - name: str - description: typing.Optional[str] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py deleted file mode 100644 index 1e60dbe9ab78..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/rule_type_search_response.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .rule_type import RuleType - - -class RuleTypeSearchResponse(UniversalBaseModel): - paging: PagingCursors - results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py deleted file mode 100644 index 5421e32870d3..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class User(UniversalBaseModel): - id: str - email: str - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py deleted file mode 100644 index 3fbf4eb995d6..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/types/user_search_response.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .user import User - - -class UserSearchResponse(UniversalBaseModel): - paging: PagingCursors - results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py deleted file mode 100644 index 00f3a2eb508a..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/version.py +++ /dev/null @@ -1,3 +0,0 @@ -from importlib import metadata - -__version__ = metadata.version("fern_allof-inline") diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py b/seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py deleted file mode 100644 index 9e586af3b2d4..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/conftest.py +++ /dev/null @@ -1,147 +0,0 @@ -""" -Pytest plugin that manages the WireMock container lifecycle for wire tests. - -This plugin is loaded globally for the test suite and is responsible for -starting and stopping the WireMock container exactly once per test run, -including when running with pytest-xdist over the entire project. - -It lives under tests/ (as tests/conftest.py) and is discovered automatically -by pytest's normal test collection rules. -""" - -import os -import subprocess - -import pytest - -_STARTED: bool = False -_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) -_WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts -_PROJECT_NAME: str = "seed-api" - -# This file lives at tests/conftest.py, so the project root is one level up. -_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -_COMPOSE_FILE = os.path.join(_PROJECT_ROOT, "wiremock", "docker-compose.test.yml") - - -def _get_wiremock_port() -> str: - """Gets the dynamically assigned port for the WireMock container.""" - try: - result = subprocess.run( - ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "port", "wiremock", "8080"], - check=True, - capture_output=True, - text=True, - ) - # Output is like "0.0.0.0:32768" or "[::]:32768" - port = result.stdout.strip().split(":")[-1] - return port - except subprocess.CalledProcessError: - return "8080" # Fallback to default - - -def _start_wiremock() -> None: - """Starts the WireMock container using docker-compose.""" - global _STARTED, _EXTERNAL, _WIREMOCK_URL - if _STARTED: - return - - # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management - existing_url = os.environ.get("WIREMOCK_URL") - if existing_url: - _WIREMOCK_URL = existing_url - _EXTERNAL = True - _STARTED = True - print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") - return - - print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") - try: - subprocess.run( - ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "up", "-d", "--wait"], - check=True, - capture_output=True, - text=True, - ) - _WIREMOCK_PORT = _get_wiremock_port() - _WIREMOCK_URL = f"http://localhost:{_WIREMOCK_PORT}" - os.environ["WIREMOCK_URL"] = _WIREMOCK_URL - print(f"WireMock container is ready at {_WIREMOCK_URL}") - _STARTED = True - except subprocess.CalledProcessError as e: - print(f"Failed to start WireMock: {e.stderr}") - raise - - -def _stop_wiremock() -> None: - """Stops and removes the WireMock container.""" - if _EXTERNAL: - # Container is managed externally; nothing to tear down. - return - - print("\nStopping WireMock container...") - subprocess.run( - ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], - check=False, - capture_output=True, - ) - - -def _is_xdist_worker(config: pytest.Config) -> bool: - """ - Determines if the current process is an xdist worker. - - In pytest-xdist, worker processes have a 'workerinput' attribute - on the config object, while the controller process does not. - """ - return hasattr(config, "workerinput") - - -def _has_httpx_aiohttp() -> bool: - """Check if httpx_aiohttp is importable.""" - try: - import httpx_aiohttp # type: ignore[import-not-found] # noqa: F401 - - return True - except ImportError: - return False - - -def pytest_collection_modifyitems(config: pytest.Config, items: list) -> None: - """Auto-skip @pytest.mark.aiohttp tests when httpx_aiohttp is not installed.""" - if _has_httpx_aiohttp(): - return - skip_aiohttp = pytest.mark.skip(reason="httpx_aiohttp not installed") - for item in items: - if "aiohttp" in item.keywords: - item.add_marker(skip_aiohttp) - - -def pytest_configure(config: pytest.Config) -> None: - """ - Pytest hook that runs during test session setup. - - Starts WireMock container only from the controller process (xdist) - or the single process (non-xdist). This ensures only one container - is started regardless of the number of worker processes. - """ - if _is_xdist_worker(config): - # Workers never manage the container lifecycle. - return - - _start_wiremock() - - -def pytest_unconfigure(config: pytest.Config) -> None: - """ - Pytest hook that runs during test session teardown. - - Stops WireMock container only from the controller process (xdist) - or the single process (non-xdist). This ensures the container is - cleaned up after all workers have finished. - """ - if _is_xdist_worker(config): - # Workers never manage the container lifecycle. - return - - _stop_wiremock() diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py b/seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py deleted file mode 100644 index ab04ce6393ef..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/custom/test_client.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - - -# Get started with writing tests with pytest at https://docs.pytest.org -@pytest.mark.skip(reason="Unimplemented") -def test_client() -> None: - assert True diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py b/seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py deleted file mode 100644 index 2ed3ed776ecf..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/test_aiohttp_autodetect.py +++ /dev/null @@ -1,116 +0,0 @@ -import importlib -import sys -import unittest -from unittest import mock - -import httpx -import pytest - - -class TestMakeDefaultAsyncClientWithoutAiohttp(unittest.TestCase): - """Tests for _make_default_async_client when httpx_aiohttp is NOT installed.""" - - def test_returns_httpx_async_client(self) -> None: - """When httpx_aiohttp is not installed, returns plain httpx.AsyncClient.""" - with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=True) - self.assertIsInstance(client, httpx.AsyncClient) - self.assertEqual(client.timeout.read, 60) - self.assertTrue(client.follow_redirects) - - def test_follow_redirects_none(self) -> None: - """When follow_redirects is None, omits it from httpx.AsyncClient.""" - with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=None) - self.assertIsInstance(client, httpx.AsyncClient) - self.assertFalse(client.follow_redirects) - - def test_explicit_httpx_client_bypasses_autodetect(self) -> None: - """When user passes httpx_client explicitly, _make_default_async_client is not called.""" - - explicit_client = httpx.AsyncClient(timeout=120) - with mock.patch("seed.client._make_default_async_client") as mock_make: - # Replicate the generated conditional: httpx_client if httpx_client is not None else _make_default_async_client(...) - result = explicit_client if explicit_client is not None else mock_make(timeout=60, follow_redirects=True) - mock_make.assert_not_called() - self.assertIs(result, explicit_client) - - -@pytest.mark.aiohttp -class TestMakeDefaultAsyncClientWithAiohttp(unittest.TestCase): - """Tests for _make_default_async_client when httpx_aiohttp IS installed.""" - - def test_returns_aiohttp_client(self) -> None: - """When httpx_aiohttp is installed, returns HttpxAiohttpClient.""" - import httpx_aiohttp # type: ignore[import-not-found] - - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=True) - self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) - self.assertEqual(client.timeout.read, 60) - self.assertTrue(client.follow_redirects) - - def test_follow_redirects_none(self) -> None: - """When httpx_aiohttp is installed and follow_redirects is None, omits it.""" - import httpx_aiohttp # type: ignore[import-not-found] - - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=None) - self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) - self.assertFalse(client.follow_redirects) - - -class TestDefaultClientsWithoutAiohttp(unittest.TestCase): - """Tests for _default_clients.py convenience classes (no aiohttp).""" - - def test_default_async_httpx_client_defaults(self) -> None: - """DefaultAsyncHttpxClient applies SDK defaults.""" - from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAsyncHttpxClient - - client = DefaultAsyncHttpxClient() - self.assertIsInstance(client, httpx.AsyncClient) - self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) - self.assertTrue(client.follow_redirects) - - def test_default_async_httpx_client_overrides(self) -> None: - """DefaultAsyncHttpxClient allows overriding defaults.""" - from seed._default_clients import DefaultAsyncHttpxClient - - client = DefaultAsyncHttpxClient(timeout=30, follow_redirects=False) - self.assertEqual(client.timeout.read, 30) - self.assertFalse(client.follow_redirects) - - def test_default_aiohttp_client_raises_without_package(self) -> None: - """DefaultAioHttpClient raises RuntimeError when httpx_aiohttp not installed.""" - import seed._default_clients - - with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): - importlib.reload(seed._default_clients) - - with self.assertRaises(RuntimeError) as ctx: - seed._default_clients.DefaultAioHttpClient() - self.assertIn("pip install fern_allof-inline[aiohttp]", str(ctx.exception)) - - importlib.reload(seed._default_clients) - - -@pytest.mark.aiohttp -class TestDefaultClientsWithAiohttp(unittest.TestCase): - """Tests for _default_clients.py when httpx_aiohttp IS installed.""" - - def test_default_aiohttp_client_defaults(self) -> None: - """DefaultAioHttpClient works when httpx_aiohttp is installed.""" - import httpx_aiohttp # type: ignore[import-not-found] - - from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAioHttpClient - - client = DefaultAioHttpClient() - self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) - self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) - self.assertTrue(client.follow_redirects) diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py deleted file mode 100644 index f3ea2659bb1c..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py deleted file mode 100644 index 2cf01263529d..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -from .circle import CircleParams -from .object_with_defaults import ObjectWithDefaultsParams -from .object_with_optional_field import ObjectWithOptionalFieldParams -from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams -from .square import SquareParams -from .undiscriminated_shape import UndiscriminatedShapeParams - -__all__ = [ - "CircleParams", - "ObjectWithDefaultsParams", - "ObjectWithOptionalFieldParams", - "ShapeParams", - "Shape_CircleParams", - "Shape_SquareParams", - "SquareParams", - "UndiscriminatedShapeParams", -] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py deleted file mode 100644 index 74ecf38c308b..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/circle.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing_extensions - -from seed.core.serialization import FieldMetadata - - -class CircleParams(typing_extensions.TypedDict): - radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py deleted file mode 100644 index 2aa2c4c52f0c..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/color.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing - -Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py deleted file mode 100644 index a977b1d2aa1c..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_defaults.py +++ /dev/null @@ -1,15 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing_extensions - - -class ObjectWithDefaultsParams(typing_extensions.TypedDict): - """ - Defines properties with default values and validation rules. - """ - - decimal: typing_extensions.NotRequired[float] - string: typing_extensions.NotRequired[str] - required_string: str diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py deleted file mode 100644 index 6b5608bc05b6..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/object_with_optional_field.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing -import uuid - -import typing_extensions -from .color import Color -from .shape import ShapeParams -from .undiscriminated_shape import UndiscriminatedShapeParams - -from seed.core.serialization import FieldMetadata - - -class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): - literal: typing.Literal["lit_one"] - string: typing_extensions.NotRequired[str] - integer: typing_extensions.NotRequired[int] - long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] - double: typing_extensions.NotRequired[float] - bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] - datetime: typing_extensions.NotRequired[dt.datetime] - date: typing_extensions.NotRequired[dt.date] - uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] - base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] - list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] - set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] - map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] - enum: typing_extensions.NotRequired[Color] - union: typing_extensions.NotRequired[ShapeParams] - second_union: typing_extensions.NotRequired[ShapeParams] - undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] - any: typing.Optional[typing.Any] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py deleted file mode 100644 index 7e70010a251f..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/shape.py +++ /dev/null @@ -1,28 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import typing - -import typing_extensions - -from seed.core.serialization import FieldMetadata - - -class Base(typing_extensions.TypedDict): - id: str - - -class Shape_CircleParams(Base): - shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] - radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] - - -class Shape_SquareParams(Base): - shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] - length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] - - -ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py deleted file mode 100644 index 71c7d25fd4ad..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/square.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing_extensions - -from seed.core.serialization import FieldMetadata - - -class SquareParams(typing_extensions.TypedDict): - length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py deleted file mode 100644 index 99f12b300d1d..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing - -from .circle import CircleParams -from .square import SquareParams - -UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py deleted file mode 100644 index aa2a8b4e4700..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_http_client.py +++ /dev/null @@ -1,662 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict -from unittest.mock import AsyncMock, MagicMock, patch - -import httpx -import pytest - -from seed.core.http_client import ( - AsyncHttpClient, - HttpClient, - _build_url, - get_request_body, - remove_none_from_dict, -) -from seed.core.request_options import RequestOptions - - -# Stub clients for testing HttpClient and AsyncHttpClient -class _DummySyncClient: - """A minimal stub for httpx.Client that records request arguments.""" - - def __init__(self) -> None: - self.last_request_kwargs: Dict[str, Any] = {} - - def request(self, **kwargs: Any) -> "_DummyResponse": - self.last_request_kwargs = kwargs - return _DummyResponse() - - -class _DummyAsyncClient: - """A minimal stub for httpx.AsyncClient that records request arguments.""" - - def __init__(self) -> None: - self.last_request_kwargs: Dict[str, Any] = {} - - async def request(self, **kwargs: Any) -> "_DummyResponse": - self.last_request_kwargs = kwargs - return _DummyResponse() - - -class _DummyResponse: - """A minimal stub for httpx.Response.""" - - status_code = 200 - headers: Dict[str, str] = {} - - -def get_request_options() -> RequestOptions: - return {"additional_body_parameters": {"see you": "later"}} - - -def get_request_options_with_none() -> RequestOptions: - return {"additional_body_parameters": {"see you": "later", "optional": None}} - - -def test_get_json_request_body() -> None: - json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) - assert json_body == {"hello": "world"} - assert data_body is None - - json_body_extras, data_body_extras = get_request_body( - json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None - ) - - assert json_body_extras == {"goodbye": "world", "see you": "later"} - assert data_body_extras is None - - -def test_get_files_request_body() -> None: - json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) - assert data_body == {"hello": "world"} - assert json_body is None - - json_body_extras, data_body_extras = get_request_body( - json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None - ) - - assert data_body_extras == {"goodbye": "world", "see you": "later"} - assert json_body_extras is None - - -def test_get_none_request_body() -> None: - json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) - assert data_body is None - assert json_body is None - - json_body_extras, data_body_extras = get_request_body( - json=None, data=None, request_options=get_request_options(), omit=None - ) - - assert json_body_extras == {"see you": "later"} - assert data_body_extras is None - - -def test_get_empty_json_request_body() -> None: - """Test that implicit empty bodies (json=None) are collapsed to None.""" - unrelated_request_options: RequestOptions = {"max_retries": 3} - json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) - assert json_body is None - assert data_body is None - - -def test_explicit_empty_json_body_is_preserved() -> None: - """Test that explicit empty bodies (json={}) are preserved and sent as {}. - - This is important for endpoints where the request body is required but all - fields are optional. The server expects valid JSON ({}) not an empty body. - """ - unrelated_request_options: RequestOptions = {"max_retries": 3} - - # Explicit json={} should be preserved - json_body, data_body = get_request_body(json={}, data=None, request_options=unrelated_request_options, omit=None) - assert json_body == {} - assert data_body is None - - # Explicit data={} should also be preserved - json_body2, data_body2 = get_request_body(json=None, data={}, request_options=unrelated_request_options, omit=None) - assert json_body2 is None - assert data_body2 == {} - - -def test_json_body_preserves_none_values() -> None: - """Test that JSON bodies preserve None values (they become JSON null).""" - json_body, data_body = get_request_body( - json={"hello": "world", "optional": None}, data=None, request_options=None, omit=None - ) - # JSON bodies should preserve None values - assert json_body == {"hello": "world", "optional": None} - assert data_body is None - - -def test_data_body_preserves_none_values_without_multipart() -> None: - """Test that data bodies preserve None values when not using multipart. - - The filtering of None values happens in HttpClient.request/stream methods, - not in get_request_body. This test verifies get_request_body doesn't filter None. - """ - json_body, data_body = get_request_body( - json=None, data={"hello": "world", "optional": None}, request_options=None, omit=None - ) - # get_request_body should preserve None values in data body - # The filtering happens later in HttpClient.request when multipart is detected - assert data_body == {"hello": "world", "optional": None} - assert json_body is None - - -def test_remove_none_from_dict_filters_none_values() -> None: - """Test that remove_none_from_dict correctly filters out None values.""" - original = {"hello": "world", "optional": None, "another": "value", "also_none": None} - filtered = remove_none_from_dict(original) - assert filtered == {"hello": "world", "another": "value"} - # Original should not be modified - assert original == {"hello": "world", "optional": None, "another": "value", "also_none": None} - - -def test_remove_none_from_dict_empty_dict() -> None: - """Test that remove_none_from_dict handles empty dict.""" - assert remove_none_from_dict({}) == {} - - -def test_remove_none_from_dict_all_none() -> None: - """Test that remove_none_from_dict handles dict with all None values.""" - assert remove_none_from_dict({"a": None, "b": None}) == {} - - -def test_http_client_does_not_pass_empty_params_list() -> None: - """Test that HttpClient passes params=None when params are empty. - - This prevents httpx from stripping existing query parameters from the URL, - which happens when params=[] or params={} is passed. - """ - dummy_client = _DummySyncClient() - http_client = HttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - ) - - # Use a path with query params (e.g., pagination cursor URL) - http_client.request( - path="resource?after=123", - method="GET", - params=None, - request_options=None, - ) - - # We care that httpx receives params=None, not [] or {} - assert "params" in dummy_client.last_request_kwargs - assert dummy_client.last_request_kwargs["params"] is None - - # Verify the query string in the URL is preserved - url = str(dummy_client.last_request_kwargs["url"]) - assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" - - -def test_http_client_passes_encoded_params_when_present() -> None: - """Test that HttpClient passes encoded params when params are provided.""" - dummy_client = _DummySyncClient() - http_client = HttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com/resource", - ) - - http_client.request( - path="", - method="GET", - params={"after": "456"}, - request_options=None, - ) - - params = dummy_client.last_request_kwargs["params"] - # For a simple dict, encode_query should give a single (key, value) tuple - assert params == [("after", "456")] - - -@pytest.mark.asyncio -async def test_async_http_client_does_not_pass_empty_params_list() -> None: - """Test that AsyncHttpClient passes params=None when params are empty. - - This prevents httpx from stripping existing query parameters from the URL, - which happens when params=[] or params={} is passed. - """ - dummy_client = _DummyAsyncClient() - http_client = AsyncHttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - async_base_headers=None, - ) - - # Use a path with query params (e.g., pagination cursor URL) - await http_client.request( - path="resource?after=123", - method="GET", - params=None, - request_options=None, - ) - - # We care that httpx receives params=None, not [] or {} - assert "params" in dummy_client.last_request_kwargs - assert dummy_client.last_request_kwargs["params"] is None - - # Verify the query string in the URL is preserved - url = str(dummy_client.last_request_kwargs["url"]) - assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" - - -@pytest.mark.asyncio -async def test_async_http_client_passes_encoded_params_when_present() -> None: - """Test that AsyncHttpClient passes encoded params when params are provided.""" - dummy_client = _DummyAsyncClient() - http_client = AsyncHttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com/resource", - async_base_headers=None, - ) - - await http_client.request( - path="", - method="GET", - params={"after": "456"}, - request_options=None, - ) - - params = dummy_client.last_request_kwargs["params"] - # For a simple dict, encode_query should give a single (key, value) tuple - assert params == [("after", "456")] - - -def test_basic_url_joining() -> None: - """Test basic URL joining with a simple base URL and path.""" - result = _build_url("https://api.example.com", "/users") - assert result == "https://api.example.com/users" - - -def test_basic_url_joining_trailing_slash() -> None: - """Test basic URL joining with a simple base URL and path.""" - result = _build_url("https://api.example.com/", "/users") - assert result == "https://api.example.com/users" - - -def test_preserves_base_url_path_prefix() -> None: - """Test that path prefixes in base URL are preserved. - - This is the critical bug fix - urllib.parse.urljoin() would strip - the path prefix when the path starts with '/'. - """ - result = _build_url("https://cloud.example.com/org/tenant/api", "/users") - assert result == "https://cloud.example.com/org/tenant/api/users" - - -def test_preserves_base_url_path_prefix_trailing_slash() -> None: - """Test that path prefixes in base URL are preserved.""" - result = _build_url("https://cloud.example.com/org/tenant/api/", "/users") - assert result == "https://cloud.example.com/org/tenant/api/users" - - -# --------------------------------------------------------------------------- -# Connection error retry tests -# --------------------------------------------------------------------------- - - -def _make_sync_http_client(mock_client: Any) -> HttpClient: - return HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - ) - - -def _make_async_http_client(mock_client: Any) -> AsyncHttpClient: - return AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - async_base_headers=None, - ) - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_retries_on_connect_error(mock_sleep: MagicMock) -> None: - """Sync: connection error retries on httpx.ConnectError.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - http_client = _make_sync_http_client(mock_client) - - response = http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_retries_on_remote_protocol_error(mock_sleep: MagicMock) -> None: - """Sync: connection error retries on httpx.RemoteProtocolError.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.RemoteProtocolError("Remote end closed connection without response"), - _DummyResponse(), - ] - http_client = _make_sync_http_client(mock_client) - - response = http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_connection_error_exhausts_retries(mock_sleep: MagicMock) -> None: - """Sync: connection error exhausts retries then raises.""" - mock_client = MagicMock() - mock_client.request.side_effect = httpx.ConnectError("connection failed") - http_client = _make_sync_http_client(mock_client) - - with pytest.raises(httpx.ConnectError): - http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - assert mock_sleep.call_count == 2 - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_connection_error_respects_max_retries_zero(mock_sleep: MagicMock) -> None: - """Sync: connection error respects max_retries=0.""" - mock_client = MagicMock() - mock_client.request.side_effect = httpx.ConnectError("connection failed") - http_client = _make_sync_http_client(mock_client) - - with pytest.raises(httpx.ConnectError): - http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 0}, - ) - - # No retries, just the initial attempt - assert mock_client.request.call_count == 1 - mock_sleep.assert_not_called() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_retries_on_connect_error(mock_sleep: AsyncMock) -> None: - """Async: connection error retries on httpx.ConnectError.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - ) - http_client = _make_async_http_client(mock_client) - - response = await http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_retries_on_remote_protocol_error(mock_sleep: AsyncMock) -> None: - """Async: connection error retries on httpx.RemoteProtocolError.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.RemoteProtocolError("Remote end closed connection without response"), - _DummyResponse(), - ] - ) - http_client = _make_async_http_client(mock_client) - - response = await http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_connection_error_exhausts_retries(mock_sleep: AsyncMock) -> None: - """Async: connection error exhausts retries then raises.""" - mock_client = MagicMock() - mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) - http_client = _make_async_http_client(mock_client) - - with pytest.raises(httpx.ConnectError): - await http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - assert mock_sleep.call_count == 2 - - -# --------------------------------------------------------------------------- -# base_max_retries constructor parameter tests -# --------------------------------------------------------------------------- - - -def test_sync_http_client_default_base_max_retries() -> None: - """HttpClient defaults to base_max_retries=2.""" - http_client = HttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - ) - assert http_client.base_max_retries == 2 - - -def test_async_http_client_default_base_max_retries() -> None: - """AsyncHttpClient defaults to base_max_retries=2.""" - http_client = AsyncHttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - ) - assert http_client.base_max_retries == 2 - - -def test_sync_http_client_custom_base_max_retries() -> None: - """HttpClient accepts a custom base_max_retries value.""" - http_client = HttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_max_retries=5, - ) - assert http_client.base_max_retries == 5 - - -def test_async_http_client_custom_base_max_retries() -> None: - """AsyncHttpClient accepts a custom base_max_retries value.""" - http_client = AsyncHttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_max_retries=5, - ) - assert http_client.base_max_retries == 5 - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_base_max_retries_zero_disables_retries(mock_sleep: MagicMock) -> None: - """Sync: base_max_retries=0 disables retries when no request_options override.""" - mock_client = MagicMock() - mock_client.request.side_effect = httpx.ConnectError("connection failed") - http_client = HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, - ) - - with pytest.raises(httpx.ConnectError): - http_client.request(path="/test", method="GET") - - # No retries, just the initial attempt - assert mock_client.request.call_count == 1 - mock_sleep.assert_not_called() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_base_max_retries_zero_disables_retries(mock_sleep: AsyncMock) -> None: - """Async: base_max_retries=0 disables retries when no request_options override.""" - mock_client = MagicMock() - mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) - http_client = AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, - ) - - with pytest.raises(httpx.ConnectError): - await http_client.request(path="/test", method="GET") - - # No retries, just the initial attempt - assert mock_client.request.call_count == 1 - mock_sleep.assert_not_called() - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_request_options_override_base_max_retries(mock_sleep: MagicMock) -> None: - """Sync: request_options max_retries overrides base_max_retries.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.ConnectError("connection failed"), - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - http_client = HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, # base says no retries - ) - - # But request_options overrides to allow 2 retries - response = http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - assert response.status_code == 200 - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_request_options_override_base_max_retries(mock_sleep: AsyncMock) -> None: - """Async: request_options max_retries overrides base_max_retries.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.ConnectError("connection failed"), - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - ) - http_client = AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, # base says no retries - ) - - # But request_options overrides to allow 2 retries - response = await http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - assert response.status_code == 200 - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_base_max_retries_used_as_default(mock_sleep: MagicMock) -> None: - """Sync: base_max_retries is used when request_options has no max_retries.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - _DummyResponse(), - ] - http_client = HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=3, - ) - - response = http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - # 1 initial + 3 retries = 4 total attempts - assert mock_client.request.call_count == 4 - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_base_max_retries_used_as_default(mock_sleep: AsyncMock) -> None: - """Async: base_max_retries is used when request_options has no max_retries.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - _DummyResponse(), - ] - ) - http_client = AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=3, - ) - - response = await http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - # 1 initial + 3 retries = 4 total attempts - assert mock_client.request.call_count == 4 diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py deleted file mode 100644 index ef5fd7094f9b..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_query_encoding.py +++ /dev/null @@ -1,36 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from seed.core.query_encoder import encode_query - - -def test_query_encoding_deep_objects() -> None: - assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")] - assert encode_query({"hello_world": {"hello": "world"}}) == [("hello_world[hello]", "world")] - assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == [ - ("hello_world[hello][world]", "today"), - ("hello_world[test]", "this"), - ("hi", "there"), - ] - - -def test_query_encoding_deep_object_arrays() -> None: - assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [ - ("objects[key]", "hello"), - ("objects[value]", "world"), - ("objects[key]", "foo"), - ("objects[value]", "bar"), - ] - assert encode_query( - {"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]} - ) == [ - ("users[name]", "string"), - ("users[tags]", "string"), - ("users[name]", "string2"), - ("users[tags]", "string2"), - ("users[tags]", "string3"), - ] - - -def test_encode_query_with_none() -> None: - encoded = encode_query(None) - assert encoded is None diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py b/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py deleted file mode 100644 index b298db89c4bd..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/utils/test_serialization.py +++ /dev/null @@ -1,72 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, List - -from .assets.models import ObjectWithOptionalFieldParams, ShapeParams - -from seed.core.serialization import convert_and_respect_annotation_metadata - -UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} -UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} - - -def test_convert_and_respect_annotation_metadata() -> None: - data: ObjectWithOptionalFieldParams = { - "string": "string", - "long_": 12345, - "bool_": True, - "literal": "lit_one", - "any": "any", - } - converted = convert_and_respect_annotation_metadata( - object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" - ) - assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} - - -def test_convert_and_respect_annotation_metadata_in_list() -> None: - data: List[ObjectWithOptionalFieldParams] = [ - {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, - {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, - ] - converted = convert_and_respect_annotation_metadata( - object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write" - ) - - assert converted == [ - {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, - {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, - ] - - -def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: - data: ObjectWithOptionalFieldParams = { - "string": "string", - "long_": 12345, - "union": UNION_TEST, - "literal": "lit_one", - "any": "any", - } - converted = convert_and_respect_annotation_metadata( - object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" - ) - - assert converted == { - "string": "string", - "long": 12345, - "union": UNION_TEST_CONVERTED, - "literal": "lit_one", - "any": "any", - } - - -def test_convert_and_respect_annotation_metadata_in_union() -> None: - converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write") - - assert converted == UNION_TEST_CONVERTED - - -def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: - data: Any = {} - converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write") - assert converted == data diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/__init__.py b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py deleted file mode 100644 index 2782ac72dd88..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/conftest.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Pytest configuration for wire tests. - -This module provides helpers for creating a configured client that talks to -WireMock and for verifying requests in WireMock. - -The WireMock container lifecycle itself is managed by a top-level pytest -plugin (tests/conftest.py) so that the container is started exactly once -per test run, even when using pytest-xdist. -""" - -import inspect -import os -from typing import Any, Dict, Optional - -import httpx - -from seed.client import SeedApi - -# Check once at import time whether the client constructor accepts a headers kwarg. -try: - _CLIENT_SUPPORTS_HEADERS: bool = "headers" in inspect.signature(SeedApi).parameters -except (TypeError, ValueError): - _CLIENT_SUPPORTS_HEADERS = False - - -def _get_wiremock_base_url() -> str: - """Returns the WireMock base URL from the WIREMOCK_URL environment variable.""" - return os.environ.get("WIREMOCK_URL", "http://localhost:8080") - - -def get_client(test_id: str) -> SeedApi: - """ - Creates a configured client instance for wire tests. - - Args: - test_id: Unique identifier for the test, used for request tracking. - - Returns: - A configured client instance with all required auth parameters. - """ - test_headers = {"X-Test-Id": test_id} - base_url = _get_wiremock_base_url() - - if _CLIENT_SUPPORTS_HEADERS: - return SeedApi( - base_url=base_url, - headers=test_headers, - ) - - return SeedApi( - base_url=base_url, - httpx_client=httpx.Client(headers=test_headers), - ) - - -def verify_request_count( - test_id: str, - method: str, - url_path: str, - query_params: Optional[Dict[str, str]], - expected: int, -) -> None: - """Verifies the number of requests made to WireMock filtered by test ID for concurrency safety.""" - wiremock_admin_url = f"{_get_wiremock_base_url()}/__admin" - request_body: Dict[str, Any] = { - "method": method, - "urlPath": url_path, - "headers": {"X-Test-Id": {"equalTo": test_id}}, - } - if query_params: - query_parameters = {k: {"equalTo": v} for k, v in query_params.items()} - request_body["queryParameters"] = query_parameters - response = httpx.post(f"{wiremock_admin_url}/requests/find", json=request_body) - assert response.status_code == 200, "Failed to query WireMock requests" - result = response.json() - requests_found = len(result.get("requests", [])) - assert requests_found == expected, f"Expected {expected} requests, found {requests_found}" diff --git a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py b/seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py deleted file mode 100644 index 2f638bc05b10..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/tests/wire/test_.py +++ /dev/null @@ -1,44 +0,0 @@ -from .conftest import get_client, verify_request_count - - -def test__search_rule_types() -> None: - """Test searchRuleTypes endpoint with WireMock""" - test_id = "search_rule_types.0" - client = get_client(test_id) - client.search_rule_types() - verify_request_count(test_id, "GET", "/rule-types", None, 1) - - -def test__create_rule() -> None: - """Test createRule endpoint with WireMock""" - test_id = "create_rule.0" - client = get_client(test_id) - client.create_rule( - name="name", - execution_context="prod", - ) - verify_request_count(test_id, "POST", "/rules", None, 1) - - -def test__list_users() -> None: - """Test listUsers endpoint with WireMock""" - test_id = "list_users.0" - client = get_client(test_id) - client.list_users() - verify_request_count(test_id, "GET", "/users", None, 1) - - -def test__get_entity() -> None: - """Test getEntity endpoint with WireMock""" - test_id = "get_entity.0" - client = get_client(test_id) - client.get_entity() - verify_request_count(test_id, "GET", "/entities", None, 1) - - -def test__get_organization() -> None: - """Test getOrganization endpoint with WireMock""" - test_id = "get_organization.0" - client = get_client(test_id) - client.get_organization() - verify_request_count(test_id, "GET", "/organizations", None, 1) diff --git a/seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml b/seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml deleted file mode 100644 index 58747d54a46b..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/wiremock/docker-compose.test.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - wiremock: - image: wiremock/wiremock:3.9.1 - ports: - - "0:8080" # Use dynamic port to avoid conflicts with concurrent tests - volumes: - - ./wiremock-mappings.json:/home/wiremock/mappings/wiremock-mappings.json - command: ["--global-response-templating", "--verbose"] - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8080/__admin/health"] - interval: 2s - timeout: 5s - retries: 15 - start_period: 5s diff --git a/seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json b/seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json deleted file mode 100644 index 801bfb5883a2..000000000000 --- a/seed/python-sdk/allof-inline/no-custom-config/wiremock/wiremock-mappings.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "mappings": [ - { - "id": "19b28390-baa1-4ec9-87a3-cd8485b994a5", - "name": "Search rule types with paginated results - default", - "request": { - "urlPathTemplate": "/rule-types", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"name\": \"name\",\n \"description\": \"description\"\n }\n ]\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "19b28390-baa1-4ec9-87a3-cd8485b994a5", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - }, - { - "id": "1a934299-52ab-4d86-aa31-9f873790df88", - "name": "Create a rule with constrained execution context - default", - "request": { - "urlPathTemplate": "/rules", - "method": "POST" - }, - "response": { - "status": 200, - "body": "{\n \"createdBy\": \"createdBy\",\n \"createdDateTime\": \"2024-01-15T09:30:00Z\",\n \"modifiedBy\": \"modifiedBy\",\n \"modifiedDateTime\": \"2024-01-15T09:30:00Z\",\n \"id\": \"id\",\n \"name\": \"name\",\n \"status\": \"active\",\n \"executionContext\": \"prod\"\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "1a934299-52ab-4d86-aa31-9f873790df88", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - } - }, - { - "id": "c4083f9d-e8ad-4944-abe5-163125679505", - "name": "List users with paginated results - default", - "request": { - "urlPathTemplate": "/users", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"email\": \"email\"\n }\n ]\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "c4083f9d-e8ad-4944-abe5-163125679505", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - }, - { - "id": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", - "name": "Get an entity that combines multiple parents - default", - "request": { - "urlPathTemplate": "/entities", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"id\": \"id\",\n \"name\": \"name\",\n \"summary\": \"summary\",\n \"status\": \"active\"\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - }, - { - "id": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", - "name": "Get an organization with merged object-typed properties - default", - "request": { - "urlPathTemplate": "/organizations", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"id\": \"id\",\n \"metadata\": {\n \"region\": \"region\",\n \"domain\": \"domain\"\n },\n \"name\": \"name\"\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - } - ], - "meta": { - "total": 5 - } -} \ No newline at end of file diff --git a/seed/python-sdk/allof/no-custom-config/.fern/metadata.json b/seed/python-sdk/allof/no-custom-config/.fern/metadata.json deleted file mode 100644 index d3303ad03b0b..000000000000 --- a/seed/python-sdk/allof/no-custom-config/.fern/metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "local", - "generatorConfig": { - "enable_wire_tests": true - }, - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml b/seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml deleted file mode 100644 index fd1df043d08d..000000000000 --- a/seed/python-sdk/allof/no-custom-config/.github/workflows/ci.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: ci -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Compile - run: poetry run mypy . - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - - name: Test - run: poetry run pytest -rP -n auto . - - - name: Install aiohttp extra - run: poetry install --extras aiohttp - - - name: Test (aiohttp) - run: poetry run pytest -rP -n auto -m aiohttp . - - publish: - needs: [compile, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Set up python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 - - name: Install dependencies - run: poetry install - - name: Publish to pypi - run: | - poetry config repositories.remote - poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD" - env: - PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} - PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/seed/python-sdk/allof/no-custom-config/.gitignore b/seed/python-sdk/allof/no-custom-config/.gitignore deleted file mode 100644 index d2e4ca808d21..000000000000 --- a/seed/python-sdk/allof/no-custom-config/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.mypy_cache/ -.ruff_cache/ -__pycache__/ -dist/ -poetry.toml diff --git a/seed/python-sdk/allof/no-custom-config/README.md b/seed/python-sdk/allof/no-custom-config/README.md deleted file mode 100644 index 8a3e78f60ee6..000000000000 --- a/seed/python-sdk/allof/no-custom-config/README.md +++ /dev/null @@ -1,176 +0,0 @@ -# Seed Python Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPython) -[![pypi](https://img.shields.io/pypi/v/fern_allof)](https://pypi.python.org/pypi/fern_allof) - -The Seed Python library provides convenient access to the Seed APIs from Python. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Async Client](#async-client) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Access Raw Response Data](#access-raw-response-data) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Custom Client](#custom-client) -- [Contributing](#contributing) - -## Installation - -```sh -pip install fern_allof -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```python -from seed import SeedApi - -client = SeedApi() - -client.create_rule( - name="name", - execution_context="prod", -) -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) -``` - -## Async Client - -The SDK also exports an `async` client so that you can make non-blocking calls to our API. Note that if you are constructing an Async httpx client class to pass into this client, use `httpx.AsyncClient()` instead of `httpx.Client()` (e.g. for the `httpx_client` parameter of this client). - -```python -import asyncio - -from seed import AsyncSeedApi - -client = AsyncSeedApi() - - -async def main() -> None: - await client.create_rule( - name="name", - execution_context="prod", - ) - - -asyncio.run(main()) -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```python -from seed.core.api_error import ApiError - -try: - client.create_rule(...) -except ApiError as e: - print(e.status_code) - print(e.body) -``` - -## Advanced - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.with_raw_response` property. -The `.with_raw_response` property returns a "raw" client that can be used to access the `.headers` and `.data` attributes. - -```python -from seed import SeedApi - -client = SeedApi(...) -response = client.with_raw_response.create_rule(...) -print(response.headers) # access the response headers -print(response.status_code) # access the response status code -print(response.data) # access the underlying object -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `max_retries` request option to configure this behavior. - -```python -client.create_rule(..., request_options={ - "max_retries": 1 -}) -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. - -```python -from seed import SeedApi - -client = SeedApi(..., timeout=20.0) - -# Override timeout for a specific method -client.create_rule(..., request_options={ - "timeout_in_seconds": 1 -}) -``` - -### Custom Client - -You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies -and transports. - -```python -import httpx -from seed import SeedApi - -client = SeedApi( - ..., - httpx_client=httpx.Client( - proxy="http://my.test.proxy.example.com", - transport=httpx.HTTPTransport(local_address="0.0.0.0"), - ), -) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/allof/no-custom-config/poetry.lock b/seed/python-sdk/allof/no-custom-config/poetry.lock deleted file mode 100644 index 613c65b508b1..000000000000 --- a/seed/python-sdk/allof/no-custom-config/poetry.lock +++ /dev/null @@ -1,1614 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -description = "Happy Eyeballs for asyncio" -optional = true -python-versions = ">=3.9" -files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, -] - -[[package]] -name = "aiohttp" -version = "3.13.5" -description = "Async http client/server framework (asyncio)" -optional = true -python-versions = ">=3.9" -files = [ - {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, - {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, - {file = "aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7"}, - {file = "aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9"}, - {file = "aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3"}, - {file = "aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06"}, - {file = "aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14"}, - {file = "aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3"}, - {file = "aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c"}, - {file = "aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc"}, - {file = "aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b"}, - {file = "aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3"}, - {file = "aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9"}, - {file = "aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8"}, - {file = "aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9"}, - {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:347542f0ea3f95b2a955ee6656461fa1c776e401ac50ebce055a6c38454a0adf"}, - {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:178c7b5e62b454c2bc790786e6058c3cc968613b4419251b478c153a4aec32b1"}, - {file = "aiohttp-3.13.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af545c2cffdb0967a96b6249e6f5f7b0d92cdfd267f9d5238d5b9ca63e8edb10"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:206b7b3ef96e4ce211754f0cd003feb28b7d81f0ad26b8d077a5d5161436067f"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee5e86776273de1795947d17bddd6bb19e0365fd2af4289c0d2c5454b6b1d36b"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95d14ca7abefde230f7639ec136ade282655431fd5db03c343b19dda72dd1643"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:912d4b6af530ddb1338a66229dac3a25ff11d4448be3ec3d6340583995f56031"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e999f0c88a458c836d5fb521814e92ed2172c649200336a6df514987c1488258"}, - {file = "aiohttp-3.13.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39380e12bd1f2fdab4285b6e055ad48efbaed5c836433b142ed4f5b9be71036a"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9efcc0f11d850cefcafdd9275b9576ad3bfb539bed96807663b32ad99c4d4b88"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:147b4f501d0292077f29d5268c16bb7c864a1f054d7001c4c1812c0421ea1ed0"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d147004fede1b12f6013a6dbb2a26a986a671a03c6ea740ddc76500e5f1c399f"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9277145d36a01653863899c665243871434694bcc3431922c3b35c978061bdb8"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4e704c52438f66fdd89588346183d898bb42167cf88f8b7ff1c0f9fc957c348f"}, - {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8a4d3427e8de1312ddf309cc482186466c79895b3a139fed3259fc01dfa9a5b"}, - {file = "aiohttp-3.13.5-cp39-cp39-win32.whl", hash = "sha256:6f497a6876aa4b1a102b04996ce4c1170c7040d83faa9387dd921c16e30d5c83"}, - {file = "aiohttp-3.13.5-cp39-cp39-win_amd64.whl", hash = "sha256:cb979826071c0986a5f08333a36104153478ce6018c58cba7f9caddaf63d5d67"}, - {file = "aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.5.0" -aiosignal = ">=1.4.0" -async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -propcache = ">=0.2.0" -yarl = ">=1.17.0,<2.0" - -[package.extras] -speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] - -[[package]] -name = "aiosignal" -version = "1.4.0" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = true -python-versions = ">=3.9" -files = [ - {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, - {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" -typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.13.0" -description = "High-level concurrency and networking framework on top of asyncio or Trio" -optional = false -python-versions = ">=3.10" -files = [ - {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, - {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -trio = ["trio (>=0.32.0)"] - -[[package]] -name = "async-timeout" -version = "5.0.1" -description = "Timeout context manager for asyncio programs" -optional = true -python-versions = ">=3.8" -files = [ - {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, - {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, -] - -[[package]] -name = "attrs" -version = "26.1.0" -description = "Classes Without Boilerplate" -optional = true -python-versions = ">=3.9" -files = [ - {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, - {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." -optional = false -python-versions = "<3.11,>=3.8" -files = [ - {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, - {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, -] - -[[package]] -name = "certifi" -version = "2026.2.25" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -files = [ - {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, - {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.7" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -files = [ - {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, - {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.2" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, - {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "frozenlist" -version = "1.8.0" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = true -python-versions = ">=3.9" -files = [ - {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, - {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, - {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, - {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, - {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, - {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, - {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, - {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, - {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, - {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, - {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, - {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, - {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, - {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, - {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, - {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, - {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, - {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, - {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, - {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, - {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, - {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, - {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, - {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, - {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, - {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, - {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, - {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, - {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, - {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, - {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, - {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, - {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, - {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, - {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, - {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, - {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, - {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, - {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, - {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, - {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, - {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, - {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, - {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, - {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, - {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, - {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, - {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, - {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, - {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, - {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, - {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, - {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, - {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, - {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, - {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, - {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, - {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, - {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, - {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, - {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, - {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, - {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, - {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, -] - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, - {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.16" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "httpx-aiohttp" -version = "0.1.8" -description = "Aiohttp transport for HTTPX" -optional = true -python-versions = ">=3.8" -files = [ - {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, - {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, -] - -[package.dependencies] -aiohttp = ">=3.10.0,<4" -httpx = ">=0.27.0" - -[[package]] -name = "idna" -version = "3.11" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "multidict" -version = "6.7.1" -description = "multidict implementation" -optional = true -python-versions = ">=3.9" -files = [ - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, - {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, - {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, - {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, - {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, - {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, - {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, - {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, - {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, - {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, - {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, - {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, - {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, - {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, - {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, - {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, - {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, - {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, - {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, - {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, - {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, - {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, - {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, - {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, - {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, - {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, - {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "mypy" -version = "1.13.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "packaging" -version = "26.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "propcache" -version = "0.4.1" -description = "Accelerated property cache" -optional = true -python-versions = ">=3.9" -files = [ - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, - {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, - {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, - {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, - {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, - {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, - {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, - {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, - {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, - {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, - {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, - {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, - {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, - {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, - {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, - {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, - {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, - {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, - {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, - {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, - {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, - {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, - {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, - {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, - {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, - {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, - {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, - {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.41.5" -typing-extensions = ">=4.14.1" -typing-inspection = ">=0.4.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, - {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, -] - -[package.dependencies] -typing-extensions = ">=4.14.1" - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.4.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.10" -files = [ - {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, - {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, -] - -[package.dependencies] -backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} -pytest = ">=8.2,<10" -typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-xdist" -version = "3.8.0" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, - {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "requests" -version = "2.33.1" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.10" -files = [ - {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, - {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, -] - -[package.dependencies] -certifi = ">=2023.5.7" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.26,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] - -[[package]] -name = "ruff" -version = "0.11.5" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, - {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, - {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, - {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, - {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, - {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, - {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "tomli" -version = "2.4.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, - {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, - {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, - {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, - {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, - {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, - {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, - {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, - {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, - {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, - {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, - {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, - {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, - {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, - {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, - {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, - {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, - {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, - {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, -] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20260408" -description = "Typing stubs for python-dateutil" -optional = false -python-versions = ">=3.10" -files = [ - {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, - {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, -] - -[[package]] -name = "types-requests" -version = "2.33.0.20260408" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.10" -files = [ - {file = "types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f"}, - {file = "types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b"}, -] - -[package.dependencies] -urllib3 = ">=2" - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[[package]] -name = "urllib3" -version = "2.6.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, -] - -[package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] - -[[package]] -name = "yarl" -version = "1.23.0" -description = "Yet another URL library" -optional = true -python-versions = ">=3.10" -files = [ - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, - {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, - {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, - {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, - {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, - {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, - {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, - {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, - {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, - {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, - {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, - {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, - {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, - {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, - {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, - {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, - {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, - {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, - {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, - {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, - {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, - {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, - {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, - {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.1" - -[extras] -aiohttp = ["aiohttp", "httpx-aiohttp"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "7ac12b5d251e81a278fba2c051cbf4f768fb951f006f23ad0faedb1289539c1a" diff --git a/seed/python-sdk/allof/no-custom-config/pyproject.toml b/seed/python-sdk/allof/no-custom-config/pyproject.toml deleted file mode 100644 index 16a027ebcb5f..000000000000 --- a/seed/python-sdk/allof/no-custom-config/pyproject.toml +++ /dev/null @@ -1,102 +0,0 @@ -[project] -name = "fern_allof" -dynamic = ["version"] - -[tool.poetry] -name = "fern_allof" -version = "0.0.1" -description = "" -readme = "README.md" -authors = [] -keywords = [ - "fern", - "test" -] - -classifiers = [ - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Programming Language :: Python :: 3.15", - "Operating System :: OS Independent", - "Operating System :: POSIX", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Topic :: Software Development :: Libraries :: Python Modules", - "Typing :: Typed" -] -packages = [ - { include = "seed", from = "src"} -] - -[tool.poetry.urls] -Documentation = 'https://buildwithfern.com/learn' -Homepage = 'https://buildwithfern.com/' -Repository = 'https://github.com/allof/fern' - -[tool.poetry.dependencies] -python = "^3.10" -aiohttp = { version = ">=3.10.0,<4", optional = true} -httpx = ">=0.21.2" -httpx-aiohttp = { version = "0.1.8", optional = true} -pydantic = ">= 1.9.2" -pydantic-core = ">=2.18.2,<2.44.0" -typing_extensions = ">= 4.0.0" - -[tool.poetry.group.dev.dependencies] -mypy = "==1.13.0" -pytest = "^8.2.0" -pytest-asyncio = "^1.0.0" -pytest-xdist = "^3.6.1" -python-dateutil = "^2.9.0" -types-python-dateutil = "^2.9.0.20240316" -requests = "^2.31.0" -types-requests = "^2.31.0" -ruff = "==0.11.5" - -[tool.pytest.ini_options] -testpaths = [ "tests" ] -asyncio_mode = "auto" -markers = [ - "aiohttp: tests that require httpx_aiohttp to be installed", -] - -[tool.mypy] -plugins = ["pydantic.mypy"] - -[tool.ruff] -line-length = 120 - -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "F", # pyflakes - "I", # isort -] -ignore = [ - "E402", # Module level import not at top of file - "E501", # Line too long - "E711", # Comparison to `None` should be `cond is not None` - "E712", # Avoid equality comparisons to `True`; use `if ...:` checks - "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks - "E722", # Do not use bare `except` - "E731", # Do not assign a `lambda` expression, use a `def` - "F821", # Undefined name - "F841" # Local variable ... is assigned to but never used -] - -[tool.ruff.lint.isort] -section-order = ["future", "standard-library", "third-party", "first-party"] - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry.extras] -aiohttp=["aiohttp", "httpx-aiohttp"] diff --git a/seed/python-sdk/allof/no-custom-config/reference.md b/seed/python-sdk/allof/no-custom-config/reference.md deleted file mode 100644 index e92bd3a6925d..000000000000 --- a/seed/python-sdk/allof/no-custom-config/reference.md +++ /dev/null @@ -1,268 +0,0 @@ -# Reference -
client.search_rule_types(...) -> RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.search_rule_types() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `typing.Optional[str]` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.create_rule(...) -> RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.create_rule( - name="name", - execution_context="prod", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `str` - -
-
- -
-
- -**execution_context:** `RuleExecutionContext` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.list_users() -> UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.list_users() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.get_entity() -> CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.get_entity() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.get_organization() -> Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from seed import SeedApi -from seed.environment import SeedApiEnvironment - -client = SeedApi( - environment=SeedApiEnvironment.DEFAULT, -) - -client.get_organization() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- diff --git a/seed/python-sdk/allof/no-custom-config/requirements.txt b/seed/python-sdk/allof/no-custom-config/requirements.txt deleted file mode 100644 index 0141a1a5014b..000000000000 --- a/seed/python-sdk/allof/no-custom-config/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -httpx>=0.21.2 -pydantic>= 1.9.2 -pydantic-core>=2.18.2,<2.44.0 -typing_extensions>= 4.0.0 diff --git a/seed/python-sdk/allof/no-custom-config/snippet.json b/seed/python-sdk/allof/no-custom-config/snippet.json deleted file mode 100644 index f000c2ddf786..000000000000 --- a/seed/python-sdk/allof/no-custom-config/snippet.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "types": {}, - "endpoints": [ - { - "example_identifier": "default", - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.search_rule_types()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.search_rule_types()\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n)\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.create_rule(\n name=\"name\",\n execution_context=\"prod\",\n )\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.list_users()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.list_users()\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_entity()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_entity()\n\n\nasyncio.run(main())\n", - "type": "python" - } - }, - { - "example_identifier": "default", - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "sync_client": "from seed import SeedApi\n\nclient = SeedApi()\nclient.get_organization()\n", - "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi()\n\n\nasync def main() -> None:\n await client.get_organization()\n\n\nasyncio.run(main())\n", - "type": "python" - } - } - ] -} \ No newline at end of file diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/__init__.py deleted file mode 100644 index 20ed7e281f4c..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/__init__.py +++ /dev/null @@ -1,110 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from .types import ( - AuditInfo, - BaseOrg, - BaseOrgMetadata, - CombinedEntity, - CombinedEntityStatus, - Describable, - DetailedOrg, - DetailedOrgMetadata, - Identifiable, - Organization, - PaginatedResult, - PagingCursors, - RuleExecutionContext, - RuleResponse, - RuleResponseStatus, - RuleType, - RuleTypeSearchResponse, - User, - UserSearchResponse, - ) - from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient - from .client import AsyncSeedApi, SeedApi - from .environment import SeedApiEnvironment - from .version import __version__ -_dynamic_imports: typing.Dict[str, str] = { - "AsyncSeedApi": ".client", - "AuditInfo": ".types", - "BaseOrg": ".types", - "BaseOrgMetadata": ".types", - "CombinedEntity": ".types", - "CombinedEntityStatus": ".types", - "DefaultAioHttpClient": "._default_clients", - "DefaultAsyncHttpxClient": "._default_clients", - "Describable": ".types", - "DetailedOrg": ".types", - "DetailedOrgMetadata": ".types", - "Identifiable": ".types", - "Organization": ".types", - "PaginatedResult": ".types", - "PagingCursors": ".types", - "RuleExecutionContext": ".types", - "RuleResponse": ".types", - "RuleResponseStatus": ".types", - "RuleType": ".types", - "RuleTypeSearchResponse": ".types", - "SeedApi": ".client", - "SeedApiEnvironment": ".environment", - "User": ".types", - "UserSearchResponse": ".types", - "__version__": ".version", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = [ - "AsyncSeedApi", - "AuditInfo", - "BaseOrg", - "BaseOrgMetadata", - "CombinedEntity", - "CombinedEntityStatus", - "DefaultAioHttpClient", - "DefaultAsyncHttpxClient", - "Describable", - "DetailedOrg", - "DetailedOrgMetadata", - "Identifiable", - "Organization", - "PaginatedResult", - "PagingCursors", - "RuleExecutionContext", - "RuleResponse", - "RuleResponseStatus", - "RuleType", - "RuleTypeSearchResponse", - "SeedApi", - "SeedApiEnvironment", - "User", - "UserSearchResponse", - "__version__", -] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py b/seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py deleted file mode 100644 index de71e3bb16d7..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/_default_clients.py +++ /dev/null @@ -1,30 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import httpx - -SDK_DEFAULT_TIMEOUT = 60 - -try: - import httpx_aiohttp # type: ignore[import-not-found] -except ImportError: - - class DefaultAioHttpClient(httpx.AsyncClient): # type: ignore - def __init__(self, **kwargs: typing.Any) -> None: - raise RuntimeError("To use the aiohttp client, install the aiohttp extra: pip install fern_allof[aiohttp]") - -else: - - class DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore - def __init__(self, **kwargs: typing.Any) -> None: - kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) - kwargs.setdefault("follow_redirects", True) - super().__init__(**kwargs) - - -class DefaultAsyncHttpxClient(httpx.AsyncClient): - def __init__(self, **kwargs: typing.Any) -> None: - kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) - kwargs.setdefault("follow_redirects", True) - super().__init__(**kwargs) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/client.py b/seed/python-sdk/allof/no-custom-config/src/seed/client.py deleted file mode 100644 index 65adf5ca16bc..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/client.py +++ /dev/null @@ -1,500 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import httpx -from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .core.logging import LogConfig, Logger -from .core.request_options import RequestOptions -from .environment import SeedApiEnvironment -from .raw_client import AsyncRawSeedApi, RawSeedApi -from .types.combined_entity import CombinedEntity -from .types.organization import Organization -from .types.rule_execution_context import RuleExecutionContext -from .types.rule_response import RuleResponse -from .types.rule_type_search_response import RuleTypeSearchResponse -from .types.user_search_response import UserSearchResponse - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class SeedApi: - """ - Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. - - Parameters - ---------- - base_url : typing.Optional[str] - The base url to use for requests from the client. - - environment : SeedApiEnvironment - The environment to use for requests from the client. from .environment import SeedApiEnvironment - - - - Defaults to SeedApiEnvironment.DEFAULT - - - - headers : typing.Optional[typing.Dict[str, str]] - Additional headers to send with every request. - - timeout : typing.Optional[float] - The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. - - follow_redirects : typing.Optional[bool] - Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. - - httpx_client : typing.Optional[httpx.Client] - The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. - - logging : typing.Optional[typing.Union[LogConfig, Logger]] - Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - """ - - def __init__( - self, - *, - base_url: typing.Optional[str] = None, - environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read - ) - self._client_wrapper = SyncClientWrapper( - base_url=_get_base_url(base_url=base_url, environment=environment), - headers=headers, - httpx_client=httpx_client - if httpx_client is not None - else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) - if follow_redirects is not None - else httpx.Client(timeout=_defaulted_timeout), - timeout=_defaulted_timeout, - logging=logging, - ) - self._raw_client = RawSeedApi(client_wrapper=self._client_wrapper) - - @property - def with_raw_response(self) -> RawSeedApi: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawSeedApi - """ - return self._raw_client - - def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> RuleTypeSearchResponse: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleTypeSearchResponse - Paginated list of rule types - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.search_rule_types() - """ - _response = self._raw_client.search_rule_types(query=query, request_options=request_options) - return _response.data - - def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> RuleResponse: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleResponse - Created rule - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.create_rule( - name="name", - execution_context="prod", - ) - """ - _response = self._raw_client.create_rule( - name=name, execution_context=execution_context, request_options=request_options - ) - return _response.data - - def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - UserSearchResponse - Paginated list of users - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.list_users() - """ - _response = self._raw_client.list_users(request_options=request_options) - return _response.data - - def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CombinedEntity - An entity with properties from multiple parents - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.get_entity() - """ - _response = self._raw_client.get_entity(request_options=request_options) - return _response.data - - def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Organization - An organization whose metadata is merged from two parents - - Examples - -------- - from seed import SeedApi - - client = SeedApi() - client.get_organization() - """ - _response = self._raw_client.get_organization(request_options=request_options) - return _response.data - - -def _make_default_async_client( - timeout: typing.Optional[float], - follow_redirects: typing.Optional[bool], -) -> httpx.AsyncClient: - try: - import httpx_aiohttp # type: ignore[import-not-found] - except ImportError: - pass - else: - if follow_redirects is not None: - return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects) - return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout) - - if follow_redirects is not None: - return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) - return httpx.AsyncClient(timeout=timeout) - - -class AsyncSeedApi: - """ - Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. - - Parameters - ---------- - base_url : typing.Optional[str] - The base url to use for requests from the client. - - environment : SeedApiEnvironment - The environment to use for requests from the client. from .environment import SeedApiEnvironment - - - - Defaults to SeedApiEnvironment.DEFAULT - - - - headers : typing.Optional[typing.Dict[str, str]] - Additional headers to send with every request. - - timeout : typing.Optional[float] - The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. - - follow_redirects : typing.Optional[bool] - Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. - - httpx_client : typing.Optional[httpx.AsyncClient] - The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. - - logging : typing.Optional[typing.Union[LogConfig, Logger]] - Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. - - Examples - -------- - from seed import AsyncSeedApi - - client = AsyncSeedApi() - """ - - def __init__( - self, - *, - base_url: typing.Optional[str] = None, - environment: SeedApiEnvironment = SeedApiEnvironment.DEFAULT, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read - ) - self._client_wrapper = AsyncClientWrapper( - base_url=_get_base_url(base_url=base_url, environment=environment), - headers=headers, - httpx_client=httpx_client - if httpx_client is not None - else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), - timeout=_defaulted_timeout, - logging=logging, - ) - self._raw_client = AsyncRawSeedApi(client_wrapper=self._client_wrapper) - - @property - def with_raw_response(self) -> AsyncRawSeedApi: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawSeedApi - """ - return self._raw_client - - async def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> RuleTypeSearchResponse: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleTypeSearchResponse - Paginated list of rule types - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.search_rule_types() - - - asyncio.run(main()) - """ - _response = await self._raw_client.search_rule_types(query=query, request_options=request_options) - return _response.data - - async def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> RuleResponse: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - RuleResponse - Created rule - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.create_rule( - name="name", - execution_context="prod", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.create_rule( - name=name, execution_context=execution_context, request_options=request_options - ) - return _response.data - - async def list_users(self, *, request_options: typing.Optional[RequestOptions] = None) -> UserSearchResponse: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - UserSearchResponse - Paginated list of users - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.list_users() - - - asyncio.run(main()) - """ - _response = await self._raw_client.list_users(request_options=request_options) - return _response.data - - async def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> CombinedEntity: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CombinedEntity - An entity with properties from multiple parents - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.get_entity() - - - asyncio.run(main()) - """ - _response = await self._raw_client.get_entity(request_options=request_options) - return _response.data - - async def get_organization(self, *, request_options: typing.Optional[RequestOptions] = None) -> Organization: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Organization - An organization whose metadata is merged from two parents - - Examples - -------- - import asyncio - - from seed import AsyncSeedApi - - client = AsyncSeedApi() - - - async def main() -> None: - await client.get_organization() - - - asyncio.run(main()) - """ - _response = await self._raw_client.get_organization(request_options=request_options) - return _response.data - - -def _get_base_url(*, base_url: typing.Optional[str] = None, environment: SeedApiEnvironment) -> str: - if base_url is not None: - return base_url - elif environment is not None: - return environment.value - else: - raise Exception("Please pass in either base_url or environment to construct the client") diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py deleted file mode 100644 index 5bc159a110f2..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/__init__.py +++ /dev/null @@ -1,127 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from .api_error import ApiError - from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper - from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime - from .file import File, convert_file_dict_to_httpx_tuples, with_content_type - from .http_client import AsyncHttpClient, HttpClient - from .http_response import AsyncHttpResponse, HttpResponse - from .jsonable_encoder import encode_path_param, jsonable_encoder - from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger - from .parse_error import ParsingError - from .pydantic_utilities import ( - IS_PYDANTIC_V2, - UniversalBaseModel, - UniversalRootModel, - parse_obj_as, - universal_field_validator, - universal_root_validator, - update_forward_refs, - ) - from .query_encoder import encode_query - from .remove_none_from_dict import remove_none_from_dict - from .request_options import RequestOptions - from .serialization import FieldMetadata, convert_and_respect_annotation_metadata -_dynamic_imports: typing.Dict[str, str] = { - "ApiError": ".api_error", - "AsyncClientWrapper": ".client_wrapper", - "AsyncHttpClient": ".http_client", - "AsyncHttpResponse": ".http_response", - "BaseClientWrapper": ".client_wrapper", - "ConsoleLogger": ".logging", - "FieldMetadata": ".serialization", - "File": ".file", - "HttpClient": ".http_client", - "HttpResponse": ".http_response", - "ILogger": ".logging", - "IS_PYDANTIC_V2": ".pydantic_utilities", - "LogConfig": ".logging", - "LogLevel": ".logging", - "Logger": ".logging", - "ParsingError": ".parse_error", - "RequestOptions": ".request_options", - "Rfc2822DateTime": ".datetime_utils", - "SyncClientWrapper": ".client_wrapper", - "UniversalBaseModel": ".pydantic_utilities", - "UniversalRootModel": ".pydantic_utilities", - "convert_and_respect_annotation_metadata": ".serialization", - "convert_file_dict_to_httpx_tuples": ".file", - "create_logger": ".logging", - "encode_path_param": ".jsonable_encoder", - "encode_query": ".query_encoder", - "jsonable_encoder": ".jsonable_encoder", - "parse_obj_as": ".pydantic_utilities", - "parse_rfc2822_datetime": ".datetime_utils", - "remove_none_from_dict": ".remove_none_from_dict", - "serialize_datetime": ".datetime_utils", - "universal_field_validator": ".pydantic_utilities", - "universal_root_validator": ".pydantic_utilities", - "update_forward_refs": ".pydantic_utilities", - "with_content_type": ".file", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = [ - "ApiError", - "AsyncClientWrapper", - "AsyncHttpClient", - "AsyncHttpResponse", - "BaseClientWrapper", - "ConsoleLogger", - "FieldMetadata", - "File", - "HttpClient", - "HttpResponse", - "ILogger", - "IS_PYDANTIC_V2", - "LogConfig", - "LogLevel", - "Logger", - "ParsingError", - "RequestOptions", - "Rfc2822DateTime", - "SyncClientWrapper", - "UniversalBaseModel", - "UniversalRootModel", - "convert_and_respect_annotation_metadata", - "convert_file_dict_to_httpx_tuples", - "create_logger", - "encode_path_param", - "encode_query", - "jsonable_encoder", - "parse_obj_as", - "parse_rfc2822_datetime", - "remove_none_from_dict", - "serialize_datetime", - "universal_field_validator", - "universal_root_validator", - "update_forward_refs", - "with_content_type", -] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py deleted file mode 100644 index 6f850a60cba3..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/api_error.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Optional - - -class ApiError(Exception): - headers: Optional[Dict[str, str]] - status_code: Optional[int] - body: Any - - def __init__( - self, - *, - headers: Optional[Dict[str, str]] = None, - status_code: Optional[int] = None, - body: Any = None, - ) -> None: - self.headers = headers - self.status_code = status_code - self.body = body - - def __str__(self) -> str: - return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py deleted file mode 100644 index a305bde51b00..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/client_wrapper.py +++ /dev/null @@ -1,95 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import httpx -from .http_client import AsyncHttpClient, HttpClient -from .logging import LogConfig, Logger - - -class BaseClientWrapper: - def __init__( - self, - *, - headers: typing.Optional[typing.Dict[str, str]] = None, - base_url: str, - timeout: typing.Optional[float] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - self._headers = headers - self._base_url = base_url - self._timeout = timeout - self._logging = logging - - def get_headers(self) -> typing.Dict[str, str]: - import platform - - headers: typing.Dict[str, str] = { - "User-Agent": "fern_allof/0.0.1", - "X-Fern-Language": "Python", - "X-Fern-Runtime": f"python/{platform.python_version()}", - "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", - "X-Fern-SDK-Name": "fern_allof", - "X-Fern-SDK-Version": "0.0.1", - **(self.get_custom_headers() or {}), - } - return headers - - def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]: - return self._headers - - def get_base_url(self) -> str: - return self._base_url - - def get_timeout(self) -> typing.Optional[float]: - return self._timeout - - -class SyncClientWrapper(BaseClientWrapper): - def __init__( - self, - *, - headers: typing.Optional[typing.Dict[str, str]] = None, - base_url: str, - timeout: typing.Optional[float] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - httpx_client: httpx.Client, - ): - super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) - self.httpx_client = HttpClient( - httpx_client=httpx_client, - base_headers=self.get_headers, - base_timeout=self.get_timeout, - base_url=self.get_base_url, - logging_config=self._logging, - ) - - -class AsyncClientWrapper(BaseClientWrapper): - def __init__( - self, - *, - headers: typing.Optional[typing.Dict[str, str]] = None, - base_url: str, - timeout: typing.Optional[float] = None, - logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, - httpx_client: httpx.AsyncClient, - ): - super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging) - self._async_token = async_token - self.httpx_client = AsyncHttpClient( - httpx_client=httpx_client, - base_headers=self.get_headers, - base_timeout=self.get_timeout, - base_url=self.get_base_url, - async_base_headers=self.async_get_headers, - logging_config=self._logging, - ) - - async def async_get_headers(self) -> typing.Dict[str, str]: - headers = self.get_headers() - if self._async_token is not None: - token = await self._async_token() - headers["Authorization"] = f"Bearer {token}" - return headers diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py deleted file mode 100644 index a12b2ad03c53..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/datetime_utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -from email.utils import parsedate_to_datetime -from typing import Any - -import pydantic - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - - -def parse_rfc2822_datetime(v: Any) -> dt.datetime: - """ - Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") - into a datetime object. If the value is already a datetime, return it as-is. - Falls back to ISO 8601 parsing if RFC 2822 parsing fails. - """ - if isinstance(v, dt.datetime): - return v - if isinstance(v, str): - try: - return parsedate_to_datetime(v) - except Exception: - pass - # Fallback to ISO 8601 parsing - return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) - raise ValueError(f"Expected str or datetime, got {type(v)}") - - -class Rfc2822DateTime(dt.datetime): - """A datetime subclass that parses RFC 2822 date strings. - - On Pydantic V1, uses __get_validators__ for pre-validation. - On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. - """ - - @classmethod - def __get_validators__(cls): # type: ignore[no-untyped-def] - yield parse_rfc2822_datetime - - @classmethod - def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] - from pydantic_core import core_schema - - return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) - - -def serialize_datetime(v: dt.datetime) -> str: - """ - Serialize a datetime including timezone info. - - Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. - - UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. - """ - - def _serialize_zoned_datetime(v: dt.datetime) -> str: - if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): - # UTC is a special case where we use "Z" at the end instead of "+00:00" - return v.isoformat().replace("+00:00", "Z") - else: - # Delegate to the typical +/- offset format - return v.isoformat() - - if v.tzinfo is not None: - return _serialize_zoned_datetime(v) - else: - local_tz = dt.datetime.now().astimezone().tzinfo - localized_dt = v.replace(tzinfo=local_tz) - return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/file.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/file.py deleted file mode 100644 index 44b0d27c0895..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/file.py +++ /dev/null @@ -1,67 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast - -# File typing inspired by the flexibility of types within the httpx library -# https://github.com/encode/httpx/blob/master/httpx/_types.py -FileContent = Union[IO[bytes], bytes, str] -File = Union[ - # file (or bytes) - FileContent, - # (filename, file (or bytes)) - Tuple[Optional[str], FileContent], - # (filename, file (or bytes), content_type) - Tuple[Optional[str], FileContent, Optional[str]], - # (filename, file (or bytes), content_type, headers) - Tuple[ - Optional[str], - FileContent, - Optional[str], - Mapping[str, str], - ], -] - - -def convert_file_dict_to_httpx_tuples( - d: Dict[str, Union[File, List[File]]], -) -> List[Tuple[str, File]]: - """ - The format we use is a list of tuples, where the first element is the - name of the file and the second is the file object. Typically HTTPX wants - a dict, but to be able to send lists of files, you have to use the list - approach (which also works for non-lists) - https://github.com/encode/httpx/pull/1032 - """ - - httpx_tuples = [] - for key, file_like in d.items(): - if isinstance(file_like, list): - for file_like_item in file_like: - httpx_tuples.append((key, file_like_item)) - else: - httpx_tuples.append((key, file_like)) - return httpx_tuples - - -def with_content_type(*, file: File, default_content_type: str) -> File: - """ - This function resolves to the file's content type, if provided, and defaults - to the default_content_type value if not. - """ - if isinstance(file, tuple): - if len(file) == 2: - filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, default_content_type) - elif len(file) == 3: - filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - out_content_type = file_content_type or default_content_type - return (filename, content, out_content_type) - elif len(file) == 4: - filename, content, file_content_type, headers = cast( # type: ignore - Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file - ) - out_content_type = file_content_type or default_content_type - return (filename, content, out_content_type, headers) - else: - raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, default_content_type) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py deleted file mode 100644 index 5440913fd4bc..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/force_multipart.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict - - -class ForceMultipartDict(Dict[str, Any]): - """ - A dictionary subclass that always evaluates to True in boolean contexts. - - This is used to force multipart/form-data encoding in HTTP requests even when - the dictionary is empty, which would normally evaluate to False. - """ - - def __bool__(self) -> bool: - return True - - -FORCE_MULTIPART = ForceMultipartDict() diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py deleted file mode 100644 index f0a39ca8243a..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_client.py +++ /dev/null @@ -1,840 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import asyncio -import email.utils -import re -import time -import typing -from contextlib import asynccontextmanager, contextmanager -from random import random - -import httpx -from .file import File, convert_file_dict_to_httpx_tuples -from .force_multipart import FORCE_MULTIPART -from .jsonable_encoder import jsonable_encoder -from .logging import LogConfig, Logger, create_logger -from .query_encoder import encode_query -from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict -from .request_options import RequestOptions -from httpx._types import RequestFiles - -INITIAL_RETRY_DELAY_SECONDS = 1.0 -MAX_RETRY_DELAY_SECONDS = 60.0 -JITTER_FACTOR = 0.2 # 20% random jitter - - -def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: - """ - This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. - - Inspired by the urllib3 retry implementation. - """ - retry_after_ms = response_headers.get("retry-after-ms") - if retry_after_ms is not None: - try: - return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 - except Exception: - pass - - retry_after = response_headers.get("retry-after") - if retry_after is None: - return None - - # Attempt to parse the header as an int. - if re.match(r"^\s*[0-9]+\s*$", retry_after): - seconds = float(retry_after) - # Fallback to parsing it as a date. - else: - retry_date_tuple = email.utils.parsedate_tz(retry_after) - if retry_date_tuple is None: - return None - if retry_date_tuple[9] is None: # Python 2 - # Assume UTC if no timezone was specified - # On Python2.7, parsedate_tz returns None for a timezone offset - # instead of 0 if no timezone is given, where mktime_tz treats - # a None timezone offset as local time. - retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] - - retry_date = email.utils.mktime_tz(retry_date_tuple) - seconds = retry_date - time.time() - - if seconds < 0: - seconds = 0 - - return seconds - - -def _add_positive_jitter(delay: float) -> float: - """Add positive jitter (0-20%) to prevent thundering herd.""" - jitter_multiplier = 1 + random() * JITTER_FACTOR - return delay * jitter_multiplier - - -def _add_symmetric_jitter(delay: float) -> float: - """Add symmetric jitter (±10%) for exponential backoff.""" - jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR - return delay * jitter_multiplier - - -def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]: - """ - Parse the X-RateLimit-Reset header (Unix timestamp in seconds). - Returns seconds to wait, or None if header is missing/invalid. - """ - reset_time_str = response_headers.get("x-ratelimit-reset") - if reset_time_str is None: - return None - - try: - reset_time = int(reset_time_str) - delay = reset_time - time.time() - if delay > 0: - return delay - except (ValueError, TypeError): - pass - - return None - - -def _retry_timeout(response: httpx.Response, retries: int) -> float: - """ - Determine the amount of time to wait before retrying a request. - This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff - with a jitter to determine the number of seconds to wait. - """ - - # 1. Check Retry-After header first - retry_after = _parse_retry_after(response.headers) - if retry_after is not None and retry_after > 0: - return min(retry_after, MAX_RETRY_DELAY_SECONDS) - - # 2. Check X-RateLimit-Reset header (with positive jitter) - ratelimit_reset = _parse_x_ratelimit_reset(response.headers) - if ratelimit_reset is not None: - return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS)) - - # 3. Fall back to exponential backoff (with symmetric jitter) - backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) - return _add_symmetric_jitter(backoff) - - -def _retry_timeout_from_retries(retries: int) -> float: - """Determine retry timeout using exponential backoff when no response is available.""" - backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) - return _add_symmetric_jitter(backoff) - - -def _should_retry(response: httpx.Response) -> bool: - retryable_400s = [429, 408, 409] - return response.status_code >= 500 or response.status_code in retryable_400s - - -_SENSITIVE_HEADERS = frozenset( - { - "authorization", - "www-authenticate", - "x-api-key", - "api-key", - "apikey", - "x-api-token", - "x-auth-token", - "auth-token", - "cookie", - "set-cookie", - "proxy-authorization", - "proxy-authenticate", - "x-csrf-token", - "x-xsrf-token", - "x-session-token", - "x-access-token", - } -) - - -def _redact_headers(headers: typing.Dict[str, str]) -> typing.Dict[str, str]: - return {k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()} - - -def _build_url(base_url: str, path: typing.Optional[str]) -> str: - """ - Build a full URL by joining a base URL with a path. - - This function correctly handles base URLs that contain path prefixes (e.g., tenant-based URLs) - by using string concatenation instead of urllib.parse.urljoin(), which would incorrectly - strip path components when the path starts with '/'. - - Example: - >>> _build_url("https://cloud.example.com/org/tenant/api", "/users") - 'https://cloud.example.com/org/tenant/api/users' - - Args: - base_url: The base URL, which may contain path prefixes. - path: The path to append. Can be None or empty string. - - Returns: - The full URL with base_url and path properly joined. - """ - if not path: - return base_url - return f"{base_url.rstrip('/')}/{path.lstrip('/')}" - - -def _maybe_filter_none_from_multipart_data( - data: typing.Optional[typing.Any], - request_files: typing.Optional[RequestFiles], - force_multipart: typing.Optional[bool], -) -> typing.Optional[typing.Any]: - """ - Filter None values from data body for multipart/form requests. - This prevents httpx from converting None to empty strings in multipart encoding. - Only applies when files are present or force_multipart is True. - """ - if data is not None and isinstance(data, typing.Mapping) and (request_files or force_multipart): - return remove_none_from_dict(data) - return data - - -def remove_omit_from_dict( - original: typing.Dict[str, typing.Optional[typing.Any]], - omit: typing.Optional[typing.Any], -) -> typing.Dict[str, typing.Any]: - if omit is None: - return original - new: typing.Dict[str, typing.Any] = {} - for key, value in original.items(): - if value is not omit: - new[key] = value - return new - - -def maybe_filter_request_body( - data: typing.Optional[typing.Any], - request_options: typing.Optional[RequestOptions], - omit: typing.Optional[typing.Any], -) -> typing.Optional[typing.Any]: - if data is None: - return ( - jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} - if request_options is not None - else None - ) - elif not isinstance(data, typing.Mapping): - data_content = jsonable_encoder(data) - else: - data_content = { - **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore - **( - jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} - if request_options is not None - else {} - ), - } - return data_content - - -# Abstracted out for testing purposes -def get_request_body( - *, - json: typing.Optional[typing.Any], - data: typing.Optional[typing.Any], - request_options: typing.Optional[RequestOptions], - omit: typing.Optional[typing.Any], -) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: - json_body = None - data_body = None - if data is not None: - data_body = maybe_filter_request_body(data, request_options, omit) - else: - # If both data and json are None, we send json data in the event extra properties are specified - json_body = maybe_filter_request_body(json, request_options, omit) - - has_additional_body_parameters = bool( - request_options is not None and request_options.get("additional_body_parameters") - ) - - # Only collapse empty dict to None when the body was not explicitly provided - # and there are no additional body parameters. This preserves explicit empty - # bodies (e.g., when an endpoint has a request body type but all fields are optional). - if json_body == {} and json is None and not has_additional_body_parameters: - json_body = None - if data_body == {} and data is None and not has_additional_body_parameters: - data_body = None - - return json_body, data_body - - -class HttpClient: - def __init__( - self, - *, - httpx_client: httpx.Client, - base_timeout: typing.Callable[[], typing.Optional[float]], - base_headers: typing.Callable[[], typing.Dict[str, str]], - base_url: typing.Optional[typing.Callable[[], str]] = None, - base_max_retries: int = 2, - logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - self.base_url = base_url - self.base_timeout = base_timeout - self.base_headers = base_headers - self.base_max_retries = base_max_retries - self.httpx_client = httpx_client - self.logger = create_logger(logging_config) - - def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: - base_url = maybe_base_url - if self.base_url is not None and base_url is None: - base_url = self.base_url() - - if base_url is None: - raise ValueError("A base_url is required to make this request, please provide one and try again.") - return base_url - - def request( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> httpx.Response: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) or {} - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **self.base_headers(), - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - has_body=json_body is not None or data_body is not None, - ) - - max_retries: int = ( - request_options.get("max_retries", self.base_max_retries) - if request_options is not None - else self.base_max_retries - ) - - try: - response = self.httpx_client.request( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) - except (httpx.ConnectError, httpx.RemoteProtocolError): - if retries < max_retries: - time.sleep(_retry_timeout_from_retries(retries=retries)) - return self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - raise - - if _should_retry(response=response): - if retries < max_retries: - time.sleep(_retry_timeout(response=response, retries=retries)) - return self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - - if self.logger.is_debug(): - if 200 <= response.status_code < 400: - self.logger.debug( - "HTTP request succeeded", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - if self.logger.is_error(): - if response.status_code >= 400: - self.logger.error( - "HTTP request failed with error status", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - return response - - @contextmanager - def stream( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> typing.Iterator[httpx.Response]: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **self.base_headers(), - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making streaming HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - ) - - with self.httpx_client.stream( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) as stream: - yield stream - - -class AsyncHttpClient: - def __init__( - self, - *, - httpx_client: httpx.AsyncClient, - base_timeout: typing.Callable[[], typing.Optional[float]], - base_headers: typing.Callable[[], typing.Dict[str, str]], - base_url: typing.Optional[typing.Callable[[], str]] = None, - base_max_retries: int = 2, - async_base_headers: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]]] = None, - logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, - ): - self.base_url = base_url - self.base_timeout = base_timeout - self.base_headers = base_headers - self.base_max_retries = base_max_retries - self.async_base_headers = async_base_headers - self.httpx_client = httpx_client - self.logger = create_logger(logging_config) - - async def _get_headers(self) -> typing.Dict[str, str]: - if self.async_base_headers is not None: - return await self.async_base_headers() - return self.base_headers() - - def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: - base_url = maybe_base_url - if self.base_url is not None and base_url is None: - base_url = self.base_url() - - if base_url is None: - raise ValueError("A base_url is required to make this request, please provide one and try again.") - return base_url - - async def request( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> httpx.Response: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Get headers (supports async token providers) - _headers = await self._get_headers() - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) or {} - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **_headers, - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - has_body=json_body is not None or data_body is not None, - ) - - max_retries: int = ( - request_options.get("max_retries", self.base_max_retries) - if request_options is not None - else self.base_max_retries - ) - - try: - response = await self.httpx_client.request( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) - except (httpx.ConnectError, httpx.RemoteProtocolError): - if retries < max_retries: - await asyncio.sleep(_retry_timeout_from_retries(retries=retries)) - return await self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - raise - - if _should_retry(response=response): - if retries < max_retries: - await asyncio.sleep(_retry_timeout(response=response, retries=retries)) - return await self.request( - path=path, - method=method, - base_url=base_url, - params=params, - json=json, - data=data, - content=content, - files=files, - headers=headers, - request_options=request_options, - retries=retries + 1, - omit=omit, - force_multipart=force_multipart, - ) - - if self.logger.is_debug(): - if 200 <= response.status_code < 400: - self.logger.debug( - "HTTP request succeeded", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - if self.logger.is_error(): - if response.status_code >= 400: - self.logger.error( - "HTTP request failed with error status", - method=method, - url=_request_url, - status_code=response.status_code, - ) - - return response - - @asynccontextmanager - async def stream( - self, - path: typing.Optional[str] = None, - *, - method: str, - base_url: typing.Optional[str] = None, - params: typing.Optional[typing.Dict[str, typing.Any]] = None, - json: typing.Optional[typing.Any] = None, - data: typing.Optional[typing.Any] = None, - content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, - files: typing.Optional[ - typing.Union[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], - typing.List[typing.Tuple[str, File]], - ] - ] = None, - headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_options: typing.Optional[RequestOptions] = None, - retries: int = 0, - omit: typing.Optional[typing.Any] = None, - force_multipart: typing.Optional[bool] = None, - ) -> typing.AsyncIterator[httpx.Response]: - base_url = self.get_base_url(base_url) - timeout = ( - request_options.get("timeout_in_seconds") - if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout() - ) - - request_files: typing.Optional[RequestFiles] = ( - convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) - if (files is not None and files is not omit and isinstance(files, dict)) - else None - ) - - if (request_files is None or len(request_files) == 0) and force_multipart: - request_files = FORCE_MULTIPART - - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) - - data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) - - # Get headers (supports async token providers) - _headers = await self._get_headers() - - # Compute encoded params separately to avoid passing empty list to httpx - # (httpx strips existing query params from URL when params=[] is passed) - _encoded_params = encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get("additional_query_parameters", {}) - if request_options is not None - else {} - ), - }, - omit=omit, - ) - ) - ) - ) - - _request_url = _build_url(base_url, path) - _request_headers = jsonable_encoder( - remove_none_from_dict( - { - **_headers, - **(headers if headers is not None else {}), - **(request_options.get("additional_headers", {}) if request_options is not None else {}), - } - ) - ) - - if self.logger.is_debug(): - self.logger.debug( - "Making streaming HTTP request", - method=method, - url=_request_url, - headers=_redact_headers(_request_headers), - ) - - async with self.httpx_client.stream( - method=method, - url=_request_url, - headers=_request_headers, - params=_encoded_params if _encoded_params else None, - json=json_body, - data=data_body, - content=content, - files=request_files, - timeout=timeout, - ) as stream: - yield stream diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py deleted file mode 100644 index 00bb1096d2d0..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_response.py +++ /dev/null @@ -1,59 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Dict, Generic, TypeVar - -import httpx - -# Generic to represent the underlying type of the data wrapped by the HTTP response. -T = TypeVar("T") - - -class BaseHttpResponse: - """Minimalist HTTP response wrapper that exposes response headers and status code.""" - - _response: httpx.Response - - def __init__(self, response: httpx.Response): - self._response = response - - @property - def headers(self) -> Dict[str, str]: - return dict(self._response.headers) - - @property - def status_code(self) -> int: - return self._response.status_code - - -class HttpResponse(Generic[T], BaseHttpResponse): - """HTTP response wrapper that exposes response headers and data.""" - - _data: T - - def __init__(self, response: httpx.Response, data: T): - super().__init__(response) - self._data = data - - @property - def data(self) -> T: - return self._data - - def close(self) -> None: - self._response.close() - - -class AsyncHttpResponse(Generic[T], BaseHttpResponse): - """HTTP response wrapper that exposes response headers and data.""" - - _data: T - - def __init__(self, response: httpx.Response, data: T): - super().__init__(response) - self._data = data - - @property - def data(self) -> T: - return self._data - - async def close(self) -> None: - await self._response.aclose() diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py deleted file mode 100644 index 730e5a3382eb..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from ._api import EventSource, aconnect_sse, connect_sse - from ._exceptions import SSEError - from ._models import ServerSentEvent -_dynamic_imports: typing.Dict[str, str] = { - "EventSource": "._api", - "SSEError": "._exceptions", - "ServerSentEvent": "._models", - "aconnect_sse": "._api", - "connect_sse": "._api", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py deleted file mode 100644 index f900b3b686de..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_api.py +++ /dev/null @@ -1,112 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import re -from contextlib import asynccontextmanager, contextmanager -from typing import Any, AsyncGenerator, AsyncIterator, Iterator, cast - -import httpx -from ._decoders import SSEDecoder -from ._exceptions import SSEError -from ._models import ServerSentEvent - - -class EventSource: - def __init__(self, response: httpx.Response) -> None: - self._response = response - - def _check_content_type(self) -> None: - content_type = self._response.headers.get("content-type", "").partition(";")[0] - if "text/event-stream" not in content_type: - raise SSEError( - f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}" - ) - - def _get_charset(self) -> str: - """Extract charset from Content-Type header, fallback to UTF-8.""" - content_type = self._response.headers.get("content-type", "") - - # Parse charset parameter using regex - charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE) - if charset_match: - charset = charset_match.group(1).strip("\"'") - # Validate that it's a known encoding - try: - # Test if the charset is valid by trying to encode/decode - "test".encode(charset).decode(charset) - return charset - except (LookupError, UnicodeError): - # If charset is invalid, fall back to UTF-8 - pass - - # Default to UTF-8 if no charset specified or invalid charset - return "utf-8" - - @property - def response(self) -> httpx.Response: - return self._response - - def iter_sse(self) -> Iterator[ServerSentEvent]: - self._check_content_type() - decoder = SSEDecoder() - charset = self._get_charset() - - buffer = "" - for chunk in self._response.iter_bytes(): - # Decode chunk using detected charset - text_chunk = chunk.decode(charset, errors="replace") - buffer += text_chunk - - # Process complete lines - while "\n" in buffer: - line, buffer = buffer.split("\n", 1) - line = line.rstrip("\r") - sse = decoder.decode(line) - # when we reach a "\n\n" => line = '' - # => decoder will attempt to return an SSE Event - if sse is not None: - yield sse - - # Process any remaining data in buffer - if buffer.strip(): - line = buffer.rstrip("\r") - sse = decoder.decode(line) - if sse is not None: - yield sse - - async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: - self._check_content_type() - decoder = SSEDecoder() - lines = cast(AsyncGenerator[str, None], self._response.aiter_lines()) - try: - async for line in lines: - line = line.rstrip("\n") - sse = decoder.decode(line) - if sse is not None: - yield sse - finally: - await lines.aclose() - - -@contextmanager -def connect_sse(client: httpx.Client, method: str, url: str, **kwargs: Any) -> Iterator[EventSource]: - headers = kwargs.pop("headers", {}) - headers["Accept"] = "text/event-stream" - headers["Cache-Control"] = "no-store" - - with client.stream(method, url, headers=headers, **kwargs) as response: - yield EventSource(response) - - -@asynccontextmanager -async def aconnect_sse( - client: httpx.AsyncClient, - method: str, - url: str, - **kwargs: Any, -) -> AsyncIterator[EventSource]: - headers = kwargs.pop("headers", {}) - headers["Accept"] = "text/event-stream" - headers["Cache-Control"] = "no-store" - - async with client.stream(method, url, headers=headers, **kwargs) as response: - yield EventSource(response) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py deleted file mode 100644 index 339b08901381..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_decoders.py +++ /dev/null @@ -1,61 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import List, Optional - -from ._models import ServerSentEvent - - -class SSEDecoder: - def __init__(self) -> None: - self._event = "" - self._data: List[str] = [] - self._last_event_id = "" - self._retry: Optional[int] = None - - def decode(self, line: str) -> Optional[ServerSentEvent]: - # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 - - if not line: - if not self._event and not self._data and not self._last_event_id and self._retry is None: - return None - - sse = ServerSentEvent( - event=self._event, - data="\n".join(self._data), - id=self._last_event_id, - retry=self._retry, - ) - - # NOTE: as per the SSE spec, do not reset last_event_id. - self._event = "" - self._data = [] - self._retry = None - - return sse - - if line.startswith(":"): - return None - - fieldname, _, value = line.partition(":") - - if value.startswith(" "): - value = value[1:] - - if fieldname == "event": - self._event = value - elif fieldname == "data": - self._data.append(value) - elif fieldname == "id": - if "\0" in value: - pass - else: - self._last_event_id = value - elif fieldname == "retry": - try: - self._retry = int(value) - except (TypeError, ValueError): - pass - else: - pass # Field is ignored. - - return None diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py deleted file mode 100644 index 81605a8a65ed..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_exceptions.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import httpx - - -class SSEError(httpx.TransportError): - pass diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py deleted file mode 100644 index 1af57f8fd0d2..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/http_sse/_models.py +++ /dev/null @@ -1,17 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import json -from dataclasses import dataclass -from typing import Any, Optional - - -@dataclass(frozen=True) -class ServerSentEvent: - event: str = "message" - data: str = "" - id: str = "" - retry: Optional[int] = None - - def json(self) -> Any: - """Parse the data field as JSON.""" - return json.loads(self.data) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py deleted file mode 100644 index 5b0902ebcde3..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py +++ /dev/null @@ -1,120 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -""" -jsonable_encoder converts a Python object to a JSON-friendly dict -(e.g. datetimes to strings, Pydantic models to dicts). - -Taken from FastAPI, and made a bit simpler -https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py -""" - -import base64 -import dataclasses -import datetime as dt -from enum import Enum -from pathlib import PurePath -from types import GeneratorType -from typing import Any, Callable, Dict, List, Optional, Set, Union - -import pydantic -from .datetime_utils import serialize_datetime -from .pydantic_utilities import ( - IS_PYDANTIC_V2, - encode_by_type, - to_jsonable_with_fallback, -) - -SetIntStr = Set[Union[int, str]] -DictIntStrAny = Dict[Union[int, str], Any] - - -def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: - custom_encoder = custom_encoder or {} - # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT". - # OMIT values should be excluded from serialized payloads. - if obj is Ellipsis: - return None - if custom_encoder: - if type(obj) in custom_encoder: - return custom_encoder[type(obj)](obj) - else: - for encoder_type, encoder_instance in custom_encoder.items(): - if isinstance(obj, encoder_type): - return encoder_instance(obj) - if isinstance(obj, pydantic.BaseModel): - if IS_PYDANTIC_V2: - encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 - else: - encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 - if custom_encoder: - encoder.update(custom_encoder) - obj_dict = obj.dict(by_alias=True) - if "__root__" in obj_dict: - obj_dict = obj_dict["__root__"] - if "root" in obj_dict: - obj_dict = obj_dict["root"] - return jsonable_encoder(obj_dict, custom_encoder=encoder) - if dataclasses.is_dataclass(obj): - obj_dict = dataclasses.asdict(obj) # type: ignore - return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) - if isinstance(obj, bytes): - return base64.b64encode(obj).decode("utf-8") - if isinstance(obj, Enum): - return obj.value - if isinstance(obj, PurePath): - return str(obj) - if isinstance(obj, (str, int, float, type(None))): - return obj - if isinstance(obj, dt.datetime): - return serialize_datetime(obj) - if isinstance(obj, dt.date): - return str(obj) - if isinstance(obj, dict): - encoded_dict = {} - allowed_keys = set(obj.keys()) - for key, value in obj.items(): - if key in allowed_keys: - if value is Ellipsis: - continue - encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) - encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) - encoded_dict[encoded_key] = encoded_value - return encoded_dict - if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): - encoded_list = [] - for item in obj: - if item is Ellipsis: - continue - encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) - return encoded_list - - def fallback_serializer(o: Any) -> Any: - attempt_encode = encode_by_type(o) - if attempt_encode is not None: - return attempt_encode - - try: - data = dict(o) - except Exception as e: - errors: List[Exception] = [] - errors.append(e) - try: - data = vars(o) - except Exception as e: - errors.append(e) - raise ValueError(errors) from e - return jsonable_encoder(data, custom_encoder=custom_encoder) - - return to_jsonable_with_fallback(obj, fallback_serializer) - - -def encode_path_param(obj: Any) -> str: - """Encode a value for use in a URL path segment. - - Ensures proper string conversion for all types, including - booleans which need lowercase 'true'/'false' rather than - Python's 'True'/'False'. - """ - if isinstance(obj, bool): - return "true" if obj else "false" - return str(jsonable_encoder(obj)) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py deleted file mode 100644 index e5e572458bc8..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/logging.py +++ /dev/null @@ -1,107 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import logging -import typing - -LogLevel = typing.Literal["debug", "info", "warn", "error"] - -_LOG_LEVEL_MAP: typing.Dict[LogLevel, int] = { - "debug": 1, - "info": 2, - "warn": 3, - "error": 4, -} - - -class ILogger(typing.Protocol): - def debug(self, message: str, **kwargs: typing.Any) -> None: ... - def info(self, message: str, **kwargs: typing.Any) -> None: ... - def warn(self, message: str, **kwargs: typing.Any) -> None: ... - def error(self, message: str, **kwargs: typing.Any) -> None: ... - - -class ConsoleLogger: - _logger: logging.Logger - - def __init__(self) -> None: - self._logger = logging.getLogger("fern") - if not self._logger.handlers: - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s")) - self._logger.addHandler(handler) - self._logger.setLevel(logging.DEBUG) - - def debug(self, message: str, **kwargs: typing.Any) -> None: - self._logger.debug(message, extra=kwargs) - - def info(self, message: str, **kwargs: typing.Any) -> None: - self._logger.info(message, extra=kwargs) - - def warn(self, message: str, **kwargs: typing.Any) -> None: - self._logger.warning(message, extra=kwargs) - - def error(self, message: str, **kwargs: typing.Any) -> None: - self._logger.error(message, extra=kwargs) - - -class LogConfig(typing.TypedDict, total=False): - level: LogLevel - logger: ILogger - silent: bool - - -class Logger: - _level: int - _logger: ILogger - _silent: bool - - def __init__(self, *, level: LogLevel, logger: ILogger, silent: bool) -> None: - self._level = _LOG_LEVEL_MAP[level] - self._logger = logger - self._silent = silent - - def _should_log(self, level: LogLevel) -> bool: - return not self._silent and self._level <= _LOG_LEVEL_MAP[level] - - def is_debug(self) -> bool: - return self._should_log("debug") - - def is_info(self) -> bool: - return self._should_log("info") - - def is_warn(self) -> bool: - return self._should_log("warn") - - def is_error(self) -> bool: - return self._should_log("error") - - def debug(self, message: str, **kwargs: typing.Any) -> None: - if self.is_debug(): - self._logger.debug(message, **kwargs) - - def info(self, message: str, **kwargs: typing.Any) -> None: - if self.is_info(): - self._logger.info(message, **kwargs) - - def warn(self, message: str, **kwargs: typing.Any) -> None: - if self.is_warn(): - self._logger.warn(message, **kwargs) - - def error(self, message: str, **kwargs: typing.Any) -> None: - if self.is_error(): - self._logger.error(message, **kwargs) - - -_default_logger: Logger = Logger(level="info", logger=ConsoleLogger(), silent=True) - - -def create_logger(config: typing.Optional[typing.Union[LogConfig, Logger]] = None) -> Logger: - if config is None: - return _default_logger - if isinstance(config, Logger): - return config - return Logger( - level=config.get("level", "info"), - logger=config.get("logger", ConsoleLogger()), - silent=config.get("silent", True), - ) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py deleted file mode 100644 index 4527c6a8adec..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/parse_error.py +++ /dev/null @@ -1,36 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Optional - - -class ParsingError(Exception): - """ - Raised when the SDK fails to parse/validate a response from the server. - This typically indicates that the server returned a response whose shape - does not match the expected schema. - """ - - headers: Optional[Dict[str, str]] - status_code: Optional[int] - body: Any - cause: Optional[Exception] - - def __init__( - self, - *, - headers: Optional[Dict[str, str]] = None, - status_code: Optional[int] = None, - body: Any = None, - cause: Optional[Exception] = None, - ) -> None: - self.headers = headers - self.status_code = status_code - self.body = body - self.cause = cause - super().__init__() - if cause is not None: - self.__cause__ = cause - - def __str__(self) -> str: - cause_str = f", cause: {self.cause}" if self.cause is not None else "" - return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py deleted file mode 100644 index fea3a08d3268..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/pydantic_utilities.py +++ /dev/null @@ -1,634 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# nopycln: file -import datetime as dt -import inspect -import json -import logging -from collections import defaultdict -from dataclasses import asdict -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - List, - Mapping, - Optional, - Set, - Tuple, - Type, - TypeVar, - Union, - cast, -) - -import pydantic -import typing_extensions -from pydantic.fields import FieldInfo as _FieldInfo - -_logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - from .http_sse._models import ServerSentEvent - -IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") - -if IS_PYDANTIC_V2: - _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] - _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] - - def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value - return _datetime_adapter.validate_python(value) - - def parse_date(value: Any) -> dt.date: # type: ignore[misc] - if isinstance(value, dt.datetime): - return value.date() - if isinstance(value, dt.date): - return value - return _date_adapter.validate_python(value) - - # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. - from typing import get_args as get_args # type: ignore[assignment] - from typing import get_origin as get_origin # type: ignore[assignment] - - def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return typing_extensions.get_origin(tp) is typing_extensions.Literal - - def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] - return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] - - # Inline encoders_by_type to avoid importing from pydantic.v1.json - import re as _re - from collections import deque as _deque - from decimal import Decimal as _Decimal - from enum import Enum as _Enum - from ipaddress import ( - IPv4Address as _IPv4Address, - ) - from ipaddress import ( - IPv4Interface as _IPv4Interface, - ) - from ipaddress import ( - IPv4Network as _IPv4Network, - ) - from ipaddress import ( - IPv6Address as _IPv6Address, - ) - from ipaddress import ( - IPv6Interface as _IPv6Interface, - ) - from ipaddress import ( - IPv6Network as _IPv6Network, - ) - from pathlib import Path as _Path - from types import GeneratorType as _GeneratorType - from uuid import UUID as _UUID - - from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] - - def _decimal_encoder(dec_value: Any) -> Any: - if dec_value.as_tuple().exponent >= 0: - return int(dec_value) - return float(dec_value) - - encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] - bytes: lambda o: o.decode(), - dt.date: lambda o: o.isoformat(), - dt.datetime: lambda o: o.isoformat(), - dt.time: lambda o: o.isoformat(), - dt.timedelta: lambda td: td.total_seconds(), - _Decimal: _decimal_encoder, - _Enum: lambda o: o.value, - frozenset: list, - _deque: list, - _GeneratorType: list, - _IPv4Address: str, - _IPv4Interface: str, - _IPv4Network: str, - _IPv6Address: str, - _IPv6Interface: str, - _IPv6Network: str, - _Path: str, - _re.Pattern: lambda o: o.pattern, - set: list, - _UUID: str, - } -else: - from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] - from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] - from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] - from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] - from pydantic.typing import get_args as get_args # type: ignore[no-redef] - from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] - from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] - from pydantic.typing import is_union as is_union # type: ignore[no-redef] - -from .datetime_utils import serialize_datetime -from .serialization import convert_and_respect_annotation_metadata -from typing_extensions import TypeAlias - -T = TypeVar("T") -Model = TypeVar("Model", bound=pydantic.BaseModel) - - -def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]: - """ - Extract the discriminator field name and union variants from a discriminated union type. - Supports Annotated[Union[...], Field(discriminator=...)] patterns. - Returns (discriminator, variants) or (None, None) if not a discriminated union. - """ - origin = typing_extensions.get_origin(type_) - - if origin is typing_extensions.Annotated: - args = typing_extensions.get_args(type_) - if len(args) >= 2: - inner_type = args[0] - # Check annotations for discriminator - discriminator = None - for annotation in args[1:]: - if hasattr(annotation, "discriminator"): - discriminator = getattr(annotation, "discriminator", None) - break - - if discriminator: - inner_origin = typing_extensions.get_origin(inner_type) - if inner_origin is Union: - variants = list(typing_extensions.get_args(inner_type)) - return discriminator, variants - return None, None - - -def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]: - """Get the type annotation of a field from a Pydantic model.""" - if IS_PYDANTIC_V2: - fields = getattr(model, "model_fields", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.annotation) - else: - fields = getattr(model, "__fields__", {}) - field_info = fields.get(field_name) - if field_info: - return cast(Optional[Type[Any]], field_info.outer_type_) - return None - - -def _find_variant_by_discriminator( - variants: List[Type[Any]], - discriminator: str, - discriminator_value: Any, -) -> Optional[Type[Any]]: - """Find the union variant that matches the discriminator value.""" - for variant in variants: - if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)): - continue - - disc_annotation = _get_field_annotation(variant, discriminator) - if disc_annotation and is_literal_type(disc_annotation): - literal_args = get_args(disc_annotation) - if literal_args and literal_args[0] == discriminator_value: - return variant - return None - - -def _is_string_type(type_: Type[Any]) -> bool: - """Check if a type is str or Optional[str].""" - if type_ is str: - return True - - origin = typing_extensions.get_origin(type_) - if origin is Union: - args = typing_extensions.get_args(type_) - # Optional[str] = Union[str, None] - non_none_args = [a for a in args if a is not type(None)] - if len(non_none_args) == 1 and non_none_args[0] is str: - return True - - return False - - -def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: - """ - Parse a ServerSentEvent into the appropriate type. - - Handles two scenarios based on where the discriminator field is located: - - 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload. - The union describes the data content, not the SSE envelope. - -> Returns: json.loads(data) parsed into the type - - Example: ChatStreamResponse with discriminator='type' - Input: ServerSentEvent(event="message", data='{"type": "content-delta", ...}', id="") - Output: ContentDeltaEvent (parsed from data, SSE envelope stripped) - - 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level. - The union describes the full SSE event structure. - -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string - - Example: JobStreamResponse with discriminator='event' - Input: ServerSentEvent(event="ERROR", data='{"code": "FAILED", ...}', id="123") - Output: JobStreamResponse_Error with data as ErrorData object - - But for variants where data is str (like STATUS_UPDATE): - Input: ServerSentEvent(event="STATUS_UPDATE", data='{"status": "processing"}', id="1") - Output: JobStreamResponse_StatusUpdate with data as string (not parsed) - - Args: - sse: The ServerSentEvent object to parse - type_: The target discriminated union type - - Returns: - The parsed object of type T - - Note: - This function is only available in SDK contexts where http_sse module exists. - """ - sse_event = asdict(sse) - discriminator, variants = _get_discriminator_and_variants(type_) - - if discriminator is None or variants is None: - # Not a discriminated union - parse the data field as JSON - data_value = sse_event.get("data") - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - data_value = sse_event.get("data") - - # Check if discriminator is at the top level (event-level discrimination) - if discriminator in sse_event: - # Case 2: Event-level discrimination - # Find the matching variant to check if 'data' field needs JSON parsing - disc_value = sse_event.get(discriminator) - matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value) - - if matching_variant is not None: - # Check what type the variant expects for 'data' - data_type = _get_field_annotation(matching_variant, "data") - if data_type is not None and not _is_string_type(data_type): - # Variant expects non-string data - parse JSON - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - new_object = dict(sse_event) - new_object["data"] = parsed_data - return parse_obj_as(type_, new_object) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - # Either no matching variant, data is string type, or JSON parse failed - return parse_obj_as(type_, sse_event) - - else: - # Case 1: Data-level discrimination - # The discriminator is inside the data payload - extract and parse data only - if isinstance(data_value, str) and data_value: - try: - parsed_data = json.loads(data_value) - return parse_obj_as(type_, parsed_data) - except json.JSONDecodeError as e: - _logger.warning( - "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s", - e, - data_value[:100] if len(data_value) > 100 else data_value, - ) - return parse_obj_as(type_, sse_event) - - -def parse_obj_as(type_: Type[T], object_: Any) -> T: - # convert_and_respect_annotation_metadata is required for TypedDict aliasing. - # - # For Pydantic models, whether we should pre-dealias depends on how the model encodes aliasing: - # - If the model uses real Pydantic aliases (pydantic.Field(alias=...)), then we must pass wire keys through - # unchanged so Pydantic can validate them. - # - If the model encodes aliasing only via FieldMetadata annotations, then we MUST pre-dealias because Pydantic - # will not recognize those aliases during validation. - if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): - has_pydantic_aliases = False - if IS_PYDANTIC_V2: - for field_name, field_info in getattr(type_, "model_fields", {}).items(): # type: ignore[attr-defined] - alias = getattr(field_info, "alias", None) - if alias is not None and alias != field_name: - has_pydantic_aliases = True - break - else: - for field in getattr(type_, "__fields__", {}).values(): - alias = getattr(field, "alias", None) - name = getattr(field, "name", None) - if alias is not None and name is not None and alias != name: - has_pydantic_aliases = True - break - - dealiased_object = ( - object_ - if has_pydantic_aliases - else convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") - ) - else: - dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") - if IS_PYDANTIC_V2: - adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] - return adapter.validate_python(dealiased_object) - return pydantic.parse_obj_as(type_, dealiased_object) - - -def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: - if IS_PYDANTIC_V2: - from pydantic_core import to_jsonable_python - - return to_jsonable_python(obj, fallback=fallback_serializer) - return fallback_serializer(obj) - - -class UniversalBaseModel(pydantic.BaseModel): - if IS_PYDANTIC_V2: - model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] - # Allow fields beginning with `model_` to be used in the model - protected_namespaces=(), - ) - - @pydantic.model_validator(mode="before") # type: ignore[attr-defined] - @classmethod - def _coerce_field_names_to_aliases(cls, data: Any) -> Any: - """ - Accept Python field names in input by rewriting them to their Pydantic aliases, - while avoiding silent collisions when a key could refer to multiple fields. - """ - if not isinstance(data, Mapping): - return data - - fields = getattr(cls, "model_fields", {}) # type: ignore[attr-defined] - name_to_alias: Dict[str, str] = {} - alias_to_name: Dict[str, str] = {} - - for name, field_info in fields.items(): - alias = getattr(field_info, "alias", None) or name - name_to_alias[name] = alias - if alias != name: - alias_to_name[alias] = name - - # Detect ambiguous keys: a key that is an alias for one field and a name for another. - ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) - for key in ambiguous_keys: - if key in data and name_to_alias[key] not in data: - raise ValueError( - f"Ambiguous input key '{key}': it is both a field name and an alias. " - "Provide the explicit alias key to disambiguate." - ) - - original_keys = set(data.keys()) - rewritten: Dict[str, Any] = dict(data) - for name, alias in name_to_alias.items(): - if alias != name and name in original_keys and alias not in rewritten: - rewritten[alias] = rewritten.pop(name) - - return rewritten - - @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] - def serialize_model(self) -> Any: # type: ignore[name-defined] - serialized = self.dict() # type: ignore[attr-defined] - data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} - return data - - else: - - class Config: - smart_union = True - json_encoders = {dt.datetime: serialize_datetime} - - @pydantic.root_validator(pre=True) - def _coerce_field_names_to_aliases(cls, values: Any) -> Any: - """ - Pydantic v1 equivalent of _coerce_field_names_to_aliases. - """ - if not isinstance(values, Mapping): - return values - - fields = getattr(cls, "__fields__", {}) - name_to_alias: Dict[str, str] = {} - alias_to_name: Dict[str, str] = {} - - for name, field in fields.items(): - alias = getattr(field, "alias", None) or name - name_to_alias[name] = alias - if alias != name: - alias_to_name[alias] = name - - ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) - for key in ambiguous_keys: - if key in values and name_to_alias[key] not in values: - raise ValueError( - f"Ambiguous input key '{key}': it is both a field name and an alias. " - "Provide the explicit alias key to disambiguate." - ) - - original_keys = set(values.keys()) - rewritten: Dict[str, Any] = dict(values) - for name, alias in name_to_alias.items(): - if alias != name and name in original_keys and alias not in rewritten: - rewritten[alias] = rewritten.pop(name) - - return rewritten - - @classmethod - def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": - dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") - return cls.construct(_fields_set, **dealiased_object) - - @classmethod - def construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": - dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") - if IS_PYDANTIC_V2: - return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc] - return super().construct(_fields_set, **dealiased_object) - - def json(self, **kwargs: Any) -> str: - kwargs_with_defaults = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - if IS_PYDANTIC_V2: - return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: Any) -> Dict[str, Any]: - """ - Override the default dict method to `exclude_unset` by default. This function patches - `exclude_unset` to work include fields within non-None default values. - """ - # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 - # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. - # - # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models - # that we have less control over, and this is less intrusive than custom serializers for now. - if IS_PYDANTIC_V2: - kwargs_with_defaults_exclude_unset = { - **kwargs, - "by_alias": True, - "exclude_unset": True, - "exclude_none": False, - } - kwargs_with_defaults_exclude_none = { - **kwargs, - "by_alias": True, - "exclude_none": True, - "exclude_unset": False, - } - dict_dump = deep_union_pydantic_dicts( - super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] - super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] - ) - - else: - _fields_set = self.__fields_set__.copy() - - fields = _get_model_fields(self.__class__) - for name, field in fields.items(): - if name not in _fields_set: - default = _get_field_default(field) - - # If the default values are non-null act like they've been set - # This effectively allows exclude_unset to work like exclude_none where - # the latter passes through intentionally set none values. - if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): - _fields_set.add(name) - - if default is not None: - self.__fields_set__.add(name) - - kwargs_with_defaults_exclude_unset_include_fields = { - "by_alias": True, - "exclude_unset": True, - "include": _fields_set, - **kwargs, - } - - dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) - - return cast( - Dict[str, Any], - convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write"), - ) - - -def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: - converted_list: List[Any] = [] - for i, item in enumerate(source): - destination_value = destination[i] - if isinstance(item, dict): - converted_list.append(deep_union_pydantic_dicts(item, destination_value)) - elif isinstance(item, list): - converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) - else: - converted_list.append(item) - return converted_list - - -def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: - for key, value in source.items(): - node = destination.setdefault(key, {}) - if isinstance(value, dict): - deep_union_pydantic_dicts(value, node) - # Note: we do not do this same processing for sets given we do not have sets of models - # and given the sets are unordered, the processing of the set and matching objects would - # be non-trivial. - elif isinstance(value, list): - destination[key] = _union_list_of_pydantic_dicts(value, node) - else: - destination[key] = value - - return destination - - -if IS_PYDANTIC_V2: - - class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg] - pass - - UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc] -else: - UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] - - -def encode_by_type(o: Any) -> Any: - encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) - for type_, encoder in encoders_by_type.items(): - encoders_by_class_tuples[encoder] += (type_,) - - if type(o) in encoders_by_type: - return encoders_by_type[type(o)](o) - for encoder, classes_tuple in encoders_by_class_tuples.items(): - if isinstance(o, classes_tuple): - return encoder(o) - - -def update_forward_refs(model: Type["Model"], **localns: Any) -> None: - if IS_PYDANTIC_V2: - model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] - else: - model.update_forward_refs(**localns) - - -# Mirrors Pydantic's internal typing -AnyCallable = Callable[..., Any] - - -def universal_root_validator( - pre: bool = False, -) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - # In Pydantic v2, for RootModel we always use "before" mode - # The custom validators transform the input value before the model is created - return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] - - return decorator - - -def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: - def decorator(func: AnyCallable) -> AnyCallable: - if IS_PYDANTIC_V2: - return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] - return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) - - return decorator - - -PydanticField = Union[ModelField, _FieldInfo] - - -def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: - if IS_PYDANTIC_V2: - return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] - return cast(Mapping[str, PydanticField], model.__fields__) - - -def _get_field_default(field: PydanticField) -> Any: - try: - value = field.get_default() # type: ignore[union-attr] - except: - value = field.default - if IS_PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None - return value - return value diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py deleted file mode 100644 index 3183001d4046..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/query_encoder.py +++ /dev/null @@ -1,58 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, List, Optional, Tuple - -import pydantic - - -# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict -def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: - result = [] - for k, v in dict_flat.items(): - key = f"{key_prefix}[{k}]" if key_prefix is not None else k - if isinstance(v, dict): - result.extend(traverse_query_dict(v, key)) - elif isinstance(v, list): - for arr_v in v: - if isinstance(arr_v, dict): - result.extend(traverse_query_dict(arr_v, key)) - else: - result.append((key, arr_v)) - else: - result.append((key, v)) - return result - - -def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: - if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): - if isinstance(query_value, pydantic.BaseModel): - obj_dict = query_value.dict(by_alias=True) - else: - obj_dict = query_value - return traverse_query_dict(obj_dict, query_key) - elif isinstance(query_value, list): - encoded_values: List[Tuple[str, Any]] = [] - for value in query_value: - if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): - if isinstance(value, pydantic.BaseModel): - obj_dict = value.dict(by_alias=True) - elif isinstance(value, dict): - obj_dict = value - - encoded_values.extend(single_query_encoder(query_key, obj_dict)) - else: - encoded_values.append((query_key, value)) - - return encoded_values - - return [(query_key, query_value)] - - -def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: - if query is None: - return None - - encoded_query = [] - for k, v in query.items(): - encoded_query.extend(single_query_encoder(k, v)) - return encoded_query diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py deleted file mode 100644 index c2298143f14a..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/remove_none_from_dict.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Mapping, Optional - - -def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: - new: Dict[str, Any] = {} - for key, value in original.items(): - if value is not None: - new[key] = value - return new diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py deleted file mode 100644 index 1b38804432ba..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/request_options.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -try: - from typing import NotRequired # type: ignore -except ImportError: - from typing_extensions import NotRequired - - -class RequestOptions(typing.TypedDict, total=False): - """ - Additional options for request-specific configuration when calling APIs via the SDK. - This is used primarily as an optional final parameter for service functions. - - Attributes: - - timeout_in_seconds: int. The number of seconds to await an API call before timing out. - - - max_retries: int. The max number of retries to attempt if the API call fails. - - - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict - - - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict - - - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict - - - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. - """ - - timeout_in_seconds: NotRequired[int] - max_retries: NotRequired[int] - additional_headers: NotRequired[typing.Dict[str, typing.Any]] - additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] - additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] - chunk_size: NotRequired[int] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py deleted file mode 100644 index c36e865cc729..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/core/serialization.py +++ /dev/null @@ -1,276 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import collections -import inspect -import typing - -import pydantic -import typing_extensions - - -class FieldMetadata: - """ - Metadata class used to annotate fields to provide additional information. - - Example: - class MyDict(TypedDict): - field: typing.Annotated[str, FieldMetadata(alias="field_name")] - - Will serialize: `{"field": "value"}` - To: `{"field_name": "value"}` - """ - - alias: str - - def __init__(self, *, alias: str) -> None: - self.alias = alias - - -def convert_and_respect_annotation_metadata( - *, - object_: typing.Any, - annotation: typing.Any, - inner_type: typing.Optional[typing.Any] = None, - direction: typing.Literal["read", "write"], -) -> typing.Any: - """ - Respect the metadata annotations on a field, such as aliasing. This function effectively - manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for - TypedDicts, which cannot support aliasing out of the box, and can be extended for additional - utilities, such as defaults. - - Parameters - ---------- - object_ : typing.Any - - annotation : type - The type we're looking to apply typing annotations from - - inner_type : typing.Optional[type] - - Returns - ------- - typing.Any - """ - - if object_ is None: - return None - if inner_type is None: - inner_type = annotation - - clean_type = _remove_annotations(inner_type) - # Pydantic models - if ( - inspect.isclass(clean_type) - and issubclass(clean_type, pydantic.BaseModel) - and isinstance(object_, typing.Mapping) - ): - return _convert_mapping(object_, clean_type, direction) - # TypedDicts - if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): - return _convert_mapping(object_, clean_type, direction) - - if ( - typing_extensions.get_origin(clean_type) == typing.Dict - or typing_extensions.get_origin(clean_type) == dict - or clean_type == typing.Dict - ) and isinstance(object_, typing.Dict): - key_type = typing_extensions.get_args(clean_type)[0] - value_type = typing_extensions.get_args(clean_type)[1] - - return { - key: convert_and_respect_annotation_metadata( - object_=value, - annotation=annotation, - inner_type=value_type, - direction=direction, - ) - for key, value in object_.items() - } - - # If you're iterating on a string, do not bother to coerce it to a sequence. - if not isinstance(object_, str): - if ( - typing_extensions.get_origin(clean_type) == typing.Set - or typing_extensions.get_origin(clean_type) == set - or clean_type == typing.Set - ) and isinstance(object_, typing.Set): - inner_type = typing_extensions.get_args(clean_type)[0] - return { - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - } - elif ( - ( - typing_extensions.get_origin(clean_type) == typing.List - or typing_extensions.get_origin(clean_type) == list - or clean_type == typing.List - ) - and isinstance(object_, typing.List) - ) or ( - ( - typing_extensions.get_origin(clean_type) == typing.Sequence - or typing_extensions.get_origin(clean_type) == collections.abc.Sequence - or clean_type == typing.Sequence - ) - and isinstance(object_, typing.Sequence) - ): - inner_type = typing_extensions.get_args(clean_type)[0] - return [ - convert_and_respect_annotation_metadata( - object_=item, - annotation=annotation, - inner_type=inner_type, - direction=direction, - ) - for item in object_ - ] - - if typing_extensions.get_origin(clean_type) == typing.Union: - # We should be able to ~relatively~ safely try to convert keys against all - # member types in the union, the edge case here is if one member aliases a field - # of the same name to a different name from another member - # Or if another member aliases a field of the same name that another member does not. - for member in typing_extensions.get_args(clean_type): - object_ = convert_and_respect_annotation_metadata( - object_=object_, - annotation=annotation, - inner_type=member, - direction=direction, - ) - return object_ - - annotated_type = _get_annotation(annotation) - if annotated_type is None: - return object_ - - # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) - # Then we can safely call it on the recursive conversion. - return object_ - - -def _convert_mapping( - object_: typing.Mapping[str, object], - expected_type: typing.Any, - direction: typing.Literal["read", "write"], -) -> typing.Mapping[str, object]: - converted_object: typing.Dict[str, object] = {} - try: - annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) - except NameError: - # The TypedDict contains a circular reference, so - # we use the __annotations__ attribute directly. - annotations = getattr(expected_type, "__annotations__", {}) - aliases_to_field_names = _get_alias_to_field_name(annotations) - for key, value in object_.items(): - if direction == "read" and key in aliases_to_field_names: - dealiased_key = aliases_to_field_names.get(key) - if dealiased_key is not None: - type_ = annotations.get(dealiased_key) - else: - type_ = annotations.get(key) - # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map - # - # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias - # then we can just pass the value through as is - if type_ is None: - converted_object[key] = value - elif direction == "read" and key not in aliases_to_field_names: - converted_object[key] = convert_and_respect_annotation_metadata( - object_=value, annotation=type_, direction=direction - ) - else: - converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( - convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) - ) - return converted_object - - -def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return None - - if maybe_annotated_type == typing_extensions.NotRequired: - type_ = typing_extensions.get_args(type_)[0] - maybe_annotated_type = typing_extensions.get_origin(type_) - - if maybe_annotated_type == typing_extensions.Annotated: - return type_ - - return None - - -def _remove_annotations(type_: typing.Any) -> typing.Any: - maybe_annotated_type = typing_extensions.get_origin(type_) - if maybe_annotated_type is None: - return type_ - - if maybe_annotated_type == typing_extensions.NotRequired: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - if maybe_annotated_type == typing_extensions.Annotated: - return _remove_annotations(typing_extensions.get_args(type_)[0]) - - return type_ - - -def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_alias_to_field_name(annotations) - - -def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: - annotations = typing_extensions.get_type_hints(type_, include_extras=True) - return _get_field_to_alias_name(annotations) - - -def _get_alias_to_field_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[maybe_alias] = field - return aliases - - -def _get_field_to_alias_name( - field_to_hint: typing.Dict[str, typing.Any], -) -> typing.Dict[str, str]: - aliases = {} - for field, hint in field_to_hint.items(): - maybe_alias = _get_alias_from_type(hint) - if maybe_alias is not None: - aliases[field] = maybe_alias - return aliases - - -def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: - maybe_annotated_type = _get_annotation(type_) - - if maybe_annotated_type is not None: - # The actual annotations are 1 onward, the first is the annotated type - annotations = typing_extensions.get_args(maybe_annotated_type)[1:] - - for annotation in annotations: - if isinstance(annotation, FieldMetadata) and annotation.alias is not None: - return annotation.alias - return None - - -def _alias_key( - key: str, - type_: typing.Any, - direction: typing.Literal["read", "write"], - aliases_to_field_names: typing.Dict[str, str], -) -> str: - if direction == "read": - return aliases_to_field_names.get(key, key) - return _get_alias_from_type(type_=type_) or key diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/environment.py b/seed/python-sdk/allof/no-custom-config/src/seed/environment.py deleted file mode 100644 index c64358bdc3c7..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/environment.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import enum - - -class SeedApiEnvironment(enum.Enum): - DEFAULT = "https://api.example.com" diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/py.typed b/seed/python-sdk/allof/no-custom-config/src/seed/py.typed deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py b/seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py deleted file mode 100644 index 2e2501a1fd92..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/raw_client.py +++ /dev/null @@ -1,451 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from .core.api_error import ApiError -from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .core.http_response import AsyncHttpResponse, HttpResponse -from .core.parse_error import ParsingError -from .core.pydantic_utilities import parse_obj_as -from .core.request_options import RequestOptions -from .types.combined_entity import CombinedEntity -from .types.organization import Organization -from .types.rule_execution_context import RuleExecutionContext -from .types.rule_response import RuleResponse -from .types.rule_type_search_response import RuleTypeSearchResponse -from .types.user_search_response import UserSearchResponse -from pydantic import ValidationError - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class RawSeedApi: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[RuleTypeSearchResponse]: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[RuleTypeSearchResponse] - Paginated list of rule types - """ - _response = self._client_wrapper.httpx_client.request( - "rule-types", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleTypeSearchResponse, - parse_obj_as( - type_=RuleTypeSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[RuleResponse]: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[RuleResponse] - Created rule - """ - _response = self._client_wrapper.httpx_client.request( - "rules", - method="POST", - json={ - "name": name, - "executionContext": execution_context, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleResponse, - parse_obj_as( - type_=RuleResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def list_users( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[UserSearchResponse]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[UserSearchResponse] - Paginated list of users - """ - _response = self._client_wrapper.httpx_client.request( - "users", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - UserSearchResponse, - parse_obj_as( - type_=UserSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def get_entity(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[CombinedEntity]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[CombinedEntity] - An entity with properties from multiple parents - """ - _response = self._client_wrapper.httpx_client.request( - "entities", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - CombinedEntity, - parse_obj_as( - type_=CombinedEntity, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - def get_organization( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[Organization]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[Organization] - An organization whose metadata is merged from two parents - """ - _response = self._client_wrapper.httpx_client.request( - "organizations", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - Organization, - parse_obj_as( - type_=Organization, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - -class AsyncRawSeedApi: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def search_rule_types( - self, *, query: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[RuleTypeSearchResponse]: - """ - Parameters - ---------- - query : typing.Optional[str] - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[RuleTypeSearchResponse] - Paginated list of rule types - """ - _response = await self._client_wrapper.httpx_client.request( - "rule-types", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleTypeSearchResponse, - parse_obj_as( - type_=RuleTypeSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def create_rule( - self, - *, - name: str, - execution_context: RuleExecutionContext, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[RuleResponse]: - """ - Parameters - ---------- - name : str - - execution_context : RuleExecutionContext - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[RuleResponse] - Created rule - """ - _response = await self._client_wrapper.httpx_client.request( - "rules", - method="POST", - json={ - "name": name, - "executionContext": execution_context, - }, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - RuleResponse, - parse_obj_as( - type_=RuleResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def list_users( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[UserSearchResponse]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[UserSearchResponse] - Paginated list of users - """ - _response = await self._client_wrapper.httpx_client.request( - "users", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - UserSearchResponse, - parse_obj_as( - type_=UserSearchResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def get_entity( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[CombinedEntity]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[CombinedEntity] - An entity with properties from multiple parents - """ - _response = await self._client_wrapper.httpx_client.request( - "entities", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - CombinedEntity, - parse_obj_as( - type_=CombinedEntity, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - async def get_organization( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[Organization]: - """ - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[Organization] - An organization whose metadata is merged from two parents - """ - _response = await self._client_wrapper.httpx_client.request( - "organizations", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - Organization, - parse_obj_as( - type_=Organization, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - except ValidationError as e: - raise ParsingError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py deleted file mode 100644 index 94ad021213b9..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/__init__.py +++ /dev/null @@ -1,92 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - -import typing -from importlib import import_module - -if typing.TYPE_CHECKING: - from .audit_info import AuditInfo - from .base_org import BaseOrg - from .base_org_metadata import BaseOrgMetadata - from .combined_entity import CombinedEntity - from .combined_entity_status import CombinedEntityStatus - from .describable import Describable - from .detailed_org import DetailedOrg - from .detailed_org_metadata import DetailedOrgMetadata - from .identifiable import Identifiable - from .organization import Organization - from .paginated_result import PaginatedResult - from .paging_cursors import PagingCursors - from .rule_execution_context import RuleExecutionContext - from .rule_response import RuleResponse - from .rule_response_status import RuleResponseStatus - from .rule_type import RuleType - from .rule_type_search_response import RuleTypeSearchResponse - from .user import User - from .user_search_response import UserSearchResponse -_dynamic_imports: typing.Dict[str, str] = { - "AuditInfo": ".audit_info", - "BaseOrg": ".base_org", - "BaseOrgMetadata": ".base_org_metadata", - "CombinedEntity": ".combined_entity", - "CombinedEntityStatus": ".combined_entity_status", - "Describable": ".describable", - "DetailedOrg": ".detailed_org", - "DetailedOrgMetadata": ".detailed_org_metadata", - "Identifiable": ".identifiable", - "Organization": ".organization", - "PaginatedResult": ".paginated_result", - "PagingCursors": ".paging_cursors", - "RuleExecutionContext": ".rule_execution_context", - "RuleResponse": ".rule_response", - "RuleResponseStatus": ".rule_response_status", - "RuleType": ".rule_type", - "RuleTypeSearchResponse": ".rule_type_search_response", - "User": ".user", - "UserSearchResponse": ".user_search_response", -} - - -def __getattr__(attr_name: str) -> typing.Any: - module_name = _dynamic_imports.get(attr_name) - if module_name is None: - raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") - try: - module = import_module(module_name, __package__) - if module_name == f".{attr_name}": - return module - else: - return getattr(module, attr_name) - except ImportError as e: - raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e - except AttributeError as e: - raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e - - -def __dir__(): - lazy_attrs = list(_dynamic_imports.keys()) - return sorted(lazy_attrs) - - -__all__ = [ - "AuditInfo", - "BaseOrg", - "BaseOrgMetadata", - "CombinedEntity", - "CombinedEntityStatus", - "Describable", - "DetailedOrg", - "DetailedOrgMetadata", - "Identifiable", - "Organization", - "PaginatedResult", - "PagingCursors", - "RuleExecutionContext", - "RuleResponse", - "RuleResponseStatus", - "RuleType", - "RuleTypeSearchResponse", - "User", - "UserSearchResponse", -] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py deleted file mode 100644 index 49474bbcdeeb..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/audit_info.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -import pydantic -import typing_extensions -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata - - -class AuditInfo(UniversalBaseModel): - """ - Common audit metadata. - """ - - created_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="createdBy"), - pydantic.Field(alias="createdBy", description="The user who created this resource."), - ] = None - created_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="createdDateTime"), - pydantic.Field(alias="createdDateTime", description="When this resource was created."), - ] = None - modified_by: typing_extensions.Annotated[ - typing.Optional[str], - FieldMetadata(alias="modifiedBy"), - pydantic.Field(alias="modifiedBy", description="The user who last modified this resource."), - ] = None - modified_date_time: typing_extensions.Annotated[ - typing.Optional[dt.datetime], - FieldMetadata(alias="modifiedDateTime"), - pydantic.Field(alias="modifiedDateTime", description="When this resource was last modified."), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py deleted file mode 100644 index 9d77974841d4..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .base_org_metadata import BaseOrgMetadata - - -class BaseOrg(UniversalBaseModel): - id: str - metadata: typing.Optional[BaseOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py deleted file mode 100644 index 69449ce2a499..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/base_org_metadata.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class BaseOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from BaseOrg. - """ - - tier: typing.Optional[str] = pydantic.Field(default=None) - """ - Subscription tier. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py deleted file mode 100644 index 11accd97dd63..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity.py +++ /dev/null @@ -1,34 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .combined_entity_status import CombinedEntityStatus - - -class CombinedEntity(UniversalBaseModel): - status: CombinedEntityStatus - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Identifiable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py deleted file mode 100644 index 42ac60430cd7..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/combined_entity_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -CombinedEntityStatus = typing.Union[typing.Literal["active", "archived"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py deleted file mode 100644 index 2b6cafc913ee..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/describable.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Describable(UniversalBaseModel): - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Describable. - """ - - summary: typing.Optional[str] = pydantic.Field(default=None) - """ - A short summary. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py deleted file mode 100644 index 744634c71d46..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .detailed_org_metadata import DetailedOrgMetadata - - -class DetailedOrg(UniversalBaseModel): - metadata: typing.Optional[DetailedOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py deleted file mode 100644 index f0f2029eb21c..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/detailed_org_metadata.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class DetailedOrgMetadata(UniversalBaseModel): - region: str = pydantic.Field() - """ - Deployment region from DetailedOrg. - """ - - domain: typing.Optional[str] = pydantic.Field(default=None) - """ - Custom domain name. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py deleted file mode 100644 index b88d9d997229..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/identifiable.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class Identifiable(UniversalBaseModel): - id: str = pydantic.Field() - """ - Unique identifier. - """ - - name: typing.Optional[str] = pydantic.Field(default=None) - """ - Display name from Identifiable. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py deleted file mode 100644 index 7a590d908e14..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/organization.py +++ /dev/null @@ -1,22 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .base_org_metadata import BaseOrgMetadata - - -class Organization(UniversalBaseModel): - name: str - id: str - metadata: typing.Optional[BaseOrgMetadata] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py deleted file mode 100644 index 56a3858ffbde..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/paginated_result.py +++ /dev/null @@ -1,24 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors - - -class PaginatedResult(UniversalBaseModel): - paging: PagingCursors - results: typing.List[typing.Any] = pydantic.Field() - """ - Current page of results from the requested resource. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py deleted file mode 100644 index f786d5462fea..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/paging_cursors.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class PagingCursors(UniversalBaseModel): - next: str = pydantic.Field() - """ - Cursor for the next page of results. - """ - - previous: typing.Optional[str] = pydantic.Field(default=None) - """ - Cursor for the previous page of results. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py deleted file mode 100644 index 1d22ed9cabf8..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_execution_context.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleExecutionContext = typing.Union[typing.Literal["prod", "staging", "dev"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py deleted file mode 100644 index ae6e7a5ad7ca..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response.py +++ /dev/null @@ -1,31 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -import typing_extensions -from ..core.pydantic_utilities import IS_PYDANTIC_V2 -from ..core.serialization import FieldMetadata -from .audit_info import AuditInfo -from .rule_execution_context import RuleExecutionContext -from .rule_response_status import RuleResponseStatus - - -class RuleResponse(AuditInfo): - id: str - name: str - status: RuleResponseStatus - execution_context: typing_extensions.Annotated[ - typing.Optional[RuleExecutionContext], - FieldMetadata(alias="executionContext"), - pydantic.Field(alias="executionContext"), - ] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py deleted file mode 100644 index 4cbd106638cb..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_response_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -RuleResponseStatus = typing.Union[typing.Literal["active", "inactive", "draft"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py deleted file mode 100644 index a5543e06211c..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class RuleType(UniversalBaseModel): - id: str - name: str - description: typing.Optional[str] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py deleted file mode 100644 index 1c4401f421b6..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/rule_type_search_response.py +++ /dev/null @@ -1,26 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .rule_type import RuleType - - -class RuleTypeSearchResponse(UniversalBaseModel): - results: typing.Optional[typing.List[RuleType]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - paging: PagingCursors - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/user.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/user.py deleted file mode 100644 index 5421e32870d3..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/user.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class User(UniversalBaseModel): - id: str - email: str - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py b/seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py deleted file mode 100644 index b361b76c9f3d..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/types/user_search_response.py +++ /dev/null @@ -1,26 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .paging_cursors import PagingCursors -from .user import User - - -class UserSearchResponse(UniversalBaseModel): - results: typing.Optional[typing.List[User]] = pydantic.Field(default=None) - """ - Current page of results from the requested resource. - """ - - paging: PagingCursors - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/version.py b/seed/python-sdk/allof/no-custom-config/src/seed/version.py deleted file mode 100644 index 33a8861c5d9e..000000000000 --- a/seed/python-sdk/allof/no-custom-config/src/seed/version.py +++ /dev/null @@ -1,3 +0,0 @@ -from importlib import metadata - -__version__ = metadata.version("fern_allof") diff --git a/seed/python-sdk/allof/no-custom-config/tests/conftest.py b/seed/python-sdk/allof/no-custom-config/tests/conftest.py deleted file mode 100644 index 9e586af3b2d4..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/conftest.py +++ /dev/null @@ -1,147 +0,0 @@ -""" -Pytest plugin that manages the WireMock container lifecycle for wire tests. - -This plugin is loaded globally for the test suite and is responsible for -starting and stopping the WireMock container exactly once per test run, -including when running with pytest-xdist over the entire project. - -It lives under tests/ (as tests/conftest.py) and is discovered automatically -by pytest's normal test collection rules. -""" - -import os -import subprocess - -import pytest - -_STARTED: bool = False -_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle) -_WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts -_PROJECT_NAME: str = "seed-api" - -# This file lives at tests/conftest.py, so the project root is one level up. -_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -_COMPOSE_FILE = os.path.join(_PROJECT_ROOT, "wiremock", "docker-compose.test.yml") - - -def _get_wiremock_port() -> str: - """Gets the dynamically assigned port for the WireMock container.""" - try: - result = subprocess.run( - ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "port", "wiremock", "8080"], - check=True, - capture_output=True, - text=True, - ) - # Output is like "0.0.0.0:32768" or "[::]:32768" - port = result.stdout.strip().split(":")[-1] - return port - except subprocess.CalledProcessError: - return "8080" # Fallback to default - - -def _start_wiremock() -> None: - """Starts the WireMock container using docker-compose.""" - global _STARTED, _EXTERNAL, _WIREMOCK_URL - if _STARTED: - return - - # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management - existing_url = os.environ.get("WIREMOCK_URL") - if existing_url: - _WIREMOCK_URL = existing_url - _EXTERNAL = True - _STARTED = True - print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)") - return - - print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...") - try: - subprocess.run( - ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "up", "-d", "--wait"], - check=True, - capture_output=True, - text=True, - ) - _WIREMOCK_PORT = _get_wiremock_port() - _WIREMOCK_URL = f"http://localhost:{_WIREMOCK_PORT}" - os.environ["WIREMOCK_URL"] = _WIREMOCK_URL - print(f"WireMock container is ready at {_WIREMOCK_URL}") - _STARTED = True - except subprocess.CalledProcessError as e: - print(f"Failed to start WireMock: {e.stderr}") - raise - - -def _stop_wiremock() -> None: - """Stops and removes the WireMock container.""" - if _EXTERNAL: - # Container is managed externally; nothing to tear down. - return - - print("\nStopping WireMock container...") - subprocess.run( - ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"], - check=False, - capture_output=True, - ) - - -def _is_xdist_worker(config: pytest.Config) -> bool: - """ - Determines if the current process is an xdist worker. - - In pytest-xdist, worker processes have a 'workerinput' attribute - on the config object, while the controller process does not. - """ - return hasattr(config, "workerinput") - - -def _has_httpx_aiohttp() -> bool: - """Check if httpx_aiohttp is importable.""" - try: - import httpx_aiohttp # type: ignore[import-not-found] # noqa: F401 - - return True - except ImportError: - return False - - -def pytest_collection_modifyitems(config: pytest.Config, items: list) -> None: - """Auto-skip @pytest.mark.aiohttp tests when httpx_aiohttp is not installed.""" - if _has_httpx_aiohttp(): - return - skip_aiohttp = pytest.mark.skip(reason="httpx_aiohttp not installed") - for item in items: - if "aiohttp" in item.keywords: - item.add_marker(skip_aiohttp) - - -def pytest_configure(config: pytest.Config) -> None: - """ - Pytest hook that runs during test session setup. - - Starts WireMock container only from the controller process (xdist) - or the single process (non-xdist). This ensures only one container - is started regardless of the number of worker processes. - """ - if _is_xdist_worker(config): - # Workers never manage the container lifecycle. - return - - _start_wiremock() - - -def pytest_unconfigure(config: pytest.Config) -> None: - """ - Pytest hook that runs during test session teardown. - - Stops WireMock container only from the controller process (xdist) - or the single process (non-xdist). This ensures the container is - cleaned up after all workers have finished. - """ - if _is_xdist_worker(config): - # Workers never manage the container lifecycle. - return - - _stop_wiremock() diff --git a/seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py b/seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py deleted file mode 100644 index ab04ce6393ef..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/custom/test_client.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - - -# Get started with writing tests with pytest at https://docs.pytest.org -@pytest.mark.skip(reason="Unimplemented") -def test_client() -> None: - assert True diff --git a/seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py b/seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py deleted file mode 100644 index 73f7f0b99b1d..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/test_aiohttp_autodetect.py +++ /dev/null @@ -1,116 +0,0 @@ -import importlib -import sys -import unittest -from unittest import mock - -import httpx -import pytest - - -class TestMakeDefaultAsyncClientWithoutAiohttp(unittest.TestCase): - """Tests for _make_default_async_client when httpx_aiohttp is NOT installed.""" - - def test_returns_httpx_async_client(self) -> None: - """When httpx_aiohttp is not installed, returns plain httpx.AsyncClient.""" - with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=True) - self.assertIsInstance(client, httpx.AsyncClient) - self.assertEqual(client.timeout.read, 60) - self.assertTrue(client.follow_redirects) - - def test_follow_redirects_none(self) -> None: - """When follow_redirects is None, omits it from httpx.AsyncClient.""" - with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=None) - self.assertIsInstance(client, httpx.AsyncClient) - self.assertFalse(client.follow_redirects) - - def test_explicit_httpx_client_bypasses_autodetect(self) -> None: - """When user passes httpx_client explicitly, _make_default_async_client is not called.""" - - explicit_client = httpx.AsyncClient(timeout=120) - with mock.patch("seed.client._make_default_async_client") as mock_make: - # Replicate the generated conditional: httpx_client if httpx_client is not None else _make_default_async_client(...) - result = explicit_client if explicit_client is not None else mock_make(timeout=60, follow_redirects=True) - mock_make.assert_not_called() - self.assertIs(result, explicit_client) - - -@pytest.mark.aiohttp -class TestMakeDefaultAsyncClientWithAiohttp(unittest.TestCase): - """Tests for _make_default_async_client when httpx_aiohttp IS installed.""" - - def test_returns_aiohttp_client(self) -> None: - """When httpx_aiohttp is installed, returns HttpxAiohttpClient.""" - import httpx_aiohttp # type: ignore[import-not-found] - - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=True) - self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) - self.assertEqual(client.timeout.read, 60) - self.assertTrue(client.follow_redirects) - - def test_follow_redirects_none(self) -> None: - """When httpx_aiohttp is installed and follow_redirects is None, omits it.""" - import httpx_aiohttp # type: ignore[import-not-found] - - from seed.client import _make_default_async_client - - client = _make_default_async_client(timeout=60, follow_redirects=None) - self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) - self.assertFalse(client.follow_redirects) - - -class TestDefaultClientsWithoutAiohttp(unittest.TestCase): - """Tests for _default_clients.py convenience classes (no aiohttp).""" - - def test_default_async_httpx_client_defaults(self) -> None: - """DefaultAsyncHttpxClient applies SDK defaults.""" - from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAsyncHttpxClient - - client = DefaultAsyncHttpxClient() - self.assertIsInstance(client, httpx.AsyncClient) - self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) - self.assertTrue(client.follow_redirects) - - def test_default_async_httpx_client_overrides(self) -> None: - """DefaultAsyncHttpxClient allows overriding defaults.""" - from seed._default_clients import DefaultAsyncHttpxClient - - client = DefaultAsyncHttpxClient(timeout=30, follow_redirects=False) - self.assertEqual(client.timeout.read, 30) - self.assertFalse(client.follow_redirects) - - def test_default_aiohttp_client_raises_without_package(self) -> None: - """DefaultAioHttpClient raises RuntimeError when httpx_aiohttp not installed.""" - import seed._default_clients - - with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): - importlib.reload(seed._default_clients) - - with self.assertRaises(RuntimeError) as ctx: - seed._default_clients.DefaultAioHttpClient() - self.assertIn("pip install fern_allof[aiohttp]", str(ctx.exception)) - - importlib.reload(seed._default_clients) - - -@pytest.mark.aiohttp -class TestDefaultClientsWithAiohttp(unittest.TestCase): - """Tests for _default_clients.py when httpx_aiohttp IS installed.""" - - def test_default_aiohttp_client_defaults(self) -> None: - """DefaultAioHttpClient works when httpx_aiohttp is installed.""" - import httpx_aiohttp # type: ignore[import-not-found] - - from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAioHttpClient - - client = DefaultAioHttpClient() - self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) - self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) - self.assertTrue(client.follow_redirects) diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py b/seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py deleted file mode 100644 index f3ea2659bb1c..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py deleted file mode 100644 index 2cf01263529d..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -from .circle import CircleParams -from .object_with_defaults import ObjectWithDefaultsParams -from .object_with_optional_field import ObjectWithOptionalFieldParams -from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams -from .square import SquareParams -from .undiscriminated_shape import UndiscriminatedShapeParams - -__all__ = [ - "CircleParams", - "ObjectWithDefaultsParams", - "ObjectWithOptionalFieldParams", - "ShapeParams", - "Shape_CircleParams", - "Shape_SquareParams", - "SquareParams", - "UndiscriminatedShapeParams", -] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py deleted file mode 100644 index 74ecf38c308b..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/circle.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing_extensions - -from seed.core.serialization import FieldMetadata - - -class CircleParams(typing_extensions.TypedDict): - radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py deleted file mode 100644 index 2aa2c4c52f0c..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/color.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing - -Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py deleted file mode 100644 index a977b1d2aa1c..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_defaults.py +++ /dev/null @@ -1,15 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing_extensions - - -class ObjectWithDefaultsParams(typing_extensions.TypedDict): - """ - Defines properties with default values and validation rules. - """ - - decimal: typing_extensions.NotRequired[float] - string: typing_extensions.NotRequired[str] - required_string: str diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py deleted file mode 100644 index 6b5608bc05b6..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/object_with_optional_field.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing -import uuid - -import typing_extensions -from .color import Color -from .shape import ShapeParams -from .undiscriminated_shape import UndiscriminatedShapeParams - -from seed.core.serialization import FieldMetadata - - -class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): - literal: typing.Literal["lit_one"] - string: typing_extensions.NotRequired[str] - integer: typing_extensions.NotRequired[int] - long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] - double: typing_extensions.NotRequired[float] - bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] - datetime: typing_extensions.NotRequired[dt.datetime] - date: typing_extensions.NotRequired[dt.date] - uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] - base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] - list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] - set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] - map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] - enum: typing_extensions.NotRequired[Color] - union: typing_extensions.NotRequired[ShapeParams] - second_union: typing_extensions.NotRequired[ShapeParams] - undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] - any: typing.Optional[typing.Any] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py deleted file mode 100644 index 7e70010a251f..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/shape.py +++ /dev/null @@ -1,28 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import typing - -import typing_extensions - -from seed.core.serialization import FieldMetadata - - -class Base(typing_extensions.TypedDict): - id: str - - -class Shape_CircleParams(Base): - shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] - radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] - - -class Shape_SquareParams(Base): - shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] - length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] - - -ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py deleted file mode 100644 index 71c7d25fd4ad..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/square.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing_extensions - -from seed.core.serialization import FieldMetadata - - -class SquareParams(typing_extensions.TypedDict): - length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py deleted file mode 100644 index 99f12b300d1d..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# This file was auto-generated by Fern from our API Definition. - -import typing - -from .circle import CircleParams -from .square import SquareParams - -UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py deleted file mode 100644 index aa2a8b4e4700..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/test_http_client.py +++ /dev/null @@ -1,662 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict -from unittest.mock import AsyncMock, MagicMock, patch - -import httpx -import pytest - -from seed.core.http_client import ( - AsyncHttpClient, - HttpClient, - _build_url, - get_request_body, - remove_none_from_dict, -) -from seed.core.request_options import RequestOptions - - -# Stub clients for testing HttpClient and AsyncHttpClient -class _DummySyncClient: - """A minimal stub for httpx.Client that records request arguments.""" - - def __init__(self) -> None: - self.last_request_kwargs: Dict[str, Any] = {} - - def request(self, **kwargs: Any) -> "_DummyResponse": - self.last_request_kwargs = kwargs - return _DummyResponse() - - -class _DummyAsyncClient: - """A minimal stub for httpx.AsyncClient that records request arguments.""" - - def __init__(self) -> None: - self.last_request_kwargs: Dict[str, Any] = {} - - async def request(self, **kwargs: Any) -> "_DummyResponse": - self.last_request_kwargs = kwargs - return _DummyResponse() - - -class _DummyResponse: - """A minimal stub for httpx.Response.""" - - status_code = 200 - headers: Dict[str, str] = {} - - -def get_request_options() -> RequestOptions: - return {"additional_body_parameters": {"see you": "later"}} - - -def get_request_options_with_none() -> RequestOptions: - return {"additional_body_parameters": {"see you": "later", "optional": None}} - - -def test_get_json_request_body() -> None: - json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) - assert json_body == {"hello": "world"} - assert data_body is None - - json_body_extras, data_body_extras = get_request_body( - json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None - ) - - assert json_body_extras == {"goodbye": "world", "see you": "later"} - assert data_body_extras is None - - -def test_get_files_request_body() -> None: - json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) - assert data_body == {"hello": "world"} - assert json_body is None - - json_body_extras, data_body_extras = get_request_body( - json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None - ) - - assert data_body_extras == {"goodbye": "world", "see you": "later"} - assert json_body_extras is None - - -def test_get_none_request_body() -> None: - json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) - assert data_body is None - assert json_body is None - - json_body_extras, data_body_extras = get_request_body( - json=None, data=None, request_options=get_request_options(), omit=None - ) - - assert json_body_extras == {"see you": "later"} - assert data_body_extras is None - - -def test_get_empty_json_request_body() -> None: - """Test that implicit empty bodies (json=None) are collapsed to None.""" - unrelated_request_options: RequestOptions = {"max_retries": 3} - json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) - assert json_body is None - assert data_body is None - - -def test_explicit_empty_json_body_is_preserved() -> None: - """Test that explicit empty bodies (json={}) are preserved and sent as {}. - - This is important for endpoints where the request body is required but all - fields are optional. The server expects valid JSON ({}) not an empty body. - """ - unrelated_request_options: RequestOptions = {"max_retries": 3} - - # Explicit json={} should be preserved - json_body, data_body = get_request_body(json={}, data=None, request_options=unrelated_request_options, omit=None) - assert json_body == {} - assert data_body is None - - # Explicit data={} should also be preserved - json_body2, data_body2 = get_request_body(json=None, data={}, request_options=unrelated_request_options, omit=None) - assert json_body2 is None - assert data_body2 == {} - - -def test_json_body_preserves_none_values() -> None: - """Test that JSON bodies preserve None values (they become JSON null).""" - json_body, data_body = get_request_body( - json={"hello": "world", "optional": None}, data=None, request_options=None, omit=None - ) - # JSON bodies should preserve None values - assert json_body == {"hello": "world", "optional": None} - assert data_body is None - - -def test_data_body_preserves_none_values_without_multipart() -> None: - """Test that data bodies preserve None values when not using multipart. - - The filtering of None values happens in HttpClient.request/stream methods, - not in get_request_body. This test verifies get_request_body doesn't filter None. - """ - json_body, data_body = get_request_body( - json=None, data={"hello": "world", "optional": None}, request_options=None, omit=None - ) - # get_request_body should preserve None values in data body - # The filtering happens later in HttpClient.request when multipart is detected - assert data_body == {"hello": "world", "optional": None} - assert json_body is None - - -def test_remove_none_from_dict_filters_none_values() -> None: - """Test that remove_none_from_dict correctly filters out None values.""" - original = {"hello": "world", "optional": None, "another": "value", "also_none": None} - filtered = remove_none_from_dict(original) - assert filtered == {"hello": "world", "another": "value"} - # Original should not be modified - assert original == {"hello": "world", "optional": None, "another": "value", "also_none": None} - - -def test_remove_none_from_dict_empty_dict() -> None: - """Test that remove_none_from_dict handles empty dict.""" - assert remove_none_from_dict({}) == {} - - -def test_remove_none_from_dict_all_none() -> None: - """Test that remove_none_from_dict handles dict with all None values.""" - assert remove_none_from_dict({"a": None, "b": None}) == {} - - -def test_http_client_does_not_pass_empty_params_list() -> None: - """Test that HttpClient passes params=None when params are empty. - - This prevents httpx from stripping existing query parameters from the URL, - which happens when params=[] or params={} is passed. - """ - dummy_client = _DummySyncClient() - http_client = HttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - ) - - # Use a path with query params (e.g., pagination cursor URL) - http_client.request( - path="resource?after=123", - method="GET", - params=None, - request_options=None, - ) - - # We care that httpx receives params=None, not [] or {} - assert "params" in dummy_client.last_request_kwargs - assert dummy_client.last_request_kwargs["params"] is None - - # Verify the query string in the URL is preserved - url = str(dummy_client.last_request_kwargs["url"]) - assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" - - -def test_http_client_passes_encoded_params_when_present() -> None: - """Test that HttpClient passes encoded params when params are provided.""" - dummy_client = _DummySyncClient() - http_client = HttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com/resource", - ) - - http_client.request( - path="", - method="GET", - params={"after": "456"}, - request_options=None, - ) - - params = dummy_client.last_request_kwargs["params"] - # For a simple dict, encode_query should give a single (key, value) tuple - assert params == [("after", "456")] - - -@pytest.mark.asyncio -async def test_async_http_client_does_not_pass_empty_params_list() -> None: - """Test that AsyncHttpClient passes params=None when params are empty. - - This prevents httpx from stripping existing query parameters from the URL, - which happens when params=[] or params={} is passed. - """ - dummy_client = _DummyAsyncClient() - http_client = AsyncHttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - async_base_headers=None, - ) - - # Use a path with query params (e.g., pagination cursor URL) - await http_client.request( - path="resource?after=123", - method="GET", - params=None, - request_options=None, - ) - - # We care that httpx receives params=None, not [] or {} - assert "params" in dummy_client.last_request_kwargs - assert dummy_client.last_request_kwargs["params"] is None - - # Verify the query string in the URL is preserved - url = str(dummy_client.last_request_kwargs["url"]) - assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" - - -@pytest.mark.asyncio -async def test_async_http_client_passes_encoded_params_when_present() -> None: - """Test that AsyncHttpClient passes encoded params when params are provided.""" - dummy_client = _DummyAsyncClient() - http_client = AsyncHttpClient( - httpx_client=dummy_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com/resource", - async_base_headers=None, - ) - - await http_client.request( - path="", - method="GET", - params={"after": "456"}, - request_options=None, - ) - - params = dummy_client.last_request_kwargs["params"] - # For a simple dict, encode_query should give a single (key, value) tuple - assert params == [("after", "456")] - - -def test_basic_url_joining() -> None: - """Test basic URL joining with a simple base URL and path.""" - result = _build_url("https://api.example.com", "/users") - assert result == "https://api.example.com/users" - - -def test_basic_url_joining_trailing_slash() -> None: - """Test basic URL joining with a simple base URL and path.""" - result = _build_url("https://api.example.com/", "/users") - assert result == "https://api.example.com/users" - - -def test_preserves_base_url_path_prefix() -> None: - """Test that path prefixes in base URL are preserved. - - This is the critical bug fix - urllib.parse.urljoin() would strip - the path prefix when the path starts with '/'. - """ - result = _build_url("https://cloud.example.com/org/tenant/api", "/users") - assert result == "https://cloud.example.com/org/tenant/api/users" - - -def test_preserves_base_url_path_prefix_trailing_slash() -> None: - """Test that path prefixes in base URL are preserved.""" - result = _build_url("https://cloud.example.com/org/tenant/api/", "/users") - assert result == "https://cloud.example.com/org/tenant/api/users" - - -# --------------------------------------------------------------------------- -# Connection error retry tests -# --------------------------------------------------------------------------- - - -def _make_sync_http_client(mock_client: Any) -> HttpClient: - return HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - ) - - -def _make_async_http_client(mock_client: Any) -> AsyncHttpClient: - return AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - async_base_headers=None, - ) - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_retries_on_connect_error(mock_sleep: MagicMock) -> None: - """Sync: connection error retries on httpx.ConnectError.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - http_client = _make_sync_http_client(mock_client) - - response = http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_retries_on_remote_protocol_error(mock_sleep: MagicMock) -> None: - """Sync: connection error retries on httpx.RemoteProtocolError.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.RemoteProtocolError("Remote end closed connection without response"), - _DummyResponse(), - ] - http_client = _make_sync_http_client(mock_client) - - response = http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_connection_error_exhausts_retries(mock_sleep: MagicMock) -> None: - """Sync: connection error exhausts retries then raises.""" - mock_client = MagicMock() - mock_client.request.side_effect = httpx.ConnectError("connection failed") - http_client = _make_sync_http_client(mock_client) - - with pytest.raises(httpx.ConnectError): - http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - assert mock_sleep.call_count == 2 - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_connection_error_respects_max_retries_zero(mock_sleep: MagicMock) -> None: - """Sync: connection error respects max_retries=0.""" - mock_client = MagicMock() - mock_client.request.side_effect = httpx.ConnectError("connection failed") - http_client = _make_sync_http_client(mock_client) - - with pytest.raises(httpx.ConnectError): - http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 0}, - ) - - # No retries, just the initial attempt - assert mock_client.request.call_count == 1 - mock_sleep.assert_not_called() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_retries_on_connect_error(mock_sleep: AsyncMock) -> None: - """Async: connection error retries on httpx.ConnectError.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - ) - http_client = _make_async_http_client(mock_client) - - response = await http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_retries_on_remote_protocol_error(mock_sleep: AsyncMock) -> None: - """Async: connection error retries on httpx.RemoteProtocolError.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.RemoteProtocolError("Remote end closed connection without response"), - _DummyResponse(), - ] - ) - http_client = _make_async_http_client(mock_client) - - response = await http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - assert mock_client.request.call_count == 2 - mock_sleep.assert_called_once() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_connection_error_exhausts_retries(mock_sleep: AsyncMock) -> None: - """Async: connection error exhausts retries then raises.""" - mock_client = MagicMock() - mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) - http_client = _make_async_http_client(mock_client) - - with pytest.raises(httpx.ConnectError): - await http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - assert mock_sleep.call_count == 2 - - -# --------------------------------------------------------------------------- -# base_max_retries constructor parameter tests -# --------------------------------------------------------------------------- - - -def test_sync_http_client_default_base_max_retries() -> None: - """HttpClient defaults to base_max_retries=2.""" - http_client = HttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - ) - assert http_client.base_max_retries == 2 - - -def test_async_http_client_default_base_max_retries() -> None: - """AsyncHttpClient defaults to base_max_retries=2.""" - http_client = AsyncHttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - ) - assert http_client.base_max_retries == 2 - - -def test_sync_http_client_custom_base_max_retries() -> None: - """HttpClient accepts a custom base_max_retries value.""" - http_client = HttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_max_retries=5, - ) - assert http_client.base_max_retries == 5 - - -def test_async_http_client_custom_base_max_retries() -> None: - """AsyncHttpClient accepts a custom base_max_retries value.""" - http_client = AsyncHttpClient( - httpx_client=MagicMock(), # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_max_retries=5, - ) - assert http_client.base_max_retries == 5 - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_base_max_retries_zero_disables_retries(mock_sleep: MagicMock) -> None: - """Sync: base_max_retries=0 disables retries when no request_options override.""" - mock_client = MagicMock() - mock_client.request.side_effect = httpx.ConnectError("connection failed") - http_client = HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, - ) - - with pytest.raises(httpx.ConnectError): - http_client.request(path="/test", method="GET") - - # No retries, just the initial attempt - assert mock_client.request.call_count == 1 - mock_sleep.assert_not_called() - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_base_max_retries_zero_disables_retries(mock_sleep: AsyncMock) -> None: - """Async: base_max_retries=0 disables retries when no request_options override.""" - mock_client = MagicMock() - mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) - http_client = AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, - ) - - with pytest.raises(httpx.ConnectError): - await http_client.request(path="/test", method="GET") - - # No retries, just the initial attempt - assert mock_client.request.call_count == 1 - mock_sleep.assert_not_called() - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_request_options_override_base_max_retries(mock_sleep: MagicMock) -> None: - """Sync: request_options max_retries overrides base_max_retries.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.ConnectError("connection failed"), - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - http_client = HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, # base says no retries - ) - - # But request_options overrides to allow 2 retries - response = http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - assert response.status_code == 200 - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_request_options_override_base_max_retries(mock_sleep: AsyncMock) -> None: - """Async: request_options max_retries overrides base_max_retries.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.ConnectError("connection failed"), - httpx.ConnectError("connection failed"), - _DummyResponse(), - ] - ) - http_client = AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=0, # base says no retries - ) - - # But request_options overrides to allow 2 retries - response = await http_client.request( - path="/test", - method="GET", - request_options={"max_retries": 2}, - ) - - assert response.status_code == 200 - # 1 initial + 2 retries = 3 total attempts - assert mock_client.request.call_count == 3 - - -@patch("seed.core.http_client.time.sleep", return_value=None) -def test_sync_base_max_retries_used_as_default(mock_sleep: MagicMock) -> None: - """Sync: base_max_retries is used when request_options has no max_retries.""" - mock_client = MagicMock() - mock_client.request.side_effect = [ - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - _DummyResponse(), - ] - http_client = HttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=3, - ) - - response = http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - # 1 initial + 3 retries = 4 total attempts - assert mock_client.request.call_count == 4 - - -@pytest.mark.asyncio -@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock) -async def test_async_base_max_retries_used_as_default(mock_sleep: AsyncMock) -> None: - """Async: base_max_retries is used when request_options has no max_retries.""" - mock_client = MagicMock() - mock_client.request = AsyncMock( - side_effect=[ - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - httpx.ConnectError("fail"), - _DummyResponse(), - ] - ) - http_client = AsyncHttpClient( - httpx_client=mock_client, # type: ignore[arg-type] - base_timeout=lambda: None, - base_headers=lambda: {}, - base_url=lambda: "https://example.com", - base_max_retries=3, - ) - - response = await http_client.request(path="/test", method="GET") - - assert response.status_code == 200 - # 1 initial + 3 retries = 4 total attempts - assert mock_client.request.call_count == 4 diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py b/seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py deleted file mode 100644 index ef5fd7094f9b..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/test_query_encoding.py +++ /dev/null @@ -1,36 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from seed.core.query_encoder import encode_query - - -def test_query_encoding_deep_objects() -> None: - assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")] - assert encode_query({"hello_world": {"hello": "world"}}) == [("hello_world[hello]", "world")] - assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == [ - ("hello_world[hello][world]", "today"), - ("hello_world[test]", "this"), - ("hi", "there"), - ] - - -def test_query_encoding_deep_object_arrays() -> None: - assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [ - ("objects[key]", "hello"), - ("objects[value]", "world"), - ("objects[key]", "foo"), - ("objects[value]", "bar"), - ] - assert encode_query( - {"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]} - ) == [ - ("users[name]", "string"), - ("users[tags]", "string"), - ("users[name]", "string2"), - ("users[tags]", "string2"), - ("users[tags]", "string3"), - ] - - -def test_encode_query_with_none() -> None: - encoded = encode_query(None) - assert encoded is None diff --git a/seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py b/seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py deleted file mode 100644 index b298db89c4bd..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/utils/test_serialization.py +++ /dev/null @@ -1,72 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, List - -from .assets.models import ObjectWithOptionalFieldParams, ShapeParams - -from seed.core.serialization import convert_and_respect_annotation_metadata - -UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} -UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} - - -def test_convert_and_respect_annotation_metadata() -> None: - data: ObjectWithOptionalFieldParams = { - "string": "string", - "long_": 12345, - "bool_": True, - "literal": "lit_one", - "any": "any", - } - converted = convert_and_respect_annotation_metadata( - object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" - ) - assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} - - -def test_convert_and_respect_annotation_metadata_in_list() -> None: - data: List[ObjectWithOptionalFieldParams] = [ - {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, - {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, - ] - converted = convert_and_respect_annotation_metadata( - object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write" - ) - - assert converted == [ - {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, - {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, - ] - - -def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: - data: ObjectWithOptionalFieldParams = { - "string": "string", - "long_": 12345, - "union": UNION_TEST, - "literal": "lit_one", - "any": "any", - } - converted = convert_and_respect_annotation_metadata( - object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" - ) - - assert converted == { - "string": "string", - "long": 12345, - "union": UNION_TEST_CONVERTED, - "literal": "lit_one", - "any": "any", - } - - -def test_convert_and_respect_annotation_metadata_in_union() -> None: - converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write") - - assert converted == UNION_TEST_CONVERTED - - -def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: - data: Any = {} - converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write") - assert converted == data diff --git a/seed/python-sdk/allof/no-custom-config/tests/wire/__init__.py b/seed/python-sdk/allof/no-custom-config/tests/wire/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py b/seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py deleted file mode 100644 index 2782ac72dd88..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/wire/conftest.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Pytest configuration for wire tests. - -This module provides helpers for creating a configured client that talks to -WireMock and for verifying requests in WireMock. - -The WireMock container lifecycle itself is managed by a top-level pytest -plugin (tests/conftest.py) so that the container is started exactly once -per test run, even when using pytest-xdist. -""" - -import inspect -import os -from typing import Any, Dict, Optional - -import httpx - -from seed.client import SeedApi - -# Check once at import time whether the client constructor accepts a headers kwarg. -try: - _CLIENT_SUPPORTS_HEADERS: bool = "headers" in inspect.signature(SeedApi).parameters -except (TypeError, ValueError): - _CLIENT_SUPPORTS_HEADERS = False - - -def _get_wiremock_base_url() -> str: - """Returns the WireMock base URL from the WIREMOCK_URL environment variable.""" - return os.environ.get("WIREMOCK_URL", "http://localhost:8080") - - -def get_client(test_id: str) -> SeedApi: - """ - Creates a configured client instance for wire tests. - - Args: - test_id: Unique identifier for the test, used for request tracking. - - Returns: - A configured client instance with all required auth parameters. - """ - test_headers = {"X-Test-Id": test_id} - base_url = _get_wiremock_base_url() - - if _CLIENT_SUPPORTS_HEADERS: - return SeedApi( - base_url=base_url, - headers=test_headers, - ) - - return SeedApi( - base_url=base_url, - httpx_client=httpx.Client(headers=test_headers), - ) - - -def verify_request_count( - test_id: str, - method: str, - url_path: str, - query_params: Optional[Dict[str, str]], - expected: int, -) -> None: - """Verifies the number of requests made to WireMock filtered by test ID for concurrency safety.""" - wiremock_admin_url = f"{_get_wiremock_base_url()}/__admin" - request_body: Dict[str, Any] = { - "method": method, - "urlPath": url_path, - "headers": {"X-Test-Id": {"equalTo": test_id}}, - } - if query_params: - query_parameters = {k: {"equalTo": v} for k, v in query_params.items()} - request_body["queryParameters"] = query_parameters - response = httpx.post(f"{wiremock_admin_url}/requests/find", json=request_body) - assert response.status_code == 200, "Failed to query WireMock requests" - result = response.json() - requests_found = len(result.get("requests", [])) - assert requests_found == expected, f"Expected {expected} requests, found {requests_found}" diff --git a/seed/python-sdk/allof/no-custom-config/tests/wire/test_.py b/seed/python-sdk/allof/no-custom-config/tests/wire/test_.py deleted file mode 100644 index 2f638bc05b10..000000000000 --- a/seed/python-sdk/allof/no-custom-config/tests/wire/test_.py +++ /dev/null @@ -1,44 +0,0 @@ -from .conftest import get_client, verify_request_count - - -def test__search_rule_types() -> None: - """Test searchRuleTypes endpoint with WireMock""" - test_id = "search_rule_types.0" - client = get_client(test_id) - client.search_rule_types() - verify_request_count(test_id, "GET", "/rule-types", None, 1) - - -def test__create_rule() -> None: - """Test createRule endpoint with WireMock""" - test_id = "create_rule.0" - client = get_client(test_id) - client.create_rule( - name="name", - execution_context="prod", - ) - verify_request_count(test_id, "POST", "/rules", None, 1) - - -def test__list_users() -> None: - """Test listUsers endpoint with WireMock""" - test_id = "list_users.0" - client = get_client(test_id) - client.list_users() - verify_request_count(test_id, "GET", "/users", None, 1) - - -def test__get_entity() -> None: - """Test getEntity endpoint with WireMock""" - test_id = "get_entity.0" - client = get_client(test_id) - client.get_entity() - verify_request_count(test_id, "GET", "/entities", None, 1) - - -def test__get_organization() -> None: - """Test getOrganization endpoint with WireMock""" - test_id = "get_organization.0" - client = get_client(test_id) - client.get_organization() - verify_request_count(test_id, "GET", "/organizations", None, 1) diff --git a/seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml b/seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml deleted file mode 100644 index 58747d54a46b..000000000000 --- a/seed/python-sdk/allof/no-custom-config/wiremock/docker-compose.test.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - wiremock: - image: wiremock/wiremock:3.9.1 - ports: - - "0:8080" # Use dynamic port to avoid conflicts with concurrent tests - volumes: - - ./wiremock-mappings.json:/home/wiremock/mappings/wiremock-mappings.json - command: ["--global-response-templating", "--verbose"] - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8080/__admin/health"] - interval: 2s - timeout: 5s - retries: 15 - start_period: 5s diff --git a/seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json b/seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json deleted file mode 100644 index bfeb998eabae..000000000000 --- a/seed/python-sdk/allof/no-custom-config/wiremock/wiremock-mappings.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "mappings": [ - { - "id": "19b28390-baa1-4ec9-87a3-cd8485b994a5", - "name": "Search rule types with paginated results - default", - "request": { - "urlPathTemplate": "/rule-types", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"name\": \"name\",\n \"description\": \"description\"\n }\n ]\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "19b28390-baa1-4ec9-87a3-cd8485b994a5", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - }, - { - "id": "1a934299-52ab-4d86-aa31-9f873790df88", - "name": "Create a rule with constrained execution context - default", - "request": { - "urlPathTemplate": "/rules", - "method": "POST" - }, - "response": { - "status": 200, - "body": "{\n \"createdBy\": \"createdBy\",\n \"createdDateTime\": \"2024-01-15T09:30:00Z\",\n \"modifiedBy\": \"modifiedBy\",\n \"modifiedDateTime\": \"2024-01-15T09:30:00Z\",\n \"id\": \"id\",\n \"name\": \"name\",\n \"status\": \"active\",\n \"executionContext\": \"prod\"\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "1a934299-52ab-4d86-aa31-9f873790df88", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - } - }, - { - "id": "c4083f9d-e8ad-4944-abe5-163125679505", - "name": "List users with paginated results - default", - "request": { - "urlPathTemplate": "/users", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"paging\": {\n \"next\": \"next\",\n \"previous\": \"previous\"\n },\n \"results\": [\n {\n \"id\": \"id\",\n \"email\": \"email\"\n }\n ]\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "c4083f9d-e8ad-4944-abe5-163125679505", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - }, - { - "id": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", - "name": "Get an entity that combines multiple parents - default", - "request": { - "urlPathTemplate": "/entities", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"name\": \"name\",\n \"summary\": \"summary\",\n \"id\": \"id\",\n \"status\": \"active\"\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "7461b7cd-2c2b-4dfe-bde6-5152d19378a2", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - }, - { - "id": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", - "name": "Get an organization with merged object-typed properties - default", - "request": { - "urlPathTemplate": "/organizations", - "method": "GET" - }, - "response": { - "status": 200, - "body": "{\n \"metadata\": {\n \"region\": \"region\",\n \"tier\": \"tier\"\n },\n \"id\": \"id\",\n \"name\": \"name\"\n}", - "headers": { - "Content-Type": "application/json" - } - }, - "uuid": "1c7f93bf-abf3-4466-99e9-5e2b1de4aac3", - "persistent": true, - "priority": 3, - "metadata": { - "mocklab": { - "created": { - "at": "2020-01-01T00:00:00.000Z", - "via": "SYSTEM" - } - } - }, - "postServeActions": [] - } - ], - "meta": { - "total": 5 - } -} \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json b/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json deleted file mode 100644 index 4e868ad06d8a..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-ruby-sdk-v2", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml b/seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 72178ea4c8f1..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: ci - -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.3" - - - name: Install dependencies - run: bundle install - - - name: Run Rubocop - run: bundle exec rubocop - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.3" - - - name: Install dependencies - run: bundle install - - - name: Run Tests - run: bundle exec rake test - - publish: - name: Publish to RubyGems.org - runs-on: ubuntu-latest - needs: [lint, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - - permissions: - id-token: write - contents: write - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.3" - - - name: Install dependencies - run: bundle install - - - name: Configure RubyGems credentials - uses: rubygems/configure-rubygems-credentials@v1.0.0 - - - name: Build gem - run: bundle exec rake build - - - name: Push gem to RubyGems - run: gem push pkg/*.gem diff --git a/seed/ruby-sdk-v2/allof-inline/.gitignore b/seed/ruby-sdk-v2/allof-inline/.gitignore deleted file mode 100644 index c111b331371a..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.gem diff --git a/seed/ruby-sdk-v2/allof-inline/.rubocop.yml b/seed/ruby-sdk-v2/allof-inline/.rubocop.yml deleted file mode 100644 index 75d8f836f2f0..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/.rubocop.yml +++ /dev/null @@ -1,69 +0,0 @@ -plugins: - - rubocop-minitest - -AllCops: - TargetRubyVersion: 3.3 - NewCops: enable - -Style/StringLiterals: - EnforcedStyle: double_quotes - -Style/StringLiteralsInInterpolation: - EnforcedStyle: double_quotes - -Style/AccessModifierDeclarations: - Enabled: false - -Lint/ConstantDefinitionInBlock: - Enabled: false - -Metrics/AbcSize: - Enabled: false - -Metrics/BlockLength: - Enabled: false - -Metrics/ClassLength: - Enabled: false - -Metrics/MethodLength: - Enabled: false - -Metrics/ParameterLists: - Enabled: false - -Metrics/PerceivedComplexity: - Enabled: false - -Metrics/CyclomaticComplexity: - Enabled: false - -Metrics/ModuleLength: - Enabled: false - -Layout/LineLength: - Enabled: false - -Naming/VariableNumber: - EnforcedStyle: normalcase - -Style/Documentation: - Enabled: false - -Style/Lambda: - EnforcedStyle: literal - -Minitest/MultipleAssertions: - Enabled: false - -Minitest/UselessAssertion: - Enabled: false - -# Dynamic snippets are code samples for documentation, not standalone Ruby files. -Style/FrozenStringLiteralComment: - Exclude: - - "dynamic-snippets/**/*" - -Layout/FirstHashElementIndentation: - Exclude: - - "dynamic-snippets/**/*" diff --git a/seed/ruby-sdk-v2/allof-inline/Gemfile b/seed/ruby-sdk-v2/allof-inline/Gemfile deleted file mode 100644 index 16877f89f300..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/Gemfile +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gemspec - -group :test, :development do - gem "minitest", "~> 5.16" - gem "minitest-rg" - gem "pry" - gem "rake", "~> 13.0" - gem "rubocop", "~> 1.21" - gem "rubocop-minitest" - gem "webmock" -end - -# Load custom Gemfile configuration if it exists -custom_gemfile = File.join(__dir__, "Gemfile.custom") -eval_gemfile(custom_gemfile) if File.exist?(custom_gemfile) diff --git a/seed/ruby-sdk-v2/allof-inline/Gemfile.custom b/seed/ruby-sdk-v2/allof-inline/Gemfile.custom deleted file mode 100644 index 11bdfaf13f2d..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/Gemfile.custom +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -# Custom Gemfile configuration file -# This file is automatically loaded by the main Gemfile. You can add custom gems, -# groups, or other Gemfile configurations here. If you do make changes to this file, -# you will need to add it to the .fernignore file to prevent your changes from being -# overwritten by the generator. - -# Example usage: -# group :test, :development do -# gem 'custom-gem', '~> 2.0' -# end - -# Add your custom gem dependencies here \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof-inline/README.md b/seed/ruby-sdk-v2/allof-inline/README.md deleted file mode 100644 index 8c9724fb7644..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# Seed Ruby Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRuby) - -The Seed Ruby library provides convenient access to the Seed APIs from Ruby. - -## Table of Contents - -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Advanced](#advanced) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Additional Headers](#additional-headers) - - [Additional Query Parameters](#additional-query-parameters) -- [Contributing](#contributing) - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```ruby -require "seed" - -client = Seed::Client.new - -client.create_rule( - name: "name", - execution_context: "prod" -) -``` - -## Environments - -This SDK allows you to configure different environments or custom URLs for API requests. You can either use the predefined environments or specify your own custom URL. -### Environments -```ruby -require "seed" - -seed = Seed::Client.new( - base_url: Seed::Environment::DEFAULT -) -``` - -### Custom URL -```ruby -require "seed" - -client = Seed::Client.new( - base_url: "https://example.com" -) -``` - -## Errors - -Failed API calls will raise errors that can be rescued from granularly. - -```ruby -require "seed" - -client = Seed::Client.new( - base_url: "https://example.com" -) - -begin - result = client.create_rule -rescue Seed::Errors::TimeoutError - puts "API didn't respond before our timeout elapsed" -rescue Seed::Errors::ServiceUnavailableError - puts "API returned status 503, is probably overloaded, try again later" -rescue Seed::Errors::ServerError - puts "API returned some other 5xx status, this is probably a bug" -rescue Seed::Errors::ResponseError => e - puts "API returned an unexpected status other than 5xx: #{e.code} #{e.message}" -rescue Seed::Errors::ApiError => e - puts "Some other error occurred when calling the API: #{e.message}" -end -``` - -## Advanced - -### Retries - -The SDK is instrumented with automatic retries. A request will be retried as long as the request is deemed -retryable and the number of retry attempts has not grown larger than the configured retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `max_retries` option to configure this behavior. - -```ruby -require "seed" - -client = Seed::Client.new( - base_url: "https://example.com", - max_retries: 3 # Configure max retries (default is 2) -) -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. Use the `timeout` option to configure this behavior. - -```ruby -require "seed" - -response = client.create_rule( - ..., - timeout: 30 # 30 second timeout -) -``` - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `additional_headers` request option. - -```ruby -require "seed" - -response = client.create_rule( - ..., - request_options: { - additional_headers: { - "X-Custom-Header" => "custom-value" - } - } -) -``` - -### Additional Query Parameters - -If you would like to send additional query parameters as part of the request, use the `additional_query_parameters` request option. - -```ruby -require "seed" - -response = client.create_rule( - ..., - request_options: { - additional_query_parameters: { - "custom_param" => "custom-value" - } - } -) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/ruby-sdk-v2/allof-inline/Rakefile b/seed/ruby-sdk-v2/allof-inline/Rakefile deleted file mode 100644 index 9bdd4a6ce80b..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/Rakefile +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require "bundler/gem_tasks" -require "minitest/test_task" - -Minitest::TestTask.create - -require "rubocop/rake_task" - -RuboCop::RakeTask.new - -task default: %i[test] - -task lint: %i[rubocop] - -# Run only the custom test file -Minitest::TestTask.create(:customtest) do |t| - t.libs << "test" - t.test_globs = ["test/custom.test.rb"] -end diff --git a/seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb b/seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb deleted file mode 100644 index 86d8efd3cd3c..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/custom.gemspec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -# Custom gemspec configuration file -# This file is automatically loaded by the main gemspec file. The 'spec' variable is available -# in this context from the main gemspec file. You can modify this file to add custom metadata, -# dependencies, or other gemspec configurations. If you do make changes to this file, you will -# need to add it to the .fernignore file to prevent your changes from being overwritten. - -def add_custom_gemspec_data(spec) - # Example custom configurations (uncomment and modify as needed) - - # spec.authors = ["Your name"] - # spec.email = ["your.email@example.com"] - # spec.homepage = "https://github.com/your-org/seed-ruby" - # spec.license = "Your license" -end diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb deleted file mode 100644 index f8b3f168d71b..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example0/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.search_rule_types diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb deleted file mode 100644 index 4b8e2cf5a383..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example1/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.search_rule_types(query: "query") diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb deleted file mode 100644 index 96d978fcde17..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example2/snippet.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.create_rule( - name: "name", - execution_context: "prod" -) diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb deleted file mode 100644 index 96d978fcde17..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example3/snippet.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.create_rule( - name: "name", - execution_context: "prod" -) diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb deleted file mode 100644 index 99237c7c6437..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example4/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.list_users diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb deleted file mode 100644 index 99237c7c6437..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example5/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.list_users diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb deleted file mode 100644 index 7769816ca1be..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example6/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_entity diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb deleted file mode 100644 index 7769816ca1be..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example7/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_entity diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb deleted file mode 100644 index f47ada1c5233..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example8/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_organization diff --git a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb b/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb deleted file mode 100644 index f47ada1c5233..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/dynamic-snippets/example9/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_organization diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed.rb deleted file mode 100644 index add97d9e68d8..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require "json" -require "net/http" -require "securerandom" - -require_relative "seed/internal/json/serializable" -require_relative "seed/internal/types/type" -require_relative "seed/internal/types/utils" -require_relative "seed/internal/types/union" -require_relative "seed/internal/errors/constraint_error" -require_relative "seed/internal/errors/type_error" -require_relative "seed/internal/http/base_request" -require_relative "seed/internal/json/request" -require_relative "seed/internal/http/raw_client" -require_relative "seed/internal/multipart/multipart_encoder" -require_relative "seed/internal/multipart/multipart_form_data_part" -require_relative "seed/internal/multipart/multipart_form_data" -require_relative "seed/internal/multipart/multipart_request" -require_relative "seed/internal/types/model/field" -require_relative "seed/internal/types/model" -require_relative "seed/internal/types/array" -require_relative "seed/internal/types/boolean" -require_relative "seed/internal/types/enum" -require_relative "seed/internal/types/hash" -require_relative "seed/internal/types/unknown" -require_relative "seed/errors/api_error" -require_relative "seed/errors/response_error" -require_relative "seed/errors/client_error" -require_relative "seed/errors/redirect_error" -require_relative "seed/errors/server_error" -require_relative "seed/errors/timeout_error" -require_relative "seed/internal/iterators/item_iterator" -require_relative "seed/internal/iterators/cursor_item_iterator" -require_relative "seed/internal/iterators/offset_item_iterator" -require_relative "seed/internal/iterators/cursor_page_iterator" -require_relative "seed/internal/iterators/offset_page_iterator" -require_relative "seed/types/paging_cursors" -require_relative "seed/types/paginated_result" -require_relative "seed/types/rule_execution_context" -require_relative "seed/types/audit_info" -require_relative "seed/types/rule_type" -require_relative "seed/types/rule_type_search_response" -require_relative "seed/types/user" -require_relative "seed/types/user_search_response" -require_relative "seed/types/rule_response_status" -require_relative "seed/types/rule_response" -require_relative "seed/types/identifiable" -require_relative "seed/types/describable" -require_relative "seed/types/combined_entity_status" -require_relative "seed/types/combined_entity" -require_relative "seed/types/base_org_metadata" -require_relative "seed/types/base_org" -require_relative "seed/types/detailed_org_metadata" -require_relative "seed/types/detailed_org" -require_relative "seed/types/organization_metadata" -require_relative "seed/types/organization" -require_relative "seed/environment" diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb deleted file mode 100644 index e994144e9573..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/environment.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module Seed - class Environment - DEFAULT = "https://api.example.com" - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb deleted file mode 100644 index b8ba53889b36..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/api_error.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ApiError < StandardError - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb deleted file mode 100644 index c3c6033641e2..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/client_error.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ClientError < ResponseError - end - - class UnauthorizedError < ClientError - end - - class ForbiddenError < ClientError - end - - class NotFoundError < ClientError - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb deleted file mode 100644 index f663c01e7615..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/redirect_error.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class RedirectError < ResponseError - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb deleted file mode 100644 index beb4a1baf959..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/response_error.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ResponseError < ApiError - attr_reader :code - - def initialize(msg, code:) - @code = code - super(msg) - end - - def inspect - "#<#{self.class.name} @code=#{code} @body=#{message}>" - end - - # Returns the most appropriate error class for the given code. - # - # @return [Class] - def self.subclass_for_code(code) - case code - when 300..399 - RedirectError - when 401 - UnauthorizedError - when 403 - ForbiddenError - when 404 - NotFoundError - when 400..499 - ClientError - when 503 - ServiceUnavailableError - when 500..599 - ServerError - else - ResponseError - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb deleted file mode 100644 index 1838027cdeab..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/server_error.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ServerError < ResponseError - end - - class ServiceUnavailableError < ApiError - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb deleted file mode 100644 index ec3a24bb7e96..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/errors/timeout_error.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class TimeoutError < ApiError - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb deleted file mode 100644 index e2f0bd66ac37..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/constraint_error.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Errors - class ConstraintError < StandardError - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb deleted file mode 100644 index 6aec80f59f05..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/errors/type_error.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Errors - class TypeError < StandardError - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb deleted file mode 100644 index d35df463e5b0..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/base_request.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Http - # @api private - class BaseRequest - attr_reader :base_url, :path, :method, :headers, :query, :request_options - - # @param base_url [String] The base URL for the request - # @param path [String] The path for the request - # @param method [String] The HTTP method for the request (:get, :post, etc.) - # @param headers [Hash] Additional headers for the request (optional) - # @param query [Hash] Query parameters for the request (optional) - # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] - def initialize(base_url:, path:, method:, headers: {}, query: {}, request_options: {}) - @base_url = base_url - @path = path - @method = method - @headers = headers - @query = query - @request_options = request_options - end - - # @return [Hash] The query parameters merged with additional query parameters from request options. - def encode_query - additional_query = @request_options&.dig(:additional_query_parameters) || @request_options&.dig("additional_query_parameters") || {} - @query.merge(additional_query) - end - - # Child classes should implement: - # - encode_headers: Returns the encoded HTTP request headers. - # - encode_body: Returns the encoded HTTP request body. - - private - - # Merges additional_headers from request_options into sdk_headers, filtering out - # any keys that collide with SDK-set or client-protected headers (case-insensitive). - # @param sdk_headers [Hash] Headers set by the SDK for this request type. - # @param protected_keys [Array] Additional header keys that must not be overridden. - # @return [Hash] The merged headers. - def merge_additional_headers(sdk_headers, protected_keys: []) - additional_headers = @request_options&.dig(:additional_headers) || @request_options&.dig("additional_headers") || {} - all_protected = (sdk_headers.keys + protected_keys).to_set { |k| k.to_s.downcase } - filtered = additional_headers.reject { |key, _| all_protected.include?(key.to_s.downcase) } - sdk_headers.merge(filtered) - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb deleted file mode 100644 index 482ab9517714..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/http/raw_client.rb +++ /dev/null @@ -1,214 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Http - # @api private - class RawClient - # Default HTTP status codes that trigger a retry - RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504, 521, 522, 524].freeze - # Initial delay between retries in seconds - INITIAL_RETRY_DELAY = 0.5 - # Maximum delay between retries in seconds - MAX_RETRY_DELAY = 60.0 - # Jitter factor for randomizing retry delays (20%) - JITTER_FACTOR = 0.2 - - # @return [String] The base URL for requests - attr_reader :base_url - - # @param base_url [String] The base url for the request. - # @param max_retries [Integer] The number of times to retry a failed request, defaults to 2. - # @param timeout [Float] The timeout for the request, defaults to 60.0 seconds. - # @param headers [Hash] The headers for the request. - def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) - @base_url = base_url - @max_retries = max_retries - @timeout = timeout - @default_headers = { - "X-Fern-Language": "Ruby", - "X-Fern-SDK-Name": "seed", - "X-Fern-SDK-Version": "0.0.1" - }.merge(headers) - end - - # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. - # @return [HTTP::Response] The HTTP response. - def send(request) - url = build_url(request) - attempt = 0 - response = nil - - loop do - http_request = build_http_request( - url:, - method: request.method, - headers: request.encode_headers(protected_keys: @default_headers.keys), - body: request.encode_body - ) - - conn = connect(url) - conn.open_timeout = @timeout - conn.read_timeout = @timeout - conn.write_timeout = @timeout - conn.continue_timeout = @timeout - - response = conn.request(http_request) - - break unless should_retry?(response, attempt) - - delay = retry_delay(response, attempt) - sleep(delay) - attempt += 1 - end - - response - end - - # Determines if a request should be retried based on the response status code. - # @param response [Net::HTTPResponse] The HTTP response. - # @param attempt [Integer] The current retry attempt (0-indexed). - # @return [Boolean] Whether the request should be retried. - def should_retry?(response, attempt) - return false if attempt >= @max_retries - - status = response.code.to_i - RETRYABLE_STATUSES.include?(status) - end - - # Calculates the delay before the next retry attempt using exponential backoff with jitter. - # Respects Retry-After header if present. - # @param response [Net::HTTPResponse] The HTTP response. - # @param attempt [Integer] The current retry attempt (0-indexed). - # @return [Float] The delay in seconds before the next retry. - def retry_delay(response, attempt) - # Check for Retry-After header (can be seconds or HTTP date) - retry_after = response["Retry-After"] - if retry_after - delay = parse_retry_after(retry_after) - return [delay, MAX_RETRY_DELAY].min if delay&.positive? - end - - # Exponential backoff with jitter: base_delay * 2^attempt - base_delay = INITIAL_RETRY_DELAY * (2**attempt) - add_jitter([base_delay, MAX_RETRY_DELAY].min) - end - - # Parses the Retry-After header value. - # @param value [String] The Retry-After header value (seconds or HTTP date). - # @return [Float, nil] The delay in seconds, or nil if parsing fails. - def parse_retry_after(value) - # Try parsing as integer (seconds) - seconds = Integer(value, exception: false) - return seconds.to_f if seconds - - # Try parsing as HTTP date - begin - retry_time = Time.httpdate(value) - delay = retry_time - Time.now - delay.positive? ? delay : nil - rescue ArgumentError - nil - end - end - - # Adds random jitter to a delay value. - # @param delay [Float] The base delay in seconds. - # @return [Float] The delay with jitter applied. - def add_jitter(delay) - jitter = delay * JITTER_FACTOR * (rand - 0.5) * 2 - [delay + jitter, 0].max - end - - LOCALHOST_HOSTS = %w[localhost 127.0.0.1 [::1]].freeze - - # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. - # @return [URI::Generic] The URL. - def build_url(request) - encoded_query = request.encode_query - - # If the path is already an absolute URL, use it directly - if request.path.start_with?("http://", "https://") - url = request.path - url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? - parsed = URI.parse(url) - validate_https!(parsed) - return parsed - end - - path = request.path.start_with?("/") ? request.path[1..] : request.path - base = request.base_url || @base_url - url = "#{base.chomp("/")}/#{path}" - url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? - parsed = URI.parse(url) - validate_https!(parsed) - parsed - end - - # Raises if the URL uses http:// for a non-localhost host, which would - # send authentication credentials in plaintext. - # @param url [URI::Generic] The parsed URL. - def validate_https!(url) - return if url.scheme != "http" - return if LOCALHOST_HOSTS.include?(url.host) - - raise ArgumentError, "Refusing to send request to non-HTTPS URL: #{url}. " \ - "HTTP is only allowed for localhost. Use HTTPS or pass a localhost URL." - end - - # @param url [URI::Generic] The url to the resource. - # @param method [String] The HTTP method to use. - # @param headers [Hash] The headers for the request. - # @param body [String, nil] The body for the request. - # @return [HTTP::Request] The HTTP request. - def build_http_request(url:, method:, headers: {}, body: nil) - request = Net::HTTPGenericRequest.new( - method, - !body.nil?, - method != "HEAD", - url - ) - - request_headers = @default_headers.merge(headers) - request_headers.each { |name, value| request[name] = value } - request.body = body if body - - request - end - - # @param query [Hash] The query for the request. - # @return [String, nil] The encoded query. - def encode_query(query) - query.to_h.empty? ? nil : URI.encode_www_form(query) - end - - # @param url [URI::Generic] The url to connect to. - # @return [Net::HTTP] The HTTP connection. - def connect(url) - is_https = (url.scheme == "https") - - port = if url.port - url.port - elsif is_https - Net::HTTP.https_default_port - else - Net::HTTP.http_default_port - end - - http = Net::HTTP.new(url.host, port) - http.use_ssl = is_https - http.verify_mode = OpenSSL::SSL::VERIFY_PEER if is_https - # NOTE: We handle retries at the application level with HTTP status code awareness, - # so we set max_retries to 0 to disable Net::HTTP's built-in network-level retries. - http.max_retries = 0 - http - end - - # @return [String] - def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)} @base_url=#{@base_url.inspect}>" - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb deleted file mode 100644 index ab627ffc7025..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_item_iterator.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class CursorItemIterator < ItemIterator - # Instantiates a CursorItemIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields individual items from it. - # - # @param initial_cursor [String] The initial cursor to use when iterating, if any. - # @param cursor_field [Symbol] The field in API responses to extract the next cursor from. - # @param item_field [Symbol] The field in API responses to extract the items to iterate over. - # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. - # @return [Seed::Internal::CursorItemIterator] - def initialize(initial_cursor:, cursor_field:, item_field:, &) - super() - @item_field = item_field - @page_iterator = CursorPageIterator.new(initial_cursor:, cursor_field:, &) - @page = nil - end - - # Returns the CursorPageIterator mediating access to the underlying API. - # - # @return [Seed::Internal::CursorPageIterator] - def pages - @page_iterator - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb deleted file mode 100644 index f479a749fef9..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/cursor_page_iterator.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class CursorPageIterator - include Enumerable - - # Instantiates a CursorPageIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields pages of items. - # - # @param initial_cursor [String] The initial cursor to use when iterating, if any. - # @param cursor_field [Symbol] The name of the field in API responses to extract the next cursor from. - # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. - # @return [Seed::Internal::CursorPageIterator] - def initialize(initial_cursor:, cursor_field:, &block) - @need_initial_load = initial_cursor.nil? - @cursor = initial_cursor - @cursor_field = cursor_field - @get_next_page = block - end - - # Iterates over each page returned by the API. - # - # @param block [Proc] The block which each retrieved page is yielded to. - # @return [NilClass] - def each(&block) - while (page = next_page) - block.call(page) - end - end - - # Whether another page will be available from the API. - # - # @return [Boolean] - def next? - @need_initial_load || !@cursor.nil? - end - - # Retrieves the next page from the API. - # - # @return [Boolean] - def next_page - return if !@need_initial_load && @cursor.nil? - - @need_initial_load = false - fetched_page = @get_next_page.call(@cursor) - @cursor = fetched_page.send(@cursor_field) - fetched_page - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb deleted file mode 100644 index 1284fb0fd367..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/item_iterator.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class ItemIterator - include Enumerable - - # Iterates over each item returned by the API. - # - # @param block [Proc] The block which each retrieved item is yielded to. - # @return [NilClass] - def each(&block) - while (item = next_element) - block.call(item) - end - end - - # Whether another item will be available from the API. - # - # @return [Boolean] - def next? - load_next_page if @page.nil? - return false if @page.nil? - - return true if any_items_in_cached_page? - - load_next_page - any_items_in_cached_page? - end - - # Retrieves the next item from the API. - def next_element - item = next_item_from_cached_page - return item if item - - load_next_page - next_item_from_cached_page - end - - private - - def next_item_from_cached_page - return unless @page - - @page.send(@item_field).shift - end - - def any_items_in_cached_page? - return false unless @page - - !@page.send(@item_field).empty? - end - - def load_next_page - @page = @page_iterator.next_page - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb deleted file mode 100644 index f8840246686d..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_item_iterator.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class OffsetItemIterator < ItemIterator - # Instantiates an OffsetItemIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields the individual items from it. - # - # @param initial_page [Integer] The initial page or offset to start from when iterating. - # @param item_field [Symbol] The name of the field in API responses to extract the items to iterate over. - # @param has_next_field [Symbol] The name of the field in API responses containing a boolean of whether another page exists. - # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) - # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. - # - # @return [Seed::Internal::OffsetItemIterator] - def initialize(initial_page:, item_field:, has_next_field:, step:, &) - super() - @item_field = item_field - @page_iterator = OffsetPageIterator.new(initial_page:, item_field:, has_next_field:, step:, &) - @page = nil - end - - # Returns the OffsetPageIterator that is mediating access to the underlying API. - # - # @return [Seed::Internal::OffsetPageIterator] - def pages - @page_iterator - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb deleted file mode 100644 index 051b65c5774c..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/iterators/offset_page_iterator.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class OffsetPageIterator - include Enumerable - - # Instantiates an OffsetPageIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields pages of items from it. - # - # @param initial_page [Integer] The initial page to use when iterating, if any. - # @param item_field [Symbol] The field to pull the list of items to iterate over. - # @param has_next_field [Symbol] The field to pull the boolean of whether a next page exists from, if any. - # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) - # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. - # @return [Seed::Internal::OffsetPageIterator] - def initialize(initial_page:, item_field:, has_next_field:, step:, &block) - @page_number = initial_page || (step ? 0 : 1) - @item_field = item_field - @has_next_field = has_next_field - @step = step - @get_next_page = block - - # A cache of whether the API has another page, if it gives us that information... - @next_page = nil - # ...or the actual next page, preloaded, if it doesn't. - @has_next_page = nil - end - - # Iterates over each page returned by the API. - # - # @param block [Proc] The block which each retrieved page is yielded to. - # @return [NilClass] - def each(&block) - while (page = next_page) - block.call(page) - end - end - - # Whether another page will be available from the API. - # - # @return [Boolean] - def next? - return @has_next_page unless @has_next_page.nil? - return true if @next_page - - fetched_page = @get_next_page.call(@page_number) - fetched_page_items = fetched_page&.send(@item_field) - if fetched_page_items.nil? || fetched_page_items.empty? - @has_next_page = false - else - @next_page = fetched_page - true - end - end - - # Returns the next page from the API. - def next_page - return nil if @page_number.nil? - - if @next_page - this_page = @next_page - @next_page = nil - else - this_page = @get_next_page.call(@page_number) - end - - @has_next_page = this_page&.send(@has_next_field) if @has_next_field - - items = this_page.send(@item_field) - if items.nil? || items.empty? - @page_number = nil - return nil - elsif @step - @page_number += items.length - else - @page_number += 1 - end - - this_page - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb deleted file mode 100644 index 667ceae8ac59..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/request.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module JSON - # @api private - class Request < Seed::Internal::Http::BaseRequest - attr_reader :body - - # @param base_url [String] The base URL for the request - # @param path [String] The path for the request - # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) - # @param headers [Hash] Additional headers for the request (optional) - # @param query [Hash] Query parameters for the request (optional) - # @param body [Object, nil] The JSON request body (optional) - # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] - def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) - super(base_url:, path:, method:, headers:, query:, request_options:) - - @body = body - end - - # @return [Hash] The encoded HTTP request headers. - # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) - # that must not be overridden by additional_headers from request_options. - def encode_headers(protected_keys: []) - sdk_headers = { - "Content-Type" => "application/json", - "Accept" => "application/json" - }.merge(@headers) - merge_additional_headers(sdk_headers, protected_keys:) - end - - # @return [String, nil] The encoded HTTP request body. - def encode_body - @body.nil? ? nil : ::JSON.generate(@body) - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb deleted file mode 100644 index f80a15fb962c..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/json/serializable.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module JSON - module Serializable - # Loads data from JSON into its deserialized form - # - # @param str [String] Raw JSON to load into an object - # @return [Object] - def load(str) - raise NotImplementedError - end - - # Dumps data from its deserialized form into JSON - # - # @param value [Object] The deserialized value - # @return [String] - def dump(value) - raise NotImplementedError - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb deleted file mode 100644 index 307ad7436a57..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_encoder.rb +++ /dev/null @@ -1,141 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Multipart - # Encodes parameters into a `multipart/form-data` payload as described by RFC - # 2388: - # - # https://tools.ietf.org/html/rfc2388 - # - # This is most useful for transferring file-like objects. - # - # Parameters should be added with `#encode`. When ready, use `#body` to get - # the encoded result and `#content_type` to get the value that should be - # placed in the `Content-Type` header of a subsequent request (which includes - # a boundary value). - # - # This abstraction is heavily inspired by Stripe's multipart/form-data implementation, - # which can be found here: - # - # https://github.com/stripe/stripe-ruby/blob/ca00b676f04ac421cf5cb5ff0325f243651677b6/lib/stripe/multipart_encoder.rb#L18 - # - # @api private - class Encoder - CONTENT_TYPE = "multipart/form-data" - CRLF = "\r\n" - - attr_reader :boundary, :body - - def initialize - # Chose the same number of random bytes that Go uses in its standard - # library implementation. Easily enough entropy to ensure that it won't - # be present in a file we're sending. - @boundary = SecureRandom.hex(30) - - @body = String.new - @closed = false - @first_field = true - end - - # Gets the content type string including the boundary. - # - # @return [String] The content type with boundary - def content_type - "#{CONTENT_TYPE}; boundary=#{@boundary}" - end - - # Encode the given FormData object into a multipart/form-data payload. - # - # @param form_data [FormData] The form data to encode - # @return [String] The encoded body. - def encode(form_data) - return "" if form_data.parts.empty? - - form_data.parts.each do |part| - write_part(part) - end - close - - @body - end - - # Writes a FormDataPart to the encoder. - # - # @param part [FormDataPart] The part to write - # @return [nil] - def write_part(part) - raise "Cannot write to closed encoder" if @closed - - write_field( - name: part.name, - data: part.contents, - filename: part.filename, - headers: part.headers - ) - - nil - end - - # Writes a field to the encoder. - # - # @param name [String] The field name - # @param data [String] The field data - # @param filename [String, nil] Optional filename - # @param headers [Hash, nil] Optional additional headers - # @return [nil] - def write_field(name:, data:, filename: nil, headers: nil) - raise "Cannot write to closed encoder" if @closed - - if @first_field - @first_field = false - else - @body << CRLF - end - - @body << "--#{@boundary}#{CRLF}" - @body << %(Content-Disposition: form-data; name="#{escape(name.to_s)}") - @body << %(; filename="#{escape(filename)}") if filename - @body << CRLF - - if headers - headers.each do |key, value| - @body << "#{key}: #{value}#{CRLF}" - end - elsif filename - # Default content type for files. - @body << "Content-Type: application/octet-stream#{CRLF}" - end - - @body << CRLF - @body << data.to_s - - nil - end - - # Finalizes the encoder by writing the final boundary. - # - # @return [nil] - def close - raise "Encoder already closed" if @closed - - @body << CRLF - @body << "--#{@boundary}--" - @closed = true - - nil - end - - private - - # Escapes quotes for use in header values and replaces line breaks with spaces. - # - # @param str [String] The string to escape - # @return [String] The escaped string - def escape(str) - str.to_s.gsub('"', "%22").tr("\n", " ").tr("\r", " ") - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb deleted file mode 100644 index 5be1bb25341f..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Multipart - # @api private - class FormData - # @return [Array] The parts in this multipart form data. - attr_reader :parts - - # @return [Encoder] The encoder for this multipart form data. - private attr_reader :encoder - - def initialize - @encoder = Encoder.new - @parts = [] - end - - # Adds a new part to the multipart form data. - # - # @param name [String] The name of the form field - # @param value [String, Integer, Float, Boolean, #read] The value of the field - # @param content_type [String, nil] Optional content type - # @return [self] Returns self for chaining - def add(name:, value:, content_type: nil) - headers = content_type ? { "Content-Type" => content_type } : nil - add_part(FormDataPart.new(name:, value:, headers:)) - end - - # Adds a file to the multipart form data. - # - # @param name [String] The name of the form field - # @param file [#read] The file or readable object - # @param filename [String, nil] Optional filename (defaults to basename of path for File objects) - # @param content_type [String, nil] Optional content type (e.g. "image/png") - # @return [self] Returns self for chaining - def add_file(name:, file:, filename: nil, content_type: nil) - headers = content_type ? { "Content-Type" => content_type } : nil - filename ||= filename_for(file) - add_part(FormDataPart.new(name:, value: file, filename:, headers:)) - end - - # Adds a pre-created part to the multipart form data. - # - # @param part [FormDataPart] The part to add - # @return [self] Returns self for chaining - def add_part(part) - @parts << part - self - end - - # Gets the content type string including the boundary. - # - # @return [String] The content type with boundary. - def content_type - @encoder.content_type - end - - # Encode the multipart form data into a multipart/form-data payload. - # - # @return [String] The encoded body. - def encode - @encoder.encode(self) - end - - private - - def filename_for(file) - if file.is_a?(::File) || file.respond_to?(:path) - ::File.basename(file.path) - elsif file.respond_to?(:name) - file.name - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb deleted file mode 100644 index de45416ee087..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_form_data_part.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require "securerandom" - -module Seed - module Internal - module Multipart - # @api private - class FormDataPart - attr_reader :name, :contents, :filename, :headers - - # @param name [String] The name of the form field - # @param value [String, Integer, Float, Boolean, File, #read] The value of the field - # @param filename [String, nil] Optional filename for file uploads - # @param headers [Hash, nil] Optional additional headers - def initialize(name:, value:, filename: nil, headers: nil) - @name = name - @contents = convert_to_content(value) - @filename = filename - @headers = headers - end - - # Converts the part to a hash suitable for serialization. - # - # @return [Hash] A hash representation of the part - def to_hash - result = { - name: @name, - contents: @contents - } - result[:filename] = @filename if @filename - result[:headers] = @headers if @headers - result - end - - private - - # Converts various types of values to a content representation - # @param value [String, Integer, Float, Boolean, #read] The value to convert - # @return [String] The string representation of the value - def convert_to_content(value) - if value.respond_to?(:read) - value.read - else - value.to_s - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb deleted file mode 100644 index 9fa80cee01ab..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/multipart/multipart_request.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Multipart - # @api private - class Request < Seed::Internal::Http::BaseRequest - attr_reader :body - - # @param base_url [String] The base URL for the request - # @param path [String] The path for the request - # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) - # @param headers [Hash] Additional headers for the request (optional) - # @param query [Hash] Query parameters for the request (optional) - # @param body [MultipartFormData, nil] The multipart form data for the request (optional) - # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] - def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) - super(base_url:, path:, method:, headers:, query:, request_options:) - - @body = body - end - - # @return [Hash] The encoded HTTP request headers. - # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) - # that must not be overridden by additional_headers from request_options. - def encode_headers(protected_keys: []) - sdk_headers = { - "Content-Type" => @body.content_type - }.merge(@headers) - merge_additional_headers(sdk_headers, protected_keys:) - end - - # @return [String, nil] The encoded HTTP request body. - def encode_body - @body&.encode - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb deleted file mode 100644 index f3c7c1bd9549..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/array.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # An array of a specific type - class Array - include Seed::Internal::Types::Type - - attr_reader :type - - class << self - # Instantiates a new `Array` of a given type - # - # @param type [Object] The member type of this array - # - # @return [Seed::Internal::Types::Array] - def [](type) - new(type) - end - end - - # @api private - def initialize(type) - @type = type - end - - # Coerces a value into this array - # - # @param value [Object] - # @option strict [Boolean] - # @return [::Array] - def coerce(value, strict: strict?) - unless value.is_a?(::Array) - raise Errors::TypeError, "cannot coerce `#{value.class}` to Array<#{type}>" if strict - - return value - end - - value.map do |element| - Utils.coerce(type, element, strict: strict) - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb deleted file mode 100644 index d4e3277e566f..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/boolean.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - module Boolean - extend Seed::Internal::Types::Union - - member TrueClass - member FalseClass - - # Overrides the base coercion method for enums to allow integer and string values to become booleans - # - # @param value [Object] - # @option strict [Boolean] - # @return [Object] - def self.coerce(value, strict: strict?) - case value - when TrueClass, FalseClass - return value - when Integer - return value == 1 - when String - return %w[1 true].include?(value) - end - - raise Errors::TypeError, "cannot coerce `#{value.class}` to Boolean" if strict - - value - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb deleted file mode 100644 index 72e45e4c1f27..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/enum.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # Module for defining enums - module Enum - include Type - - # @api private - # - # @return [Array] - def values - @values ||= constants.map { |c| const_get(c) } - end - - # @api private - def finalize! - values - end - - # @api private - def strict? - @strict ||= false - end - - # @api private - def strict! - @strict = true - end - - def coerce(value, strict: strict?) - coerced_value = Utils.coerce(Symbol, value) - - return coerced_value if values.include?(coerced_value) - - raise Errors::TypeError, "`#{value}` not in enum #{self}" if strict - - value - end - - # Parse JSON string and coerce to the enum value - # - # @param str [String] JSON string to parse - # @return [String] The enum value - def load(str) - coerce(::JSON.parse(str)) - end - - def inspect - "#{name}[#{values.join(", ")}]" - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb deleted file mode 100644 index d8bffa63ac11..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/hash.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - class Hash - include Type - - attr_reader :key_type, :value_type - - class << self - def [](key_type, value_type) - new(key_type, value_type) - end - end - - def initialize(key_type, value_type) - @key_type = key_type - @value_type = value_type - end - - def coerce(value, strict: strict?) - unless value.is_a?(::Hash) - raise Errors::TypeError, "not hash" if strict - - return value - end - - value.to_h do |k, v| - [Utils.coerce(key_type, k, strict: strict), Utils.coerce(value_type, v, strict: strict)] - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb deleted file mode 100644 index 8caca14ff7ea..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model.rb +++ /dev/null @@ -1,208 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # @abstract - # - # An abstract model that all data objects will inherit from - class Model - include Type - - class << self - # The defined fields for this model - # - # @api private - # - # @return [Hash] - def fields - @fields ||= if self < Seed::Internal::Types::Model - superclass.fields.dup - else - {} - end - end - - # Any extra fields that have been created from instantiation - # - # @api private - # - # @return [Hash] - def extra_fields - @extra_fields ||= {} - end - - # Define a new field on this model - # - # @param name [Symbol] The name of the field - # @param type [Class] Type of the field - # @option optional [Boolean] If it is an optional field - # @option nullable [Boolean] If it is a nullable field - # @option api_name [Symbol, String] Name in the API of this field. When serializing/deserializing, will use - # this field name - # @return [void] - def field(name, type, optional: false, nullable: false, api_name: nil, default: nil) - add_field_definition(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, - default: default) - - define_accessor(name) - define_setter(name) - end - - # Define a new literal for this model - # - # @param name [Symbol] - # @param value [Object] - # @option api_name [Symbol, String] - # @return [void] - def literal(name, value, api_name: nil) - add_field_definition(name: name, type: value.class, optional: false, nullable: false, api_name: api_name, - value: value) - - define_accessor(name) - end - - # Adds a new field definition into the class's fields registry - # - # @api private - # - # @param name [Symbol] - # @param type [Class] - # @option optional [Boolean] - # @return [void] - private def add_field_definition(name:, type:, optional:, nullable:, api_name:, default: nil, value: nil) - fields[name.to_sym] = - Field.new(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, - value: value, default: default) - end - - # Adds a new field definition into the class's extra fields registry - # - # @api private - # - # @param name [Symbol] - # @param type [Class] - # @option required [Boolean] - # @option optional [Boolean] - # @return [void] - def add_extra_field_definition(name:, type:) - return if extra_fields.key?(name.to_sym) - - extra_fields[name.to_sym] = Field.new(name: name, type: type, optional: true, nullable: false) - - define_accessor(name) - define_setter(name) - end - - # @api private - private def define_accessor(name) - method_name = name.to_sym - - define_method(method_name) do - @data[name] - end - end - - # @api private - private def define_setter(name) - method_name = :"#{name}=" - - define_method(method_name) do |val| - @data[name] = val - end - end - - def coerce(value, strict: (respond_to?(:strict?) ? strict? : false)) # rubocop:disable Lint/UnusedMethodArgument - return value if value.is_a?(self) - - return value unless value.is_a?(::Hash) - - new(value) - end - - def load(str) - coerce(::JSON.parse(str, symbolize_names: true)) - end - - def ===(instance) - instance.class.ancestors.include?(self) - end - end - - # Creates a new instance of this model - # TODO: Should all this logic be in `#coerce` instead? - # - # @param values [Hash] - # @option strict [Boolean] - # @return [self] - def initialize(values = {}) - @data = {} - - values = Utils.symbolize_keys(values.dup) - - self.class.fields.each do |field_name, field| - value = values.delete(field.api_name.to_sym) || values.delete(field.api_name) || values.delete(field_name) - - field_value = value || (if field.literal? - field.value - elsif field.default - field.default - end) - - @data[field_name] = Utils.coerce(field.type, field_value) - end - - # Any remaining values in the input become extra fields - values.each do |name, value| - self.class.add_extra_field_definition(name: name, type: value.class) - - @data[name.to_sym] = value - end - end - - def to_h - result = self.class.fields.merge(self.class.extra_fields).each_with_object({}) do |(name, field), acc| - # If there is a value present in the data, use that value - # If there is a `nil` value present in the data, and it is optional but NOT nullable, exclude key altogether - # If there is a `nil` value present in the data, and it is optional and nullable, use the nil value - - value = @data[name] - - next if value.nil? && field.optional && !field.nullable - - if value.is_a?(::Array) - value = value.map { |item| item.respond_to?(:to_h) ? item.to_h : item } - elsif value.respond_to?(:to_h) - value = value.to_h - end - - acc[field.api_name] = value - end - - # Inject union discriminant if this instance was coerced from a discriminated union - # and the discriminant key is not already present in the result - discriminant_key = instance_variable_get(:@_fern_union_discriminant_key) - discriminant_value = instance_variable_get(:@_fern_union_discriminant_value) - result[discriminant_key] = discriminant_value if discriminant_key && discriminant_value && !result.key?(discriminant_key) - - result - end - - def ==(other) - self.class == other.class && to_h == other.to_h - end - - # @return [String] - def inspect - attrs = @data.map do |name, value| - field = self.class.fields[name] || self.class.extra_fields[name] - display_value = field&.sensitive? ? "[REDACTED]" : value.inspect - "#{name}=#{display_value}" - end - - "#<#{self.class.name}:0x#{object_id&.to_s(16)} #{attrs.join(" ")}>" - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb deleted file mode 100644 index 6ce0186f6a5d..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/model/field.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - class Model - # Definition of a field on a model - class Field - SENSITIVE_FIELD_NAMES = %i[ - password secret token api_key apikey access_token refresh_token - client_secret client_id credential bearer authorization - ].freeze - - attr_reader :name, :type, :optional, :nullable, :api_name, :value, :default - - def initialize(name:, type:, optional: false, nullable: false, api_name: nil, value: nil, default: nil) - @name = name.to_sym - @type = type - @optional = optional - @nullable = nullable - @api_name = api_name || name.to_s - @value = value - @default = default - end - - def literal? - !value.nil? - end - - def sensitive? - SENSITIVE_FIELD_NAMES.include?(@name) || - SENSITIVE_FIELD_NAMES.any? { |sensitive| @name.to_s.include?(sensitive.to_s) } - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb deleted file mode 100644 index 5866caf1dbda..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/type.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # @abstract - module Type - include Seed::Internal::JSON::Serializable - - # Coerces a value to this type - # - # @param value [unknown] - # @option strict [Boolean] If we should strictly coerce this value - def coerce(value, strict: strict?) - raise NotImplementedError - end - - # Returns if strictness is on for this type, defaults to `false` - # - # @return [Boolean] - def strict? - @strict ||= false - end - - # Enable strictness by default for this type - # - # @return [void] - def strict! - @strict = true - self - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb deleted file mode 100644 index f3e118a2fa78..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/union.rb +++ /dev/null @@ -1,161 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # Define a union between two types - module Union - include Seed::Internal::Types::Type - - def members - @members ||= [] - end - - # Add a member to this union - # - # @param type [Object] - # @option key [Symbol, String] - # @return [void] - def member(type, key: nil) - members.push([key, Utils.wrap_type(type)]) - self - end - - def type_member?(type) - members.any? { |_key, type_fn| type == type_fn.call } - end - - # Set the discriminant for this union - # - # @param key [Symbol, String] - # @return [void] - def discriminant(key) - @discriminant = key - end - - # @api private - private def discriminated? - !@discriminant.nil? - end - - # Check if value matches a type, handling type wrapper instances - # (Internal::Types::Hash and Internal::Types::Array instances) - # - # @param value [Object] - # @param member_type [Object] - # @return [Boolean] - private def type_matches?(value, member_type) - case member_type - when Seed::Internal::Types::Hash - value.is_a?(::Hash) - when Seed::Internal::Types::Array - value.is_a?(::Array) - when Class, Module - value.is_a?(member_type) - else - false - end - end - - # Resolves the type of a value to be one of the members - # - # @param value [Object] - # @return [Class] - private def resolve_member(value) - if discriminated? && value.is_a?(::Hash) - # Try both symbol and string keys for the discriminant - discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) - - return if discriminant_value.nil? - - # Convert to string for consistent comparison - discriminant_str = discriminant_value.to_s - - # First try exact match - members_hash = members.to_h - result = members_hash[discriminant_str]&.call - return result if result - - # Try case-insensitive match as fallback - discriminant_lower = discriminant_str.downcase - matching_keys = members_hash.keys.select { |k| k.to_s.downcase == discriminant_lower } - - # Only use case-insensitive match if exactly one key matches (avoid ambiguity) - return members_hash[matching_keys.first]&.call if matching_keys.length == 1 - - nil - else - # First try exact type matching - result = members.find do |_key, mem| - member_type = Utils.unwrap_type(mem) - type_matches?(value, member_type) - end&.last&.call - - return result if result - - # For Hash values, try to coerce into Model member types - if value.is_a?(::Hash) - members.find do |_key, mem| - member_type = Utils.unwrap_type(mem) - # Check if member_type is a Model class - next unless member_type.is_a?(Class) && member_type <= Model - - # Try to coerce the hash into this model type with strict mode - begin - candidate = Utils.coerce(member_type, value, strict: true) - - # Validate that all required (non-optional) fields are present - # This ensures undiscriminated unions properly distinguish between member types - member_type.fields.each do |field_name, field| - raise Errors::TypeError, "Required field `#{field_name}` missing for union member #{member_type.name}" if candidate.instance_variable_get(:@data)[field_name].nil? && !field.optional - end - - true - rescue Errors::TypeError - false - end - end&.last&.call - end - end - end - - def coerce(value, strict: strict?) - type = resolve_member(value) - - unless type - return value unless strict - - if discriminated? - raise Errors::TypeError, - "value of type `#{value.class}` not member of union #{self}" - end - - raise Errors::TypeError, "could not resolve to member of union #{self}" - end - - coerced = Utils.coerce(type, value, strict: strict) - - # For discriminated unions, store the discriminant info on the coerced instance - # so it can be injected back during serialization (to_h) - if discriminated? && value.is_a?(::Hash) && coerced.is_a?(Model) - discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) - if discriminant_value - coerced.instance_variable_set(:@_fern_union_discriminant_key, @discriminant.to_s) - coerced.instance_variable_set(:@_fern_union_discriminant_value, discriminant_value) - end - end - - coerced - end - - # Parse JSON string and coerce to the correct union member type - # - # @param str [String] JSON string to parse - # @return [Object] Coerced value matching a union member - def load(str) - coerce(::JSON.parse(str, symbolize_names: true)) - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb deleted file mode 100644 index 7b58de956da9..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/unknown.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - module Unknown - include Seed::Internal::Types::Type - - def coerce(value) - value - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb deleted file mode 100644 index 0ac6179e855f..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/internal/types/utils.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # Utilities for dealing with and checking types - module Utils - # Wraps a type into a type function - # - # @param type [Proc, Object] - # @return [Proc] - def self.wrap_type(type) - case type - when Proc - type - else - -> { type } - end - end - - # Resolves a type or type function into a type - # - # @param type [Proc, Object] - # @return [Object] - def self.unwrap_type(type) - type.is_a?(Proc) ? type.call : type - end - - def self.coerce(target, value, strict: false) - type = unwrap_type(target) - - case type - in Array - case value - when ::Array - return type.coerce(value, strict: strict) - when Set, ::Hash - return coerce(type, value.to_a) - end - in Hash - case value - when ::Hash - return type.coerce(value, strict: strict) - when ::Array - return coerce(type, value.to_h) - end - in ->(t) { t <= NilClass } - return nil - in ->(t) { t <= String } - case value - when String, Symbol, Numeric, TrueClass, FalseClass - return value.to_s - end - in ->(t) { t <= Symbol } - case value - when Symbol, String - return value.to_sym - end - in ->(t) { t <= Integer } - case value - when Numeric, String, Time - return value.to_i - end - in ->(t) { t <= Float } - case value - when Numeric, Time, String - return value.to_f - end - in ->(t) { t <= Model } - case value - when type - return value - when ::Hash - return type.coerce(value, strict: strict) - end - in Module - case type - in ->(t) { - t.singleton_class.included_modules.include?(Enum) || - t.singleton_class.included_modules.include?(Union) - } - return type.coerce(value, strict: strict) - else - value # rubocop:disable Lint/Void - end - else - value # rubocop:disable Lint/Void - end - - raise Errors::TypeError, "cannot coerce value of type `#{value.class}` to `#{target}`" if strict - - value - end - - def self.symbolize_keys(hash) - hash.transform_keys(&:to_sym) - end - - # Converts camelCase keys to snake_case symbols - # This allows SDK methods to accept both snake_case and camelCase keys - # e.g., { refundMethod: ... } becomes { refund_method: ... } - # - # @param hash [Hash] - # @return [Hash] - def self.normalize_keys(hash) - hash.transform_keys do |key| - key_str = key.to_s - # Convert camelCase to snake_case - snake_case = key_str.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase - snake_case.to_sym - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb deleted file mode 100644 index 2df8e8d92220..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/audit_info.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - # Common audit metadata. - class AuditInfo < Internal::Types::Model - field :created_by, -> { String }, optional: true, nullable: false, api_name: "createdBy" - field :created_date_time, -> { String }, optional: true, nullable: false, api_name: "createdDateTime" - field :modified_by, -> { String }, optional: true, nullable: false, api_name: "modifiedBy" - field :modified_date_time, -> { String }, optional: true, nullable: false, api_name: "modifiedDateTime" - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb deleted file mode 100644 index 8e6b5538aad2..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class BaseOrg < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :metadata, -> { Seed::Types::BaseOrgMetadata }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb deleted file mode 100644 index 4167d5d9464f..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/base_org_metadata.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class BaseOrgMetadata < Internal::Types::Model - field :region, -> { String }, optional: false, nullable: false - field :tier, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb deleted file mode 100644 index 8d74cacbb442..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class CombinedEntity < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: true, nullable: false - field :summary, -> { String }, optional: true, nullable: false - field :status, -> { Seed::Types::CombinedEntityStatus }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb deleted file mode 100644 index ce445c2e0f19..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/combined_entity_status.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - module CombinedEntityStatus - extend Seed::Internal::Types::Enum - - ACTIVE = "active" - ARCHIVED = "archived" - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb deleted file mode 100644 index bf18865e61b5..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/describable.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class Describable < Internal::Types::Model - field :name, -> { String }, optional: true, nullable: false - field :summary, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb deleted file mode 100644 index 5839792d7c2c..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class DetailedOrg < Internal::Types::Model - field :metadata, -> { Seed::Types::DetailedOrgMetadata }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb deleted file mode 100644 index 5c5a311e208c..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/detailed_org_metadata.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class DetailedOrgMetadata < Internal::Types::Model - field :region, -> { String }, optional: false, nullable: false - field :domain, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb deleted file mode 100644 index 420ec1d027dc..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/identifiable.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class Identifiable < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb deleted file mode 100644 index 3cf709f96cd0..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class Organization < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :metadata, -> { Seed::Types::OrganizationMetadata }, optional: true, nullable: false - field :name, -> { String }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb deleted file mode 100644 index c25fc51ef2a5..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/organization_metadata.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class OrganizationMetadata < Internal::Types::Model - field :region, -> { String }, optional: false, nullable: false - field :domain, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb deleted file mode 100644 index 10755d50c291..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paginated_result.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class PaginatedResult < Internal::Types::Model - field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false - field :results, -> { Internal::Types::Array[Object] }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb deleted file mode 100644 index 925959b21486..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/paging_cursors.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class PagingCursors < Internal::Types::Model - field :next_, -> { String }, optional: false, nullable: false, api_name: "next" - field :previous, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb deleted file mode 100644 index be802bd7fd55..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_execution_context.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - module RuleExecutionContext - extend Seed::Internal::Types::Enum - - PROD = "prod" - STAGING = "staging" - DEV = "dev" - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb deleted file mode 100644 index e11c0b53c809..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class RuleResponse < Internal::Types::Model - field :created_by, -> { String }, optional: true, nullable: false, api_name: "createdBy" - field :created_date_time, -> { String }, optional: true, nullable: false, api_name: "createdDateTime" - field :modified_by, -> { String }, optional: true, nullable: false, api_name: "modifiedBy" - field :modified_date_time, -> { String }, optional: true, nullable: false, api_name: "modifiedDateTime" - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: false, nullable: false - field :status, -> { Seed::Types::RuleResponseStatus }, optional: false, nullable: false - field :execution_context, -> { Seed::Types::RuleExecutionContext }, optional: true, nullable: false, api_name: "executionContext" - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb deleted file mode 100644 index 609f51bfabb4..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_response_status.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - module RuleResponseStatus - extend Seed::Internal::Types::Enum - - ACTIVE = "active" - INACTIVE = "inactive" - DRAFT = "draft" - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb deleted file mode 100644 index 798fc21f7888..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class RuleType < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: false, nullable: false - field :description, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb deleted file mode 100644 index 1efe97d4ec28..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/rule_type_search_response.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class RuleTypeSearchResponse < Internal::Types::Model - field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false - field :results, -> { Internal::Types::Array[Seed::Types::RuleType] }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb deleted file mode 100644 index 9d5a150b6803..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class User < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :email, -> { String }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb deleted file mode 100644 index b08cad04abab..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/types/user_search_response.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class UserSearchResponse < Internal::Types::Model - field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false - field :results, -> { Internal::Types::Array[Seed::Types::User] }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb b/seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb deleted file mode 100644 index 00dd45cdd958..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/lib/seed/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module Seed - VERSION = "0.0.1" -end diff --git a/seed/ruby-sdk-v2/allof-inline/reference.md b/seed/ruby-sdk-v2/allof-inline/reference.md deleted file mode 100644 index 2a0eb7a77a05..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/reference.md +++ /dev/null @@ -1,228 +0,0 @@ -# Reference -
client.search_rule_types() -> Seed::Types::RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.search_rule_types -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `String` - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.create_rule(request) -> Seed::Types::RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.create_rule( - name: "name", - execution_context: "prod" -) -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `String` - -
-
- -
-
- -**execution_context:** `Seed::Types::RuleExecutionContext` - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.list_users() -> Seed::Types::UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.list_users -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.get_entity() -> Seed::Types::CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.get_entity -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.get_organization() -> Seed::Types::Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.get_organization -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- diff --git a/seed/ruby-sdk-v2/allof-inline/seed.gemspec b/seed/ruby-sdk-v2/allof-inline/seed.gemspec deleted file mode 100644 index f87ecf152d70..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/seed.gemspec +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative "lib/seed/version" -require_relative "custom.gemspec" - -# NOTE: A handful of these fields are required as part of the Ruby specification. -# You can change them here or overwrite them in the custom gemspec file. -Gem::Specification.new do |spec| - spec.name = "fern_allof-inline" - spec.authors = ["Seed"] - spec.version = Seed::VERSION - spec.summary = "Ruby client library for the Seed API" - spec.description = "The Seed Ruby library provides convenient access to the Seed API from Ruby." - spec.required_ruby_version = ">= 3.3.0" - spec.metadata["rubygems_mfa_required"] = "true" - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - gemspec = File.basename(__FILE__) - spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| - ls.readlines("\x0", chomp: true).reject do |f| - (f == gemspec) || - f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile]) - end - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] - - # For more information and examples about making a new gem, check out our - # guide at: https://bundler.io/guides/creating_gem.html - - # Load custom gemspec configuration if it exists - custom_gemspec_file = File.join(__dir__, "custom.gemspec.rb") - add_custom_gemspec_data(spec) if File.exist?(custom_gemspec_file) -end diff --git a/seed/ruby-sdk-v2/allof-inline/snippet.json b/seed/ruby-sdk-v2/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/ruby-sdk-v2/allof-inline/test/custom.test.rb b/seed/ruby-sdk-v2/allof-inline/test/custom.test.rb deleted file mode 100644 index 4bd57989d43d..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/custom.test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# This is a custom test file, if you wish to add more tests -# to your SDK. -# Be sure to mark this file in `.fernignore`. -# -# If you include example requests/responses in your fern definition, -# you will have tests automatically generated for you. - -# This test is run via command line: rake customtest -describe "Custom Test" do - it "Default" do - refute false - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/test_helper.rb b/seed/ruby-sdk-v2/allof-inline/test/test_helper.rb deleted file mode 100644 index b086fe6d76ec..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/test_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require_relative "../lib/seed" diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb deleted file mode 100644 index 5008f6abf69f..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_cursor_item_iterator.rb +++ /dev/null @@ -1,189 +0,0 @@ -# frozen_string_literal: true - -require "minitest/autorun" -require "stringio" -require "json" -require "test_helper" - -NUMBERS = (1..65).to_a -PageResponse = Struct.new(:cards, :next_cursor, keyword_init: true) - -class CursorItemIteratorTest < Minitest::Test - def make_iterator(initial_cursor:) - @times_called = 0 - - Seed::Internal::CursorItemIterator.new(initial_cursor:, cursor_field: :next_cursor, item_field: :cards) do |cursor| - @times_called += 1 - cursor ||= 0 - next_cursor = cursor + 10 - PageResponse.new( - cards: NUMBERS[cursor...next_cursor], - next_cursor: next_cursor < NUMBERS.length ? next_cursor : nil - ) - end - end - - def test_item_iterator_can_iterate_to_exhaustion - iterator = make_iterator(initial_cursor: 0) - - assert_equal NUMBERS, iterator.to_a - assert_equal 7, @times_called - - iterator = make_iterator(initial_cursor: 10) - - assert_equal (11..65).to_a, iterator.to_a - - iterator = make_iterator(initial_cursor: 5) - - assert_equal (6..65).to_a, iterator.to_a - end - - def test_item_iterator_can_work_without_an_initial_cursor - iterator = make_iterator(initial_cursor: nil) - - assert_equal NUMBERS, iterator.to_a - assert_equal 7, @times_called - end - - def test_items_iterator_iterates_lazily - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - assert_equal 1, iterator.first - assert_equal 1, @times_called - - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - assert_equal (1..15).to_a, iterator.first(15) - assert_equal 2, @times_called - - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - iterator.each do |card| - break if card >= 15 - end - - assert_equal 2, @times_called - end - - def test_items_iterator_implements_enumerable - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - doubled = iterator.map { |card| card * 2 } - - assert_equal 7, @times_called - assert_equal NUMBERS.length, doubled.length - end - - def test_items_iterator_can_be_advanced_manually - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - - items = [] - expected_times_called = 0 - while (item = iterator.next_element) - expected_times_called += 1 if (item % 10) == 1 - - assert_equal expected_times_called, @times_called - assert_equal item != NUMBERS.last, iterator.next?, "#{item} #{iterator}" - items.push(item) - end - - assert_equal 7, @times_called - assert_equal NUMBERS, items - end - - def test_pages_iterator - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal( - [ - (1..10).to_a, - (11..20).to_a, - (21..30).to_a, - (31..40).to_a, - (41..50).to_a, - (51..60).to_a, - (61..65).to_a - ], - iterator.to_a.map(&:cards) - ) - - iterator = make_iterator(initial_cursor: 10).pages - - assert_equal( - [ - (11..20).to_a, - (21..30).to_a, - (31..40).to_a, - (41..50).to_a, - (51..60).to_a, - (61..65).to_a - ], - iterator.to_a.map(&:cards) - ) - end - - def test_pages_iterator_can_work_without_an_initial_cursor - iterator = make_iterator(initial_cursor: nil).pages - - assert_equal 7, iterator.to_a.length - assert_equal 7, @times_called - end - - def test_pages_iterator_iterates_lazily - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - iterator.first - - assert_equal 1, @times_called - - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - assert_equal 2, iterator.first(2).length - assert_equal 2, @times_called - end - - def test_pages_iterator_knows_whether_another_page_is_upcoming - iterator = make_iterator(initial_cursor: 0).pages - - iterator.each_with_index do |_page, index| - assert_equal index + 1, @times_called - assert_equal index < 6, iterator.next? - end - end - - def test_pages_iterator_can_be_advanced_manually - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - - lengths = [] - expected_times_called = 0 - while (page = iterator.next_page) - expected_times_called += 1 - - assert_equal expected_times_called, @times_called - lengths.push(page.cards.length) - end - - assert_equal 7, @times_called - assert_equal [10, 10, 10, 10, 10, 10, 5], lengths - end - - def test_pages_iterator_implements_enumerable - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - lengths = iterator.map { |page| page.cards.length } - - assert_equal 7, @times_called - assert_equal [10, 10, 10, 10, 10, 10, 5], lengths - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb deleted file mode 100644 index 92576b820128..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/iterators/test_offset_item_iterator.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true - -require "minitest/autorun" -require "stringio" -require "json" -require "test_helper" - -OffsetPageResponse = Struct.new(:items, :has_next, keyword_init: true) -TestIteratorConfig = Struct.new( - :step, - :has_next_field, - :total_item_count, - :per_page, - :initial_page -) do - def first_item_returned - if step - (initial_page || 0) + 1 - else - (((initial_page || 1) - 1) * per_page) + 1 - end - end -end - -LAZY_TEST_ITERATOR_CONFIG = TestIteratorConfig.new(initial_page: 1, step: false, has_next_field: :has_next, total_item_count: 65, per_page: 10) -ALL_TEST_ITERATOR_CONFIGS = [true, false].map do |step| - [:has_next, nil].map do |has_next_field| - [0, 5, 10, 60, 63].map do |total_item_count| - [5, 10].map do |per_page| - initial_pages = [nil, 3, 100] - initial_pages << (step ? 0 : 1) - - initial_pages.map do |initial_page| - TestIteratorConfig.new( - step: step, - has_next_field: has_next_field, - total_item_count: total_item_count, - per_page: per_page, - initial_page: initial_page - ) - end - end - end - end -end.flatten - -class OffsetItemIteratorTest < Minitest::Test - def make_iterator(config) - @times_called = 0 - - items = (1..config.total_item_count).to_a - - Seed::Internal::OffsetItemIterator.new( - initial_page: config.initial_page, - item_field: :items, - has_next_field: config.has_next_field, - step: config.step - ) do |page| - @times_called += 1 - - slice_start = config.step ? page : (page - 1) * config.per_page - slice_end = slice_start + config.per_page - - output = { - items: items[slice_start...slice_end] - } - output[config.has_next_field] = slice_end < items.length if config.has_next_field - - OffsetPageResponse.new(**output) - end - end - - def test_item_iterator_can_iterate_to_exhaustion - ALL_TEST_ITERATOR_CONFIGS.each do |config| - iterator = make_iterator(config) - - assert_equal (config.first_item_returned..config.total_item_count).to_a, iterator.to_a - end - end - - def test_items_iterator_can_be_advanced_manually_and_has_accurate_has_next - ALL_TEST_ITERATOR_CONFIGS.each do |config| - iterator = make_iterator(config) - items = [] - - while (item = iterator.next_element) - assert_equal(item != config.total_item_count, iterator.next?, "#{item} #{iterator}") - items.push(item) - end - - assert_equal (config.first_item_returned..config.total_item_count).to_a, items - end - end - - def test_pages_iterator_can_be_advanced_manually_and_has_accurate_has_next - ALL_TEST_ITERATOR_CONFIGS.each do |config| - iterator = make_iterator(config).pages - pages = [] - - loop do - has_next_output = iterator.next? - page = iterator.next_page - - assert_equal(has_next_output, !page.nil?, "next? was inaccurate: #{config} #{iterator.inspect}") - break if page.nil? - - pages.push(page) - end - - assert_equal pages, make_iterator(config).pages.to_a - end - end - - def test_items_iterator_iterates_lazily - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) - - assert_equal 0, @times_called - assert_equal 1, iterator.first - assert_equal 1, @times_called - - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) - - assert_equal 0, @times_called - assert_equal (1..15).to_a, iterator.first(15) - assert_equal 2, @times_called - - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) - - assert_equal 0, @times_called - iterator.each do |card| - break if card >= 15 - end - - assert_equal 2, @times_called - end - - def test_pages_iterator_iterates_lazily - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages - - assert_equal 0, @times_called - iterator.first - - assert_equal 1, @times_called - - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages - - assert_equal 0, @times_called - assert_equal 3, iterator.first(3).length - assert_equal 3, @times_called - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb deleted file mode 100644 index e7e6571f03ee..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_array.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Array do - module TestArray - StringArray = Seed::Internal::Types::Array[String] - end - - describe "#initialize" do - it "sets the type" do - assert_equal String, TestArray::StringArray.type - end - end - - describe "#coerce" do - it "does not perform coercion if not an array" do - assert_equal 1, TestArray::StringArray.coerce(1) - end - - it "raises an error if not an array and strictness is on" do - assert_raises Seed::Internal::Errors::TypeError do - TestArray::StringArray.coerce(1, strict: true) - end - end - - it "coerces the elements" do - assert_equal %w[foobar 1 true], TestArray::StringArray.coerce(["foobar", 1, true]) - end - - it "raises an error if element of array is not coercable and strictness is on" do - assert_raises Seed::Internal::Errors::TypeError do - TestArray::StringArray.coerce([Object.new], strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb deleted file mode 100644 index cba18e48765b..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_boolean.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Boolean do - describe ".coerce" do - it "coerces true/false" do - assert Seed::Internal::Types::Boolean.coerce(true) - refute Seed::Internal::Types::Boolean.coerce(false) - end - - it "coerces an Integer" do - assert Seed::Internal::Types::Boolean.coerce(1) - refute Seed::Internal::Types::Boolean.coerce(0) - end - - it "coerces a String" do - assert Seed::Internal::Types::Boolean.coerce("1") - assert Seed::Internal::Types::Boolean.coerce("true") - refute Seed::Internal::Types::Boolean.coerce("0") - end - - it "passes through other values with strictness off" do - obj = Object.new - - assert_equal obj, Seed::Internal::Types::Boolean.coerce(obj) - end - - it "raises an error with other values with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - Seed::Internal::Types::Boolean.coerce(Object.new, strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb deleted file mode 100644 index e8d89bce467f..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_enum.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Enum do - module EnumTest - module ExampleEnum - extend Seed::Internal::Types::Enum - - FOO = :foo - BAR = :bar - - finalize! - end - end - - describe "#values" do - it "defines values" do - assert_equal %i[foo bar].sort, EnumTest::ExampleEnum.values.sort - end - end - - describe "#coerce" do - it "coerces an existing member" do - assert_equal :foo, EnumTest::ExampleEnum.coerce(:foo) - end - - it "coerces a string version of a member" do - assert_equal :foo, EnumTest::ExampleEnum.coerce("foo") - end - - it "returns the value if not a member with strictness off" do - assert_equal 1, EnumTest::ExampleEnum.coerce(1) - end - - it "raises an error if value is not a member with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - EnumTest::ExampleEnum.coerce(1, strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb deleted file mode 100644 index 6c5e58a6a946..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_hash.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Hash do - module TestHash - SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] - end - - describe ".[]" do - it "defines the key and value type" do - assert_equal Symbol, TestHash::SymbolStringHash.key_type - assert_equal String, TestHash::SymbolStringHash.value_type - end - end - - describe "#coerce" do - it "coerces the keys" do - assert_equal %i[foo bar], TestHash::SymbolStringHash.coerce({ "foo" => "1", :bar => "2" }).keys - end - - it "coerces the values" do - assert_equal %w[foo 1], TestHash::SymbolStringHash.coerce({ foo: :foo, bar: 1 }).values - end - - it "passes through other values with strictness off" do - obj = Object.new - - assert_equal obj, TestHash::SymbolStringHash.coerce(obj) - end - - it "raises an error with other values with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - TestHash::SymbolStringHash.coerce(Object.new, strict: true) - end - end - - it "raises an error with non-coercable key types with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - TestHash::SymbolStringHash.coerce({ Object.new => 1 }, strict: true) - end - end - - it "raises an error with non-coercable value types with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - TestHash::SymbolStringHash.coerce({ "foobar" => Object.new }, strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb deleted file mode 100644 index 3d87b9f5a8c7..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_model.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Model do - module StringInteger - extend Seed::Internal::Types::Union - - member String - member Integer - end - - class ExampleModel < Seed::Internal::Types::Model - field :name, String - field :rating, StringInteger, optional: true - field :year, Integer, optional: true, nullable: true, api_name: "yearOfRelease" - end - - class ExampleModelInheritance < ExampleModel - field :director, String - end - - class ExampleWithDefaults < ExampleModel - field :type, String, default: "example" - end - - class ExampleChild < Seed::Internal::Types::Model - field :value, String - end - - class ExampleParent < Seed::Internal::Types::Model - field :child, ExampleChild - end - - describe ".field" do - before do - @example = ExampleModel.new(name: "Inception", rating: 4) - end - - it "defines fields on model" do - assert_equal %i[name rating year], ExampleModel.fields.keys - end - - it "defines fields from parent models" do - assert_equal %i[name rating year director], ExampleModelInheritance.fields.keys - end - - it "sets the field's type" do - assert_equal String, ExampleModel.fields[:name].type - assert_equal StringInteger, ExampleModel.fields[:rating].type - end - - it "sets the `default` option" do - assert_equal "example", ExampleWithDefaults.fields[:type].default - end - - it "defines getters" do - assert_respond_to @example, :name - assert_respond_to @example, :rating - - assert_equal "Inception", @example.name - assert_equal 4, @example.rating - end - - it "defines setters" do - assert_respond_to @example, :name= - assert_respond_to @example, :rating= - - @example.name = "Inception 2" - @example.rating = 5 - - assert_equal "Inception 2", @example.name - assert_equal 5, @example.rating - end - end - - describe "#initialize" do - it "sets the data" do - example = ExampleModel.new(name: "Inception", rating: 4) - - assert_equal "Inception", example.name - assert_equal 4, example.rating - end - - it "allows extra fields to be set" do - example = ExampleModel.new(name: "Inception", rating: 4, director: "Christopher Nolan") - - assert_equal "Christopher Nolan", example.director - end - - it "sets the defaults where applicable" do - example_using_defaults = ExampleWithDefaults.new - - assert_equal "example", example_using_defaults.type - - example_without_defaults = ExampleWithDefaults.new(type: "not example") - - assert_equal "not example", example_without_defaults.type - end - - it "coerces child models" do - parent = ExampleParent.new(child: { value: "foobar" }) - - assert_kind_of ExampleChild, parent.child - end - - it "uses the api_name to pull the value" do - example = ExampleModel.new({ name: "Inception", yearOfRelease: 2014 }) - - assert_equal 2014, example.year - refute_respond_to example, :yearOfRelease - end - end - - describe "#inspect" do - class SensitiveModel < Seed::Internal::Types::Model - field :username, String - field :password, String - field :client_secret, String - field :access_token, String - field :api_key, String - end - - it "redacts sensitive fields" do - model = SensitiveModel.new( - username: "user123", - password: "secret123", - client_secret: "cs_abc", - access_token: "token_xyz", - api_key: "key_123" - ) - - inspect_output = model.inspect - - assert_includes inspect_output, "username=\"user123\"" - assert_includes inspect_output, "password=[REDACTED]" - assert_includes inspect_output, "client_secret=[REDACTED]" - assert_includes inspect_output, "access_token=[REDACTED]" - assert_includes inspect_output, "api_key=[REDACTED]" - refute_includes inspect_output, "secret123" - refute_includes inspect_output, "cs_abc" - refute_includes inspect_output, "token_xyz" - refute_includes inspect_output, "key_123" - end - - it "does not redact non-sensitive fields" do - example = ExampleModel.new(name: "Inception", rating: 4) - inspect_output = example.inspect - - assert_includes inspect_output, "name=\"Inception\"" - assert_includes inspect_output, "rating=4" - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb deleted file mode 100644 index e4e95c93139f..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_union.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Union do - class Rectangle < Seed::Internal::Types::Model - literal :type, "square" - - field :area, Float - end - - class Circle < Seed::Internal::Types::Model - literal :type, "circle" - - field :area, Float - end - - class Pineapple < Seed::Internal::Types::Model - literal :type, "pineapple" - - field :area, Float - end - - module Shape - extend Seed::Internal::Types::Union - - discriminant :type - - member -> { Rectangle }, key: "rect" - member -> { Circle }, key: "circle" - end - - module StringOrInteger - extend Seed::Internal::Types::Union - - member String - member Integer - end - - describe "#coerce" do - it "coerces hashes into member models with discriminated unions" do - circle = Shape.coerce({ type: "circle", area: 4.0 }) - - assert_instance_of Circle, circle - end - end - - describe "#type_member?" do - it "defines Model members" do - assert Shape.type_member?(Rectangle) - assert Shape.type_member?(Circle) - refute Shape.type_member?(Pineapple) - end - - it "defines other members" do - assert StringOrInteger.type_member?(String) - assert StringOrInteger.type_member?(Integer) - refute StringOrInteger.type_member?(Float) - refute StringOrInteger.type_member?(Pineapple) - end - end -end diff --git a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb b/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb deleted file mode 100644 index 29d14621a229..000000000000 --- a/seed/ruby-sdk-v2/allof-inline/test/unit/internal/types/test_utils.rb +++ /dev/null @@ -1,212 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Utils do - Utils = Seed::Internal::Types::Utils - - module TestUtils - class M < Seed::Internal::Types::Model - field :value, String - end - - class UnionMemberA < Seed::Internal::Types::Model - literal :type, "A" - field :only_on_a, String - end - - class UnionMemberB < Seed::Internal::Types::Model - literal :type, "B" - field :only_on_b, String - end - - module U - extend Seed::Internal::Types::Union - - discriminant :type - - member -> { UnionMemberA }, key: "A" - member -> { UnionMemberB }, key: "B" - end - - SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] - SymbolModelHash = -> { Seed::Internal::Types::Hash[Symbol, TestUtils::M] } - end - - describe ".coerce" do - describe "NilClass" do - it "always returns nil" do - assert_nil Utils.coerce(NilClass, "foobar") - assert_nil Utils.coerce(NilClass, 1) - assert_nil Utils.coerce(NilClass, Object.new) - end - end - - describe "String" do - it "coerces from String, Symbol, Numeric, or Boolean" do - assert_equal "foobar", Utils.coerce(String, "foobar") - assert_equal "foobar", Utils.coerce(String, :foobar) - assert_equal "1", Utils.coerce(String, 1) - assert_equal "1.0", Utils.coerce(String, 1.0) - assert_equal "true", Utils.coerce(String, true) - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(String, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(String, Object.new, strict: true) - end - end - end - - describe "Symbol" do - it "coerces from Symbol, String" do - assert_equal :foobar, Utils.coerce(Symbol, :foobar) - assert_equal :foobar, Utils.coerce(Symbol, "foobar") - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(Symbol, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(Symbol, Object.new, strict: true) - end - end - end - - describe "Integer" do - it "coerces from Numeric, String, Time" do - assert_equal 1, Utils.coerce(Integer, 1) - assert_equal 1, Utils.coerce(Integer, 1.0) - assert_equal 1, Utils.coerce(Integer, Complex.rect(1)) - assert_equal 1, Utils.coerce(Integer, Rational(1)) - assert_equal 1, Utils.coerce(Integer, "1") - assert_equal 1_713_916_800, Utils.coerce(Integer, Time.utc(2024, 4, 24)) - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(Integer, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(Integer, Object.new, strict: true) - end - end - end - - describe "Float" do - it "coerces from Numeric, Time" do - assert_in_delta(1.0, Utils.coerce(Float, 1.0)) - assert_in_delta(1.0, Utils.coerce(Float, 1)) - assert_in_delta(1.0, Utils.coerce(Float, Complex.rect(1))) - assert_in_delta(1.0, Utils.coerce(Float, Rational(1))) - assert_in_delta(1_713_916_800.0, Utils.coerce(Integer, Time.utc(2024, 4, 24))) - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(Float, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(Float, Object.new, strict: true) - end - end - end - - describe "Model" do - it "coerces a hash" do - result = Utils.coerce(TestUtils::M, { value: "foobar" }) - - assert_kind_of TestUtils::M, result - assert_equal "foobar", result.value - end - - it "coerces a hash when the target is a type function" do - result = Utils.coerce(-> { TestUtils::M }, { value: "foobar" }) - - assert_kind_of TestUtils::M, result - assert_equal "foobar", result.value - end - - it "will not coerce non-hashes" do - assert_equal "foobar", Utils.coerce(TestUtils::M, "foobar") - end - end - - describe "Enum" do - module ExampleEnum - extend Seed::Internal::Types::Enum - - FOO = :FOO - BAR = :BAR - - finalize! - end - - it "coerces into a Symbol version of the member value" do - assert_equal :FOO, Utils.coerce(ExampleEnum, "FOO") - end - - it "returns given value if not a member" do - assert_equal "NOPE", Utils.coerce(ExampleEnum, "NOPE") - end - end - - describe "Array" do - StringArray = Seed::Internal::Types::Array[String] - ModelArray = -> { Seed::Internal::Types::Array[TestUtils::M] } - UnionArray = -> { Seed::Internal::Types::Array[TestUtils::U] } - - it "coerces an array of literals" do - assert_equal %w[a b c], Utils.coerce(StringArray, %w[a b c]) - assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, [1, 2.0, true]) - assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, Set.new([1, 2.0, true])) - end - - it "coerces an array of Models" do - assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], - Utils.coerce(ModelArray, [{ value: "foobar" }, { value: "bizbaz" }]) - - assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], - Utils.coerce(ModelArray, [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")]) - end - - it "coerces an array of model unions" do - assert_equal [TestUtils::UnionMemberA.new(type: "A", only_on_a: "A"), TestUtils::UnionMemberB.new(type: "B", only_on_b: "B")], - Utils.coerce(UnionArray, [{ type: "A", only_on_a: "A" }, { type: "B", only_on_b: "B" }]) - end - - it "returns given value if not an array" do - assert_equal 1, Utils.coerce(StringArray, 1) - end - end - - describe "Hash" do - it "coerces the keys and values" do - ssh_res = Utils.coerce(TestUtils::SymbolStringHash, { "foo" => "bar", "biz" => "2" }) - - assert_equal "bar", ssh_res[:foo] - assert_equal "2", ssh_res[:biz] - - smh_res = Utils.coerce(TestUtils::SymbolModelHash, { "foo" => { "value" => "foo" } }) - - assert_equal TestUtils::M.new(value: "foo"), smh_res[:foo] - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/.fern/metadata.json b/seed/ruby-sdk-v2/allof/.fern/metadata.json deleted file mode 100644 index 4e868ad06d8a..000000000000 --- a/seed/ruby-sdk-v2/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-ruby-sdk-v2", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof/.github/workflows/ci.yml b/seed/ruby-sdk-v2/allof/.github/workflows/ci.yml deleted file mode 100644 index 72178ea4c8f1..000000000000 --- a/seed/ruby-sdk-v2/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: ci - -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.3" - - - name: Install dependencies - run: bundle install - - - name: Run Rubocop - run: bundle exec rubocop - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.3" - - - name: Install dependencies - run: bundle install - - - name: Run Tests - run: bundle exec rake test - - publish: - name: Publish to RubyGems.org - runs-on: ubuntu-latest - needs: [lint, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - - permissions: - id-token: write - contents: write - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "3.3" - - - name: Install dependencies - run: bundle install - - - name: Configure RubyGems credentials - uses: rubygems/configure-rubygems-credentials@v1.0.0 - - - name: Build gem - run: bundle exec rake build - - - name: Push gem to RubyGems - run: gem push pkg/*.gem diff --git a/seed/ruby-sdk-v2/allof/.gitignore b/seed/ruby-sdk-v2/allof/.gitignore deleted file mode 100644 index c111b331371a..000000000000 --- a/seed/ruby-sdk-v2/allof/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.gem diff --git a/seed/ruby-sdk-v2/allof/.rubocop.yml b/seed/ruby-sdk-v2/allof/.rubocop.yml deleted file mode 100644 index 75d8f836f2f0..000000000000 --- a/seed/ruby-sdk-v2/allof/.rubocop.yml +++ /dev/null @@ -1,69 +0,0 @@ -plugins: - - rubocop-minitest - -AllCops: - TargetRubyVersion: 3.3 - NewCops: enable - -Style/StringLiterals: - EnforcedStyle: double_quotes - -Style/StringLiteralsInInterpolation: - EnforcedStyle: double_quotes - -Style/AccessModifierDeclarations: - Enabled: false - -Lint/ConstantDefinitionInBlock: - Enabled: false - -Metrics/AbcSize: - Enabled: false - -Metrics/BlockLength: - Enabled: false - -Metrics/ClassLength: - Enabled: false - -Metrics/MethodLength: - Enabled: false - -Metrics/ParameterLists: - Enabled: false - -Metrics/PerceivedComplexity: - Enabled: false - -Metrics/CyclomaticComplexity: - Enabled: false - -Metrics/ModuleLength: - Enabled: false - -Layout/LineLength: - Enabled: false - -Naming/VariableNumber: - EnforcedStyle: normalcase - -Style/Documentation: - Enabled: false - -Style/Lambda: - EnforcedStyle: literal - -Minitest/MultipleAssertions: - Enabled: false - -Minitest/UselessAssertion: - Enabled: false - -# Dynamic snippets are code samples for documentation, not standalone Ruby files. -Style/FrozenStringLiteralComment: - Exclude: - - "dynamic-snippets/**/*" - -Layout/FirstHashElementIndentation: - Exclude: - - "dynamic-snippets/**/*" diff --git a/seed/ruby-sdk-v2/allof/Gemfile b/seed/ruby-sdk-v2/allof/Gemfile deleted file mode 100644 index 16877f89f300..000000000000 --- a/seed/ruby-sdk-v2/allof/Gemfile +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gemspec - -group :test, :development do - gem "minitest", "~> 5.16" - gem "minitest-rg" - gem "pry" - gem "rake", "~> 13.0" - gem "rubocop", "~> 1.21" - gem "rubocop-minitest" - gem "webmock" -end - -# Load custom Gemfile configuration if it exists -custom_gemfile = File.join(__dir__, "Gemfile.custom") -eval_gemfile(custom_gemfile) if File.exist?(custom_gemfile) diff --git a/seed/ruby-sdk-v2/allof/Gemfile.custom b/seed/ruby-sdk-v2/allof/Gemfile.custom deleted file mode 100644 index 11bdfaf13f2d..000000000000 --- a/seed/ruby-sdk-v2/allof/Gemfile.custom +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -# Custom Gemfile configuration file -# This file is automatically loaded by the main Gemfile. You can add custom gems, -# groups, or other Gemfile configurations here. If you do make changes to this file, -# you will need to add it to the .fernignore file to prevent your changes from being -# overwritten by the generator. - -# Example usage: -# group :test, :development do -# gem 'custom-gem', '~> 2.0' -# end - -# Add your custom gem dependencies here \ No newline at end of file diff --git a/seed/ruby-sdk-v2/allof/README.md b/seed/ruby-sdk-v2/allof/README.md deleted file mode 100644 index 8c9724fb7644..000000000000 --- a/seed/ruby-sdk-v2/allof/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# Seed Ruby Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRuby) - -The Seed Ruby library provides convenient access to the Seed APIs from Ruby. - -## Table of Contents - -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Advanced](#advanced) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Additional Headers](#additional-headers) - - [Additional Query Parameters](#additional-query-parameters) -- [Contributing](#contributing) - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```ruby -require "seed" - -client = Seed::Client.new - -client.create_rule( - name: "name", - execution_context: "prod" -) -``` - -## Environments - -This SDK allows you to configure different environments or custom URLs for API requests. You can either use the predefined environments or specify your own custom URL. -### Environments -```ruby -require "seed" - -seed = Seed::Client.new( - base_url: Seed::Environment::DEFAULT -) -``` - -### Custom URL -```ruby -require "seed" - -client = Seed::Client.new( - base_url: "https://example.com" -) -``` - -## Errors - -Failed API calls will raise errors that can be rescued from granularly. - -```ruby -require "seed" - -client = Seed::Client.new( - base_url: "https://example.com" -) - -begin - result = client.create_rule -rescue Seed::Errors::TimeoutError - puts "API didn't respond before our timeout elapsed" -rescue Seed::Errors::ServiceUnavailableError - puts "API returned status 503, is probably overloaded, try again later" -rescue Seed::Errors::ServerError - puts "API returned some other 5xx status, this is probably a bug" -rescue Seed::Errors::ResponseError => e - puts "API returned an unexpected status other than 5xx: #{e.code} #{e.message}" -rescue Seed::Errors::ApiError => e - puts "Some other error occurred when calling the API: #{e.message}" -end -``` - -## Advanced - -### Retries - -The SDK is instrumented with automatic retries. A request will be retried as long as the request is deemed -retryable and the number of retry attempts has not grown larger than the configured retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `max_retries` option to configure this behavior. - -```ruby -require "seed" - -client = Seed::Client.new( - base_url: "https://example.com", - max_retries: 3 # Configure max retries (default is 2) -) -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. Use the `timeout` option to configure this behavior. - -```ruby -require "seed" - -response = client.create_rule( - ..., - timeout: 30 # 30 second timeout -) -``` - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `additional_headers` request option. - -```ruby -require "seed" - -response = client.create_rule( - ..., - request_options: { - additional_headers: { - "X-Custom-Header" => "custom-value" - } - } -) -``` - -### Additional Query Parameters - -If you would like to send additional query parameters as part of the request, use the `additional_query_parameters` request option. - -```ruby -require "seed" - -response = client.create_rule( - ..., - request_options: { - additional_query_parameters: { - "custom_param" => "custom-value" - } - } -) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/ruby-sdk-v2/allof/Rakefile b/seed/ruby-sdk-v2/allof/Rakefile deleted file mode 100644 index 9bdd4a6ce80b..000000000000 --- a/seed/ruby-sdk-v2/allof/Rakefile +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require "bundler/gem_tasks" -require "minitest/test_task" - -Minitest::TestTask.create - -require "rubocop/rake_task" - -RuboCop::RakeTask.new - -task default: %i[test] - -task lint: %i[rubocop] - -# Run only the custom test file -Minitest::TestTask.create(:customtest) do |t| - t.libs << "test" - t.test_globs = ["test/custom.test.rb"] -end diff --git a/seed/ruby-sdk-v2/allof/custom.gemspec.rb b/seed/ruby-sdk-v2/allof/custom.gemspec.rb deleted file mode 100644 index 86d8efd3cd3c..000000000000 --- a/seed/ruby-sdk-v2/allof/custom.gemspec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -# Custom gemspec configuration file -# This file is automatically loaded by the main gemspec file. The 'spec' variable is available -# in this context from the main gemspec file. You can modify this file to add custom metadata, -# dependencies, or other gemspec configurations. If you do make changes to this file, you will -# need to add it to the .fernignore file to prevent your changes from being overwritten. - -def add_custom_gemspec_data(spec) - # Example custom configurations (uncomment and modify as needed) - - # spec.authors = ["Your name"] - # spec.email = ["your.email@example.com"] - # spec.homepage = "https://github.com/your-org/seed-ruby" - # spec.license = "Your license" -end diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb deleted file mode 100644 index f8b3f168d71b..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example0/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.search_rule_types diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb deleted file mode 100644 index 4b8e2cf5a383..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example1/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.search_rule_types(query: "query") diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb deleted file mode 100644 index 96d978fcde17..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example2/snippet.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.create_rule( - name: "name", - execution_context: "prod" -) diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb deleted file mode 100644 index 96d978fcde17..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example3/snippet.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.create_rule( - name: "name", - execution_context: "prod" -) diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb deleted file mode 100644 index 99237c7c6437..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example4/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.list_users diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb deleted file mode 100644 index 99237c7c6437..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example5/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.list_users diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb deleted file mode 100644 index 7769816ca1be..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example6/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_entity diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb deleted file mode 100644 index 7769816ca1be..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example7/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_entity diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb deleted file mode 100644 index f47ada1c5233..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example8/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_organization diff --git a/seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb b/seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb deleted file mode 100644 index f47ada1c5233..000000000000 --- a/seed/ruby-sdk-v2/allof/dynamic-snippets/example9/snippet.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "seed" - -client = Seed::Client.new(base_url: "https://api.fern.com") - -client.get_organization diff --git a/seed/ruby-sdk-v2/allof/lib/seed.rb b/seed/ruby-sdk-v2/allof/lib/seed.rb deleted file mode 100644 index 4a7e567f5fb2..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -require "json" -require "net/http" -require "securerandom" - -require_relative "seed/internal/json/serializable" -require_relative "seed/internal/types/type" -require_relative "seed/internal/types/utils" -require_relative "seed/internal/types/union" -require_relative "seed/internal/errors/constraint_error" -require_relative "seed/internal/errors/type_error" -require_relative "seed/internal/http/base_request" -require_relative "seed/internal/json/request" -require_relative "seed/internal/http/raw_client" -require_relative "seed/internal/multipart/multipart_encoder" -require_relative "seed/internal/multipart/multipart_form_data_part" -require_relative "seed/internal/multipart/multipart_form_data" -require_relative "seed/internal/multipart/multipart_request" -require_relative "seed/internal/types/model/field" -require_relative "seed/internal/types/model" -require_relative "seed/internal/types/array" -require_relative "seed/internal/types/boolean" -require_relative "seed/internal/types/enum" -require_relative "seed/internal/types/hash" -require_relative "seed/internal/types/unknown" -require_relative "seed/errors/api_error" -require_relative "seed/errors/response_error" -require_relative "seed/errors/client_error" -require_relative "seed/errors/redirect_error" -require_relative "seed/errors/server_error" -require_relative "seed/errors/timeout_error" -require_relative "seed/internal/iterators/item_iterator" -require_relative "seed/internal/iterators/cursor_item_iterator" -require_relative "seed/internal/iterators/offset_item_iterator" -require_relative "seed/internal/iterators/cursor_page_iterator" -require_relative "seed/internal/iterators/offset_page_iterator" -require_relative "seed/types/paging_cursors" -require_relative "seed/types/paginated_result" -require_relative "seed/types/rule_execution_context" -require_relative "seed/types/audit_info" -require_relative "seed/types/rule_type" -require_relative "seed/types/rule_type_search_response" -require_relative "seed/types/user" -require_relative "seed/types/user_search_response" -require_relative "seed/types/rule_response_status" -require_relative "seed/types/rule_response" -require_relative "seed/types/identifiable" -require_relative "seed/types/describable" -require_relative "seed/types/combined_entity_status" -require_relative "seed/types/combined_entity" -require_relative "seed/types/base_org_metadata" -require_relative "seed/types/base_org" -require_relative "seed/types/detailed_org_metadata" -require_relative "seed/types/detailed_org" -require_relative "seed/types/organization" -require_relative "seed/environment" diff --git a/seed/ruby-sdk-v2/allof/lib/seed/environment.rb b/seed/ruby-sdk-v2/allof/lib/seed/environment.rb deleted file mode 100644 index e994144e9573..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/environment.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module Seed - class Environment - DEFAULT = "https://api.example.com" - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb deleted file mode 100644 index b8ba53889b36..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/errors/api_error.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ApiError < StandardError - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb deleted file mode 100644 index c3c6033641e2..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/errors/client_error.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ClientError < ResponseError - end - - class UnauthorizedError < ClientError - end - - class ForbiddenError < ClientError - end - - class NotFoundError < ClientError - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb deleted file mode 100644 index f663c01e7615..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/errors/redirect_error.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class RedirectError < ResponseError - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb deleted file mode 100644 index beb4a1baf959..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/errors/response_error.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ResponseError < ApiError - attr_reader :code - - def initialize(msg, code:) - @code = code - super(msg) - end - - def inspect - "#<#{self.class.name} @code=#{code} @body=#{message}>" - end - - # Returns the most appropriate error class for the given code. - # - # @return [Class] - def self.subclass_for_code(code) - case code - when 300..399 - RedirectError - when 401 - UnauthorizedError - when 403 - ForbiddenError - when 404 - NotFoundError - when 400..499 - ClientError - when 503 - ServiceUnavailableError - when 500..599 - ServerError - else - ResponseError - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb deleted file mode 100644 index 1838027cdeab..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/errors/server_error.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class ServerError < ResponseError - end - - class ServiceUnavailableError < ApiError - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb deleted file mode 100644 index ec3a24bb7e96..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/errors/timeout_error.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Errors - class TimeoutError < ApiError - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb deleted file mode 100644 index e2f0bd66ac37..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/constraint_error.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Errors - class ConstraintError < StandardError - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb deleted file mode 100644 index 6aec80f59f05..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/errors/type_error.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Errors - class TypeError < StandardError - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb deleted file mode 100644 index d35df463e5b0..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/http/base_request.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Http - # @api private - class BaseRequest - attr_reader :base_url, :path, :method, :headers, :query, :request_options - - # @param base_url [String] The base URL for the request - # @param path [String] The path for the request - # @param method [String] The HTTP method for the request (:get, :post, etc.) - # @param headers [Hash] Additional headers for the request (optional) - # @param query [Hash] Query parameters for the request (optional) - # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] - def initialize(base_url:, path:, method:, headers: {}, query: {}, request_options: {}) - @base_url = base_url - @path = path - @method = method - @headers = headers - @query = query - @request_options = request_options - end - - # @return [Hash] The query parameters merged with additional query parameters from request options. - def encode_query - additional_query = @request_options&.dig(:additional_query_parameters) || @request_options&.dig("additional_query_parameters") || {} - @query.merge(additional_query) - end - - # Child classes should implement: - # - encode_headers: Returns the encoded HTTP request headers. - # - encode_body: Returns the encoded HTTP request body. - - private - - # Merges additional_headers from request_options into sdk_headers, filtering out - # any keys that collide with SDK-set or client-protected headers (case-insensitive). - # @param sdk_headers [Hash] Headers set by the SDK for this request type. - # @param protected_keys [Array] Additional header keys that must not be overridden. - # @return [Hash] The merged headers. - def merge_additional_headers(sdk_headers, protected_keys: []) - additional_headers = @request_options&.dig(:additional_headers) || @request_options&.dig("additional_headers") || {} - all_protected = (sdk_headers.keys + protected_keys).to_set { |k| k.to_s.downcase } - filtered = additional_headers.reject { |key, _| all_protected.include?(key.to_s.downcase) } - sdk_headers.merge(filtered) - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb deleted file mode 100644 index 482ab9517714..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/http/raw_client.rb +++ /dev/null @@ -1,214 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Http - # @api private - class RawClient - # Default HTTP status codes that trigger a retry - RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504, 521, 522, 524].freeze - # Initial delay between retries in seconds - INITIAL_RETRY_DELAY = 0.5 - # Maximum delay between retries in seconds - MAX_RETRY_DELAY = 60.0 - # Jitter factor for randomizing retry delays (20%) - JITTER_FACTOR = 0.2 - - # @return [String] The base URL for requests - attr_reader :base_url - - # @param base_url [String] The base url for the request. - # @param max_retries [Integer] The number of times to retry a failed request, defaults to 2. - # @param timeout [Float] The timeout for the request, defaults to 60.0 seconds. - # @param headers [Hash] The headers for the request. - def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) - @base_url = base_url - @max_retries = max_retries - @timeout = timeout - @default_headers = { - "X-Fern-Language": "Ruby", - "X-Fern-SDK-Name": "seed", - "X-Fern-SDK-Version": "0.0.1" - }.merge(headers) - end - - # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. - # @return [HTTP::Response] The HTTP response. - def send(request) - url = build_url(request) - attempt = 0 - response = nil - - loop do - http_request = build_http_request( - url:, - method: request.method, - headers: request.encode_headers(protected_keys: @default_headers.keys), - body: request.encode_body - ) - - conn = connect(url) - conn.open_timeout = @timeout - conn.read_timeout = @timeout - conn.write_timeout = @timeout - conn.continue_timeout = @timeout - - response = conn.request(http_request) - - break unless should_retry?(response, attempt) - - delay = retry_delay(response, attempt) - sleep(delay) - attempt += 1 - end - - response - end - - # Determines if a request should be retried based on the response status code. - # @param response [Net::HTTPResponse] The HTTP response. - # @param attempt [Integer] The current retry attempt (0-indexed). - # @return [Boolean] Whether the request should be retried. - def should_retry?(response, attempt) - return false if attempt >= @max_retries - - status = response.code.to_i - RETRYABLE_STATUSES.include?(status) - end - - # Calculates the delay before the next retry attempt using exponential backoff with jitter. - # Respects Retry-After header if present. - # @param response [Net::HTTPResponse] The HTTP response. - # @param attempt [Integer] The current retry attempt (0-indexed). - # @return [Float] The delay in seconds before the next retry. - def retry_delay(response, attempt) - # Check for Retry-After header (can be seconds or HTTP date) - retry_after = response["Retry-After"] - if retry_after - delay = parse_retry_after(retry_after) - return [delay, MAX_RETRY_DELAY].min if delay&.positive? - end - - # Exponential backoff with jitter: base_delay * 2^attempt - base_delay = INITIAL_RETRY_DELAY * (2**attempt) - add_jitter([base_delay, MAX_RETRY_DELAY].min) - end - - # Parses the Retry-After header value. - # @param value [String] The Retry-After header value (seconds or HTTP date). - # @return [Float, nil] The delay in seconds, or nil if parsing fails. - def parse_retry_after(value) - # Try parsing as integer (seconds) - seconds = Integer(value, exception: false) - return seconds.to_f if seconds - - # Try parsing as HTTP date - begin - retry_time = Time.httpdate(value) - delay = retry_time - Time.now - delay.positive? ? delay : nil - rescue ArgumentError - nil - end - end - - # Adds random jitter to a delay value. - # @param delay [Float] The base delay in seconds. - # @return [Float] The delay with jitter applied. - def add_jitter(delay) - jitter = delay * JITTER_FACTOR * (rand - 0.5) * 2 - [delay + jitter, 0].max - end - - LOCALHOST_HOSTS = %w[localhost 127.0.0.1 [::1]].freeze - - # @param request [Seed::Internal::Http::BaseRequest] The HTTP request. - # @return [URI::Generic] The URL. - def build_url(request) - encoded_query = request.encode_query - - # If the path is already an absolute URL, use it directly - if request.path.start_with?("http://", "https://") - url = request.path - url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? - parsed = URI.parse(url) - validate_https!(parsed) - return parsed - end - - path = request.path.start_with?("/") ? request.path[1..] : request.path - base = request.base_url || @base_url - url = "#{base.chomp("/")}/#{path}" - url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any? - parsed = URI.parse(url) - validate_https!(parsed) - parsed - end - - # Raises if the URL uses http:// for a non-localhost host, which would - # send authentication credentials in plaintext. - # @param url [URI::Generic] The parsed URL. - def validate_https!(url) - return if url.scheme != "http" - return if LOCALHOST_HOSTS.include?(url.host) - - raise ArgumentError, "Refusing to send request to non-HTTPS URL: #{url}. " \ - "HTTP is only allowed for localhost. Use HTTPS or pass a localhost URL." - end - - # @param url [URI::Generic] The url to the resource. - # @param method [String] The HTTP method to use. - # @param headers [Hash] The headers for the request. - # @param body [String, nil] The body for the request. - # @return [HTTP::Request] The HTTP request. - def build_http_request(url:, method:, headers: {}, body: nil) - request = Net::HTTPGenericRequest.new( - method, - !body.nil?, - method != "HEAD", - url - ) - - request_headers = @default_headers.merge(headers) - request_headers.each { |name, value| request[name] = value } - request.body = body if body - - request - end - - # @param query [Hash] The query for the request. - # @return [String, nil] The encoded query. - def encode_query(query) - query.to_h.empty? ? nil : URI.encode_www_form(query) - end - - # @param url [URI::Generic] The url to connect to. - # @return [Net::HTTP] The HTTP connection. - def connect(url) - is_https = (url.scheme == "https") - - port = if url.port - url.port - elsif is_https - Net::HTTP.https_default_port - else - Net::HTTP.http_default_port - end - - http = Net::HTTP.new(url.host, port) - http.use_ssl = is_https - http.verify_mode = OpenSSL::SSL::VERIFY_PEER if is_https - # NOTE: We handle retries at the application level with HTTP status code awareness, - # so we set max_retries to 0 to disable Net::HTTP's built-in network-level retries. - http.max_retries = 0 - http - end - - # @return [String] - def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)} @base_url=#{@base_url.inspect}>" - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb deleted file mode 100644 index ab627ffc7025..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_item_iterator.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class CursorItemIterator < ItemIterator - # Instantiates a CursorItemIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields individual items from it. - # - # @param initial_cursor [String] The initial cursor to use when iterating, if any. - # @param cursor_field [Symbol] The field in API responses to extract the next cursor from. - # @param item_field [Symbol] The field in API responses to extract the items to iterate over. - # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. - # @return [Seed::Internal::CursorItemIterator] - def initialize(initial_cursor:, cursor_field:, item_field:, &) - super() - @item_field = item_field - @page_iterator = CursorPageIterator.new(initial_cursor:, cursor_field:, &) - @page = nil - end - - # Returns the CursorPageIterator mediating access to the underlying API. - # - # @return [Seed::Internal::CursorPageIterator] - def pages - @page_iterator - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb deleted file mode 100644 index f479a749fef9..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/cursor_page_iterator.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class CursorPageIterator - include Enumerable - - # Instantiates a CursorPageIterator, an Enumerable class which wraps calls to a cursor-based paginated API and yields pages of items. - # - # @param initial_cursor [String] The initial cursor to use when iterating, if any. - # @param cursor_field [Symbol] The name of the field in API responses to extract the next cursor from. - # @param block [Proc] A block which is responsible for receiving a cursor to use and returning the given page from the API. - # @return [Seed::Internal::CursorPageIterator] - def initialize(initial_cursor:, cursor_field:, &block) - @need_initial_load = initial_cursor.nil? - @cursor = initial_cursor - @cursor_field = cursor_field - @get_next_page = block - end - - # Iterates over each page returned by the API. - # - # @param block [Proc] The block which each retrieved page is yielded to. - # @return [NilClass] - def each(&block) - while (page = next_page) - block.call(page) - end - end - - # Whether another page will be available from the API. - # - # @return [Boolean] - def next? - @need_initial_load || !@cursor.nil? - end - - # Retrieves the next page from the API. - # - # @return [Boolean] - def next_page - return if !@need_initial_load && @cursor.nil? - - @need_initial_load = false - fetched_page = @get_next_page.call(@cursor) - @cursor = fetched_page.send(@cursor_field) - fetched_page - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb deleted file mode 100644 index 1284fb0fd367..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/item_iterator.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class ItemIterator - include Enumerable - - # Iterates over each item returned by the API. - # - # @param block [Proc] The block which each retrieved item is yielded to. - # @return [NilClass] - def each(&block) - while (item = next_element) - block.call(item) - end - end - - # Whether another item will be available from the API. - # - # @return [Boolean] - def next? - load_next_page if @page.nil? - return false if @page.nil? - - return true if any_items_in_cached_page? - - load_next_page - any_items_in_cached_page? - end - - # Retrieves the next item from the API. - def next_element - item = next_item_from_cached_page - return item if item - - load_next_page - next_item_from_cached_page - end - - private - - def next_item_from_cached_page - return unless @page - - @page.send(@item_field).shift - end - - def any_items_in_cached_page? - return false unless @page - - !@page.send(@item_field).empty? - end - - def load_next_page - @page = @page_iterator.next_page - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb deleted file mode 100644 index f8840246686d..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_item_iterator.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class OffsetItemIterator < ItemIterator - # Instantiates an OffsetItemIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields the individual items from it. - # - # @param initial_page [Integer] The initial page or offset to start from when iterating. - # @param item_field [Symbol] The name of the field in API responses to extract the items to iterate over. - # @param has_next_field [Symbol] The name of the field in API responses containing a boolean of whether another page exists. - # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) - # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. - # - # @return [Seed::Internal::OffsetItemIterator] - def initialize(initial_page:, item_field:, has_next_field:, step:, &) - super() - @item_field = item_field - @page_iterator = OffsetPageIterator.new(initial_page:, item_field:, has_next_field:, step:, &) - @page = nil - end - - # Returns the OffsetPageIterator that is mediating access to the underlying API. - # - # @return [Seed::Internal::OffsetPageIterator] - def pages - @page_iterator - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb deleted file mode 100644 index 051b65c5774c..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/iterators/offset_page_iterator.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - class OffsetPageIterator - include Enumerable - - # Instantiates an OffsetPageIterator, an Enumerable class which wraps calls to an offset-based paginated API and yields pages of items from it. - # - # @param initial_page [Integer] The initial page to use when iterating, if any. - # @param item_field [Symbol] The field to pull the list of items to iterate over. - # @param has_next_field [Symbol] The field to pull the boolean of whether a next page exists from, if any. - # @param step [Boolean] If true, treats the page number as a true offset (i.e. increments the page number by the number of items returned from each call rather than just 1) - # @param block [Proc] A block which is responsible for receiving a page number to use and returning the given page from the API. - # @return [Seed::Internal::OffsetPageIterator] - def initialize(initial_page:, item_field:, has_next_field:, step:, &block) - @page_number = initial_page || (step ? 0 : 1) - @item_field = item_field - @has_next_field = has_next_field - @step = step - @get_next_page = block - - # A cache of whether the API has another page, if it gives us that information... - @next_page = nil - # ...or the actual next page, preloaded, if it doesn't. - @has_next_page = nil - end - - # Iterates over each page returned by the API. - # - # @param block [Proc] The block which each retrieved page is yielded to. - # @return [NilClass] - def each(&block) - while (page = next_page) - block.call(page) - end - end - - # Whether another page will be available from the API. - # - # @return [Boolean] - def next? - return @has_next_page unless @has_next_page.nil? - return true if @next_page - - fetched_page = @get_next_page.call(@page_number) - fetched_page_items = fetched_page&.send(@item_field) - if fetched_page_items.nil? || fetched_page_items.empty? - @has_next_page = false - else - @next_page = fetched_page - true - end - end - - # Returns the next page from the API. - def next_page - return nil if @page_number.nil? - - if @next_page - this_page = @next_page - @next_page = nil - else - this_page = @get_next_page.call(@page_number) - end - - @has_next_page = this_page&.send(@has_next_field) if @has_next_field - - items = this_page.send(@item_field) - if items.nil? || items.empty? - @page_number = nil - return nil - elsif @step - @page_number += items.length - else - @page_number += 1 - end - - this_page - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb deleted file mode 100644 index 667ceae8ac59..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/json/request.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module JSON - # @api private - class Request < Seed::Internal::Http::BaseRequest - attr_reader :body - - # @param base_url [String] The base URL for the request - # @param path [String] The path for the request - # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) - # @param headers [Hash] Additional headers for the request (optional) - # @param query [Hash] Query parameters for the request (optional) - # @param body [Object, nil] The JSON request body (optional) - # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] - def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) - super(base_url:, path:, method:, headers:, query:, request_options:) - - @body = body - end - - # @return [Hash] The encoded HTTP request headers. - # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) - # that must not be overridden by additional_headers from request_options. - def encode_headers(protected_keys: []) - sdk_headers = { - "Content-Type" => "application/json", - "Accept" => "application/json" - }.merge(@headers) - merge_additional_headers(sdk_headers, protected_keys:) - end - - # @return [String, nil] The encoded HTTP request body. - def encode_body - @body.nil? ? nil : ::JSON.generate(@body) - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb deleted file mode 100644 index f80a15fb962c..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/json/serializable.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module JSON - module Serializable - # Loads data from JSON into its deserialized form - # - # @param str [String] Raw JSON to load into an object - # @return [Object] - def load(str) - raise NotImplementedError - end - - # Dumps data from its deserialized form into JSON - # - # @param value [Object] The deserialized value - # @return [String] - def dump(value) - raise NotImplementedError - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb deleted file mode 100644 index 307ad7436a57..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_encoder.rb +++ /dev/null @@ -1,141 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Multipart - # Encodes parameters into a `multipart/form-data` payload as described by RFC - # 2388: - # - # https://tools.ietf.org/html/rfc2388 - # - # This is most useful for transferring file-like objects. - # - # Parameters should be added with `#encode`. When ready, use `#body` to get - # the encoded result and `#content_type` to get the value that should be - # placed in the `Content-Type` header of a subsequent request (which includes - # a boundary value). - # - # This abstraction is heavily inspired by Stripe's multipart/form-data implementation, - # which can be found here: - # - # https://github.com/stripe/stripe-ruby/blob/ca00b676f04ac421cf5cb5ff0325f243651677b6/lib/stripe/multipart_encoder.rb#L18 - # - # @api private - class Encoder - CONTENT_TYPE = "multipart/form-data" - CRLF = "\r\n" - - attr_reader :boundary, :body - - def initialize - # Chose the same number of random bytes that Go uses in its standard - # library implementation. Easily enough entropy to ensure that it won't - # be present in a file we're sending. - @boundary = SecureRandom.hex(30) - - @body = String.new - @closed = false - @first_field = true - end - - # Gets the content type string including the boundary. - # - # @return [String] The content type with boundary - def content_type - "#{CONTENT_TYPE}; boundary=#{@boundary}" - end - - # Encode the given FormData object into a multipart/form-data payload. - # - # @param form_data [FormData] The form data to encode - # @return [String] The encoded body. - def encode(form_data) - return "" if form_data.parts.empty? - - form_data.parts.each do |part| - write_part(part) - end - close - - @body - end - - # Writes a FormDataPart to the encoder. - # - # @param part [FormDataPart] The part to write - # @return [nil] - def write_part(part) - raise "Cannot write to closed encoder" if @closed - - write_field( - name: part.name, - data: part.contents, - filename: part.filename, - headers: part.headers - ) - - nil - end - - # Writes a field to the encoder. - # - # @param name [String] The field name - # @param data [String] The field data - # @param filename [String, nil] Optional filename - # @param headers [Hash, nil] Optional additional headers - # @return [nil] - def write_field(name:, data:, filename: nil, headers: nil) - raise "Cannot write to closed encoder" if @closed - - if @first_field - @first_field = false - else - @body << CRLF - end - - @body << "--#{@boundary}#{CRLF}" - @body << %(Content-Disposition: form-data; name="#{escape(name.to_s)}") - @body << %(; filename="#{escape(filename)}") if filename - @body << CRLF - - if headers - headers.each do |key, value| - @body << "#{key}: #{value}#{CRLF}" - end - elsif filename - # Default content type for files. - @body << "Content-Type: application/octet-stream#{CRLF}" - end - - @body << CRLF - @body << data.to_s - - nil - end - - # Finalizes the encoder by writing the final boundary. - # - # @return [nil] - def close - raise "Encoder already closed" if @closed - - @body << CRLF - @body << "--#{@boundary}--" - @closed = true - - nil - end - - private - - # Escapes quotes for use in header values and replaces line breaks with spaces. - # - # @param str [String] The string to escape - # @return [String] The escaped string - def escape(str) - str.to_s.gsub('"', "%22").tr("\n", " ").tr("\r", " ") - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb deleted file mode 100644 index 5be1bb25341f..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Multipart - # @api private - class FormData - # @return [Array] The parts in this multipart form data. - attr_reader :parts - - # @return [Encoder] The encoder for this multipart form data. - private attr_reader :encoder - - def initialize - @encoder = Encoder.new - @parts = [] - end - - # Adds a new part to the multipart form data. - # - # @param name [String] The name of the form field - # @param value [String, Integer, Float, Boolean, #read] The value of the field - # @param content_type [String, nil] Optional content type - # @return [self] Returns self for chaining - def add(name:, value:, content_type: nil) - headers = content_type ? { "Content-Type" => content_type } : nil - add_part(FormDataPart.new(name:, value:, headers:)) - end - - # Adds a file to the multipart form data. - # - # @param name [String] The name of the form field - # @param file [#read] The file or readable object - # @param filename [String, nil] Optional filename (defaults to basename of path for File objects) - # @param content_type [String, nil] Optional content type (e.g. "image/png") - # @return [self] Returns self for chaining - def add_file(name:, file:, filename: nil, content_type: nil) - headers = content_type ? { "Content-Type" => content_type } : nil - filename ||= filename_for(file) - add_part(FormDataPart.new(name:, value: file, filename:, headers:)) - end - - # Adds a pre-created part to the multipart form data. - # - # @param part [FormDataPart] The part to add - # @return [self] Returns self for chaining - def add_part(part) - @parts << part - self - end - - # Gets the content type string including the boundary. - # - # @return [String] The content type with boundary. - def content_type - @encoder.content_type - end - - # Encode the multipart form data into a multipart/form-data payload. - # - # @return [String] The encoded body. - def encode - @encoder.encode(self) - end - - private - - def filename_for(file) - if file.is_a?(::File) || file.respond_to?(:path) - ::File.basename(file.path) - elsif file.respond_to?(:name) - file.name - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb deleted file mode 100644 index de45416ee087..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_form_data_part.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require "securerandom" - -module Seed - module Internal - module Multipart - # @api private - class FormDataPart - attr_reader :name, :contents, :filename, :headers - - # @param name [String] The name of the form field - # @param value [String, Integer, Float, Boolean, File, #read] The value of the field - # @param filename [String, nil] Optional filename for file uploads - # @param headers [Hash, nil] Optional additional headers - def initialize(name:, value:, filename: nil, headers: nil) - @name = name - @contents = convert_to_content(value) - @filename = filename - @headers = headers - end - - # Converts the part to a hash suitable for serialization. - # - # @return [Hash] A hash representation of the part - def to_hash - result = { - name: @name, - contents: @contents - } - result[:filename] = @filename if @filename - result[:headers] = @headers if @headers - result - end - - private - - # Converts various types of values to a content representation - # @param value [String, Integer, Float, Boolean, #read] The value to convert - # @return [String] The string representation of the value - def convert_to_content(value) - if value.respond_to?(:read) - value.read - else - value.to_s - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb deleted file mode 100644 index 9fa80cee01ab..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/multipart/multipart_request.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Multipart - # @api private - class Request < Seed::Internal::Http::BaseRequest - attr_reader :body - - # @param base_url [String] The base URL for the request - # @param path [String] The path for the request - # @param method [Symbol] The HTTP method for the request (:get, :post, etc.) - # @param headers [Hash] Additional headers for the request (optional) - # @param query [Hash] Query parameters for the request (optional) - # @param body [MultipartFormData, nil] The multipart form data for the request (optional) - # @param request_options [Seed::RequestOptions, Hash{Symbol=>Object}, nil] - def initialize(base_url:, path:, method:, headers: {}, query: {}, body: nil, request_options: {}) - super(base_url:, path:, method:, headers:, query:, request_options:) - - @body = body - end - - # @return [Hash] The encoded HTTP request headers. - # @param protected_keys [Array] Header keys set by the SDK client (e.g. auth, metadata) - # that must not be overridden by additional_headers from request_options. - def encode_headers(protected_keys: []) - sdk_headers = { - "Content-Type" => @body.content_type - }.merge(@headers) - merge_additional_headers(sdk_headers, protected_keys:) - end - - # @return [String, nil] The encoded HTTP request body. - def encode_body - @body&.encode - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb deleted file mode 100644 index f3c7c1bd9549..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/array.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # An array of a specific type - class Array - include Seed::Internal::Types::Type - - attr_reader :type - - class << self - # Instantiates a new `Array` of a given type - # - # @param type [Object] The member type of this array - # - # @return [Seed::Internal::Types::Array] - def [](type) - new(type) - end - end - - # @api private - def initialize(type) - @type = type - end - - # Coerces a value into this array - # - # @param value [Object] - # @option strict [Boolean] - # @return [::Array] - def coerce(value, strict: strict?) - unless value.is_a?(::Array) - raise Errors::TypeError, "cannot coerce `#{value.class}` to Array<#{type}>" if strict - - return value - end - - value.map do |element| - Utils.coerce(type, element, strict: strict) - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb deleted file mode 100644 index d4e3277e566f..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/boolean.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - module Boolean - extend Seed::Internal::Types::Union - - member TrueClass - member FalseClass - - # Overrides the base coercion method for enums to allow integer and string values to become booleans - # - # @param value [Object] - # @option strict [Boolean] - # @return [Object] - def self.coerce(value, strict: strict?) - case value - when TrueClass, FalseClass - return value - when Integer - return value == 1 - when String - return %w[1 true].include?(value) - end - - raise Errors::TypeError, "cannot coerce `#{value.class}` to Boolean" if strict - - value - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb deleted file mode 100644 index 72e45e4c1f27..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/enum.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # Module for defining enums - module Enum - include Type - - # @api private - # - # @return [Array] - def values - @values ||= constants.map { |c| const_get(c) } - end - - # @api private - def finalize! - values - end - - # @api private - def strict? - @strict ||= false - end - - # @api private - def strict! - @strict = true - end - - def coerce(value, strict: strict?) - coerced_value = Utils.coerce(Symbol, value) - - return coerced_value if values.include?(coerced_value) - - raise Errors::TypeError, "`#{value}` not in enum #{self}" if strict - - value - end - - # Parse JSON string and coerce to the enum value - # - # @param str [String] JSON string to parse - # @return [String] The enum value - def load(str) - coerce(::JSON.parse(str)) - end - - def inspect - "#{name}[#{values.join(", ")}]" - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb deleted file mode 100644 index d8bffa63ac11..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/hash.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - class Hash - include Type - - attr_reader :key_type, :value_type - - class << self - def [](key_type, value_type) - new(key_type, value_type) - end - end - - def initialize(key_type, value_type) - @key_type = key_type - @value_type = value_type - end - - def coerce(value, strict: strict?) - unless value.is_a?(::Hash) - raise Errors::TypeError, "not hash" if strict - - return value - end - - value.to_h do |k, v| - [Utils.coerce(key_type, k, strict: strict), Utils.coerce(value_type, v, strict: strict)] - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb deleted file mode 100644 index 8caca14ff7ea..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model.rb +++ /dev/null @@ -1,208 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # @abstract - # - # An abstract model that all data objects will inherit from - class Model - include Type - - class << self - # The defined fields for this model - # - # @api private - # - # @return [Hash] - def fields - @fields ||= if self < Seed::Internal::Types::Model - superclass.fields.dup - else - {} - end - end - - # Any extra fields that have been created from instantiation - # - # @api private - # - # @return [Hash] - def extra_fields - @extra_fields ||= {} - end - - # Define a new field on this model - # - # @param name [Symbol] The name of the field - # @param type [Class] Type of the field - # @option optional [Boolean] If it is an optional field - # @option nullable [Boolean] If it is a nullable field - # @option api_name [Symbol, String] Name in the API of this field. When serializing/deserializing, will use - # this field name - # @return [void] - def field(name, type, optional: false, nullable: false, api_name: nil, default: nil) - add_field_definition(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, - default: default) - - define_accessor(name) - define_setter(name) - end - - # Define a new literal for this model - # - # @param name [Symbol] - # @param value [Object] - # @option api_name [Symbol, String] - # @return [void] - def literal(name, value, api_name: nil) - add_field_definition(name: name, type: value.class, optional: false, nullable: false, api_name: api_name, - value: value) - - define_accessor(name) - end - - # Adds a new field definition into the class's fields registry - # - # @api private - # - # @param name [Symbol] - # @param type [Class] - # @option optional [Boolean] - # @return [void] - private def add_field_definition(name:, type:, optional:, nullable:, api_name:, default: nil, value: nil) - fields[name.to_sym] = - Field.new(name: name, type: type, optional: optional, nullable: nullable, api_name: api_name, - value: value, default: default) - end - - # Adds a new field definition into the class's extra fields registry - # - # @api private - # - # @param name [Symbol] - # @param type [Class] - # @option required [Boolean] - # @option optional [Boolean] - # @return [void] - def add_extra_field_definition(name:, type:) - return if extra_fields.key?(name.to_sym) - - extra_fields[name.to_sym] = Field.new(name: name, type: type, optional: true, nullable: false) - - define_accessor(name) - define_setter(name) - end - - # @api private - private def define_accessor(name) - method_name = name.to_sym - - define_method(method_name) do - @data[name] - end - end - - # @api private - private def define_setter(name) - method_name = :"#{name}=" - - define_method(method_name) do |val| - @data[name] = val - end - end - - def coerce(value, strict: (respond_to?(:strict?) ? strict? : false)) # rubocop:disable Lint/UnusedMethodArgument - return value if value.is_a?(self) - - return value unless value.is_a?(::Hash) - - new(value) - end - - def load(str) - coerce(::JSON.parse(str, symbolize_names: true)) - end - - def ===(instance) - instance.class.ancestors.include?(self) - end - end - - # Creates a new instance of this model - # TODO: Should all this logic be in `#coerce` instead? - # - # @param values [Hash] - # @option strict [Boolean] - # @return [self] - def initialize(values = {}) - @data = {} - - values = Utils.symbolize_keys(values.dup) - - self.class.fields.each do |field_name, field| - value = values.delete(field.api_name.to_sym) || values.delete(field.api_name) || values.delete(field_name) - - field_value = value || (if field.literal? - field.value - elsif field.default - field.default - end) - - @data[field_name] = Utils.coerce(field.type, field_value) - end - - # Any remaining values in the input become extra fields - values.each do |name, value| - self.class.add_extra_field_definition(name: name, type: value.class) - - @data[name.to_sym] = value - end - end - - def to_h - result = self.class.fields.merge(self.class.extra_fields).each_with_object({}) do |(name, field), acc| - # If there is a value present in the data, use that value - # If there is a `nil` value present in the data, and it is optional but NOT nullable, exclude key altogether - # If there is a `nil` value present in the data, and it is optional and nullable, use the nil value - - value = @data[name] - - next if value.nil? && field.optional && !field.nullable - - if value.is_a?(::Array) - value = value.map { |item| item.respond_to?(:to_h) ? item.to_h : item } - elsif value.respond_to?(:to_h) - value = value.to_h - end - - acc[field.api_name] = value - end - - # Inject union discriminant if this instance was coerced from a discriminated union - # and the discriminant key is not already present in the result - discriminant_key = instance_variable_get(:@_fern_union_discriminant_key) - discriminant_value = instance_variable_get(:@_fern_union_discriminant_value) - result[discriminant_key] = discriminant_value if discriminant_key && discriminant_value && !result.key?(discriminant_key) - - result - end - - def ==(other) - self.class == other.class && to_h == other.to_h - end - - # @return [String] - def inspect - attrs = @data.map do |name, value| - field = self.class.fields[name] || self.class.extra_fields[name] - display_value = field&.sensitive? ? "[REDACTED]" : value.inspect - "#{name}=#{display_value}" - end - - "#<#{self.class.name}:0x#{object_id&.to_s(16)} #{attrs.join(" ")}>" - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb deleted file mode 100644 index 6ce0186f6a5d..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/model/field.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - class Model - # Definition of a field on a model - class Field - SENSITIVE_FIELD_NAMES = %i[ - password secret token api_key apikey access_token refresh_token - client_secret client_id credential bearer authorization - ].freeze - - attr_reader :name, :type, :optional, :nullable, :api_name, :value, :default - - def initialize(name:, type:, optional: false, nullable: false, api_name: nil, value: nil, default: nil) - @name = name.to_sym - @type = type - @optional = optional - @nullable = nullable - @api_name = api_name || name.to_s - @value = value - @default = default - end - - def literal? - !value.nil? - end - - def sensitive? - SENSITIVE_FIELD_NAMES.include?(@name) || - SENSITIVE_FIELD_NAMES.any? { |sensitive| @name.to_s.include?(sensitive.to_s) } - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb deleted file mode 100644 index 5866caf1dbda..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/type.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # @abstract - module Type - include Seed::Internal::JSON::Serializable - - # Coerces a value to this type - # - # @param value [unknown] - # @option strict [Boolean] If we should strictly coerce this value - def coerce(value, strict: strict?) - raise NotImplementedError - end - - # Returns if strictness is on for this type, defaults to `false` - # - # @return [Boolean] - def strict? - @strict ||= false - end - - # Enable strictness by default for this type - # - # @return [void] - def strict! - @strict = true - self - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb deleted file mode 100644 index f3e118a2fa78..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/union.rb +++ /dev/null @@ -1,161 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # Define a union between two types - module Union - include Seed::Internal::Types::Type - - def members - @members ||= [] - end - - # Add a member to this union - # - # @param type [Object] - # @option key [Symbol, String] - # @return [void] - def member(type, key: nil) - members.push([key, Utils.wrap_type(type)]) - self - end - - def type_member?(type) - members.any? { |_key, type_fn| type == type_fn.call } - end - - # Set the discriminant for this union - # - # @param key [Symbol, String] - # @return [void] - def discriminant(key) - @discriminant = key - end - - # @api private - private def discriminated? - !@discriminant.nil? - end - - # Check if value matches a type, handling type wrapper instances - # (Internal::Types::Hash and Internal::Types::Array instances) - # - # @param value [Object] - # @param member_type [Object] - # @return [Boolean] - private def type_matches?(value, member_type) - case member_type - when Seed::Internal::Types::Hash - value.is_a?(::Hash) - when Seed::Internal::Types::Array - value.is_a?(::Array) - when Class, Module - value.is_a?(member_type) - else - false - end - end - - # Resolves the type of a value to be one of the members - # - # @param value [Object] - # @return [Class] - private def resolve_member(value) - if discriminated? && value.is_a?(::Hash) - # Try both symbol and string keys for the discriminant - discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) - - return if discriminant_value.nil? - - # Convert to string for consistent comparison - discriminant_str = discriminant_value.to_s - - # First try exact match - members_hash = members.to_h - result = members_hash[discriminant_str]&.call - return result if result - - # Try case-insensitive match as fallback - discriminant_lower = discriminant_str.downcase - matching_keys = members_hash.keys.select { |k| k.to_s.downcase == discriminant_lower } - - # Only use case-insensitive match if exactly one key matches (avoid ambiguity) - return members_hash[matching_keys.first]&.call if matching_keys.length == 1 - - nil - else - # First try exact type matching - result = members.find do |_key, mem| - member_type = Utils.unwrap_type(mem) - type_matches?(value, member_type) - end&.last&.call - - return result if result - - # For Hash values, try to coerce into Model member types - if value.is_a?(::Hash) - members.find do |_key, mem| - member_type = Utils.unwrap_type(mem) - # Check if member_type is a Model class - next unless member_type.is_a?(Class) && member_type <= Model - - # Try to coerce the hash into this model type with strict mode - begin - candidate = Utils.coerce(member_type, value, strict: true) - - # Validate that all required (non-optional) fields are present - # This ensures undiscriminated unions properly distinguish between member types - member_type.fields.each do |field_name, field| - raise Errors::TypeError, "Required field `#{field_name}` missing for union member #{member_type.name}" if candidate.instance_variable_get(:@data)[field_name].nil? && !field.optional - end - - true - rescue Errors::TypeError - false - end - end&.last&.call - end - end - end - - def coerce(value, strict: strict?) - type = resolve_member(value) - - unless type - return value unless strict - - if discriminated? - raise Errors::TypeError, - "value of type `#{value.class}` not member of union #{self}" - end - - raise Errors::TypeError, "could not resolve to member of union #{self}" - end - - coerced = Utils.coerce(type, value, strict: strict) - - # For discriminated unions, store the discriminant info on the coerced instance - # so it can be injected back during serialization (to_h) - if discriminated? && value.is_a?(::Hash) && coerced.is_a?(Model) - discriminant_value = value.fetch(@discriminant, nil) || value.fetch(@discriminant.to_s, nil) - if discriminant_value - coerced.instance_variable_set(:@_fern_union_discriminant_key, @discriminant.to_s) - coerced.instance_variable_set(:@_fern_union_discriminant_value, discriminant_value) - end - end - - coerced - end - - # Parse JSON string and coerce to the correct union member type - # - # @param str [String] JSON string to parse - # @return [Object] Coerced value matching a union member - def load(str) - coerce(::JSON.parse(str, symbolize_names: true)) - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb deleted file mode 100644 index 7b58de956da9..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/unknown.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - module Unknown - include Seed::Internal::Types::Type - - def coerce(value) - value - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb b/seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb deleted file mode 100644 index 0ac6179e855f..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/internal/types/utils.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Internal - module Types - # Utilities for dealing with and checking types - module Utils - # Wraps a type into a type function - # - # @param type [Proc, Object] - # @return [Proc] - def self.wrap_type(type) - case type - when Proc - type - else - -> { type } - end - end - - # Resolves a type or type function into a type - # - # @param type [Proc, Object] - # @return [Object] - def self.unwrap_type(type) - type.is_a?(Proc) ? type.call : type - end - - def self.coerce(target, value, strict: false) - type = unwrap_type(target) - - case type - in Array - case value - when ::Array - return type.coerce(value, strict: strict) - when Set, ::Hash - return coerce(type, value.to_a) - end - in Hash - case value - when ::Hash - return type.coerce(value, strict: strict) - when ::Array - return coerce(type, value.to_h) - end - in ->(t) { t <= NilClass } - return nil - in ->(t) { t <= String } - case value - when String, Symbol, Numeric, TrueClass, FalseClass - return value.to_s - end - in ->(t) { t <= Symbol } - case value - when Symbol, String - return value.to_sym - end - in ->(t) { t <= Integer } - case value - when Numeric, String, Time - return value.to_i - end - in ->(t) { t <= Float } - case value - when Numeric, Time, String - return value.to_f - end - in ->(t) { t <= Model } - case value - when type - return value - when ::Hash - return type.coerce(value, strict: strict) - end - in Module - case type - in ->(t) { - t.singleton_class.included_modules.include?(Enum) || - t.singleton_class.included_modules.include?(Union) - } - return type.coerce(value, strict: strict) - else - value # rubocop:disable Lint/Void - end - else - value # rubocop:disable Lint/Void - end - - raise Errors::TypeError, "cannot coerce value of type `#{value.class}` to `#{target}`" if strict - - value - end - - def self.symbolize_keys(hash) - hash.transform_keys(&:to_sym) - end - - # Converts camelCase keys to snake_case symbols - # This allows SDK methods to accept both snake_case and camelCase keys - # e.g., { refundMethod: ... } becomes { refund_method: ... } - # - # @param hash [Hash] - # @return [Hash] - def self.normalize_keys(hash) - hash.transform_keys do |key| - key_str = key.to_s - # Convert camelCase to snake_case - snake_case = key_str.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase - snake_case.to_sym - end - end - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb deleted file mode 100644 index 2df8e8d92220..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/audit_info.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - # Common audit metadata. - class AuditInfo < Internal::Types::Model - field :created_by, -> { String }, optional: true, nullable: false, api_name: "createdBy" - field :created_date_time, -> { String }, optional: true, nullable: false, api_name: "createdDateTime" - field :modified_by, -> { String }, optional: true, nullable: false, api_name: "modifiedBy" - field :modified_date_time, -> { String }, optional: true, nullable: false, api_name: "modifiedDateTime" - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb deleted file mode 100644 index 8e6b5538aad2..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/base_org.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class BaseOrg < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :metadata, -> { Seed::Types::BaseOrgMetadata }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb deleted file mode 100644 index 4167d5d9464f..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/base_org_metadata.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class BaseOrgMetadata < Internal::Types::Model - field :region, -> { String }, optional: false, nullable: false - field :tier, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb deleted file mode 100644 index 3603bc98cbc5..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class CombinedEntity < Internal::Types::Model - field :status, -> { Seed::Types::CombinedEntityStatus }, optional: false, nullable: false - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: true, nullable: false - field :summary, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb deleted file mode 100644 index ce445c2e0f19..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/combined_entity_status.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - module CombinedEntityStatus - extend Seed::Internal::Types::Enum - - ACTIVE = "active" - ARCHIVED = "archived" - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb deleted file mode 100644 index bf18865e61b5..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/describable.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class Describable < Internal::Types::Model - field :name, -> { String }, optional: true, nullable: false - field :summary, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb deleted file mode 100644 index 5839792d7c2c..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class DetailedOrg < Internal::Types::Model - field :metadata, -> { Seed::Types::DetailedOrgMetadata }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb deleted file mode 100644 index 5c5a311e208c..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/detailed_org_metadata.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class DetailedOrgMetadata < Internal::Types::Model - field :region, -> { String }, optional: false, nullable: false - field :domain, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb deleted file mode 100644 index 420ec1d027dc..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/identifiable.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class Identifiable < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb deleted file mode 100644 index 22f80e523436..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/organization.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class Organization < Internal::Types::Model - field :name, -> { String }, optional: false, nullable: false - field :id, -> { String }, optional: false, nullable: false - field :metadata, -> { Seed::Types::BaseOrgMetadata }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb deleted file mode 100644 index 10755d50c291..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/paginated_result.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class PaginatedResult < Internal::Types::Model - field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false - field :results, -> { Internal::Types::Array[Object] }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb deleted file mode 100644 index 925959b21486..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/paging_cursors.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class PagingCursors < Internal::Types::Model - field :next_, -> { String }, optional: false, nullable: false, api_name: "next" - field :previous, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb deleted file mode 100644 index be802bd7fd55..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_execution_context.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - module RuleExecutionContext - extend Seed::Internal::Types::Enum - - PROD = "prod" - STAGING = "staging" - DEV = "dev" - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb deleted file mode 100644 index 7f12d62b7420..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class RuleResponse < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: false, nullable: false - field :status, -> { Seed::Types::RuleResponseStatus }, optional: false, nullable: false - field :execution_context, -> { Seed::Types::RuleExecutionContext }, optional: true, nullable: false, api_name: "executionContext" - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb deleted file mode 100644 index 609f51bfabb4..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_response_status.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - module RuleResponseStatus - extend Seed::Internal::Types::Enum - - ACTIVE = "active" - INACTIVE = "inactive" - DRAFT = "draft" - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb deleted file mode 100644 index 798fc21f7888..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class RuleType < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :name, -> { String }, optional: false, nullable: false - field :description, -> { String }, optional: true, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb deleted file mode 100644 index 7195d2c81c41..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/rule_type_search_response.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class RuleTypeSearchResponse < Internal::Types::Model - field :results, -> { Internal::Types::Array[Seed::Types::RuleType] }, optional: true, nullable: false - field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/user.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/user.rb deleted file mode 100644 index 9d5a150b6803..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/user.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class User < Internal::Types::Model - field :id, -> { String }, optional: false, nullable: false - field :email, -> { String }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb b/seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb deleted file mode 100644 index b1c494f6bb39..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/types/user_search_response.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Seed - module Types - class UserSearchResponse < Internal::Types::Model - field :results, -> { Internal::Types::Array[Seed::Types::User] }, optional: true, nullable: false - field :paging, -> { Seed::Types::PagingCursors }, optional: false, nullable: false - end - end -end diff --git a/seed/ruby-sdk-v2/allof/lib/seed/version.rb b/seed/ruby-sdk-v2/allof/lib/seed/version.rb deleted file mode 100644 index 00dd45cdd958..000000000000 --- a/seed/ruby-sdk-v2/allof/lib/seed/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module Seed - VERSION = "0.0.1" -end diff --git a/seed/ruby-sdk-v2/allof/reference.md b/seed/ruby-sdk-v2/allof/reference.md deleted file mode 100644 index 2a0eb7a77a05..000000000000 --- a/seed/ruby-sdk-v2/allof/reference.md +++ /dev/null @@ -1,228 +0,0 @@ -# Reference -
client.search_rule_types() -> Seed::Types::RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.search_rule_types -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `String` - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.create_rule(request) -> Seed::Types::RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.create_rule( - name: "name", - execution_context: "prod" -) -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `String` - -
-
- -
-
- -**execution_context:** `Seed::Types::RuleExecutionContext` - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.list_users() -> Seed::Types::UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.list_users -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.get_entity() -> Seed::Types::CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.get_entity -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- -
client.get_organization() -> Seed::Types::Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```ruby -client.get_organization -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `Seed::RequestOptions` - -
-
-
-
- - -
-
-
- diff --git a/seed/ruby-sdk-v2/allof/seed.gemspec b/seed/ruby-sdk-v2/allof/seed.gemspec deleted file mode 100644 index cffe9f840a9b..000000000000 --- a/seed/ruby-sdk-v2/allof/seed.gemspec +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative "lib/seed/version" -require_relative "custom.gemspec" - -# NOTE: A handful of these fields are required as part of the Ruby specification. -# You can change them here or overwrite them in the custom gemspec file. -Gem::Specification.new do |spec| - spec.name = "fern_allof" - spec.authors = ["Seed"] - spec.version = Seed::VERSION - spec.summary = "Ruby client library for the Seed API" - spec.description = "The Seed Ruby library provides convenient access to the Seed API from Ruby." - spec.required_ruby_version = ">= 3.3.0" - spec.metadata["rubygems_mfa_required"] = "true" - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - gemspec = File.basename(__FILE__) - spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| - ls.readlines("\x0", chomp: true).reject do |f| - (f == gemspec) || - f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile]) - end - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] - - # For more information and examples about making a new gem, check out our - # guide at: https://bundler.io/guides/creating_gem.html - - # Load custom gemspec configuration if it exists - custom_gemspec_file = File.join(__dir__, "custom.gemspec.rb") - add_custom_gemspec_data(spec) if File.exist?(custom_gemspec_file) -end diff --git a/seed/ruby-sdk-v2/allof/snippet.json b/seed/ruby-sdk-v2/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/ruby-sdk-v2/allof/test/custom.test.rb b/seed/ruby-sdk-v2/allof/test/custom.test.rb deleted file mode 100644 index 4bd57989d43d..000000000000 --- a/seed/ruby-sdk-v2/allof/test/custom.test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# This is a custom test file, if you wish to add more tests -# to your SDK. -# Be sure to mark this file in `.fernignore`. -# -# If you include example requests/responses in your fern definition, -# you will have tests automatically generated for you. - -# This test is run via command line: rake customtest -describe "Custom Test" do - it "Default" do - refute false - end -end diff --git a/seed/ruby-sdk-v2/allof/test/test_helper.rb b/seed/ruby-sdk-v2/allof/test/test_helper.rb deleted file mode 100644 index b086fe6d76ec..000000000000 --- a/seed/ruby-sdk-v2/allof/test/test_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require_relative "../lib/seed" diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb deleted file mode 100644 index 5008f6abf69f..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_cursor_item_iterator.rb +++ /dev/null @@ -1,189 +0,0 @@ -# frozen_string_literal: true - -require "minitest/autorun" -require "stringio" -require "json" -require "test_helper" - -NUMBERS = (1..65).to_a -PageResponse = Struct.new(:cards, :next_cursor, keyword_init: true) - -class CursorItemIteratorTest < Minitest::Test - def make_iterator(initial_cursor:) - @times_called = 0 - - Seed::Internal::CursorItemIterator.new(initial_cursor:, cursor_field: :next_cursor, item_field: :cards) do |cursor| - @times_called += 1 - cursor ||= 0 - next_cursor = cursor + 10 - PageResponse.new( - cards: NUMBERS[cursor...next_cursor], - next_cursor: next_cursor < NUMBERS.length ? next_cursor : nil - ) - end - end - - def test_item_iterator_can_iterate_to_exhaustion - iterator = make_iterator(initial_cursor: 0) - - assert_equal NUMBERS, iterator.to_a - assert_equal 7, @times_called - - iterator = make_iterator(initial_cursor: 10) - - assert_equal (11..65).to_a, iterator.to_a - - iterator = make_iterator(initial_cursor: 5) - - assert_equal (6..65).to_a, iterator.to_a - end - - def test_item_iterator_can_work_without_an_initial_cursor - iterator = make_iterator(initial_cursor: nil) - - assert_equal NUMBERS, iterator.to_a - assert_equal 7, @times_called - end - - def test_items_iterator_iterates_lazily - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - assert_equal 1, iterator.first - assert_equal 1, @times_called - - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - assert_equal (1..15).to_a, iterator.first(15) - assert_equal 2, @times_called - - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - iterator.each do |card| - break if card >= 15 - end - - assert_equal 2, @times_called - end - - def test_items_iterator_implements_enumerable - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - doubled = iterator.map { |card| card * 2 } - - assert_equal 7, @times_called - assert_equal NUMBERS.length, doubled.length - end - - def test_items_iterator_can_be_advanced_manually - iterator = make_iterator(initial_cursor: 0) - - assert_equal 0, @times_called - - items = [] - expected_times_called = 0 - while (item = iterator.next_element) - expected_times_called += 1 if (item % 10) == 1 - - assert_equal expected_times_called, @times_called - assert_equal item != NUMBERS.last, iterator.next?, "#{item} #{iterator}" - items.push(item) - end - - assert_equal 7, @times_called - assert_equal NUMBERS, items - end - - def test_pages_iterator - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal( - [ - (1..10).to_a, - (11..20).to_a, - (21..30).to_a, - (31..40).to_a, - (41..50).to_a, - (51..60).to_a, - (61..65).to_a - ], - iterator.to_a.map(&:cards) - ) - - iterator = make_iterator(initial_cursor: 10).pages - - assert_equal( - [ - (11..20).to_a, - (21..30).to_a, - (31..40).to_a, - (41..50).to_a, - (51..60).to_a, - (61..65).to_a - ], - iterator.to_a.map(&:cards) - ) - end - - def test_pages_iterator_can_work_without_an_initial_cursor - iterator = make_iterator(initial_cursor: nil).pages - - assert_equal 7, iterator.to_a.length - assert_equal 7, @times_called - end - - def test_pages_iterator_iterates_lazily - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - iterator.first - - assert_equal 1, @times_called - - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - assert_equal 2, iterator.first(2).length - assert_equal 2, @times_called - end - - def test_pages_iterator_knows_whether_another_page_is_upcoming - iterator = make_iterator(initial_cursor: 0).pages - - iterator.each_with_index do |_page, index| - assert_equal index + 1, @times_called - assert_equal index < 6, iterator.next? - end - end - - def test_pages_iterator_can_be_advanced_manually - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - - lengths = [] - expected_times_called = 0 - while (page = iterator.next_page) - expected_times_called += 1 - - assert_equal expected_times_called, @times_called - lengths.push(page.cards.length) - end - - assert_equal 7, @times_called - assert_equal [10, 10, 10, 10, 10, 10, 5], lengths - end - - def test_pages_iterator_implements_enumerable - iterator = make_iterator(initial_cursor: 0).pages - - assert_equal 0, @times_called - lengths = iterator.map { |page| page.cards.length } - - assert_equal 7, @times_called - assert_equal [10, 10, 10, 10, 10, 10, 5], lengths - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb deleted file mode 100644 index 92576b820128..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/iterators/test_offset_item_iterator.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true - -require "minitest/autorun" -require "stringio" -require "json" -require "test_helper" - -OffsetPageResponse = Struct.new(:items, :has_next, keyword_init: true) -TestIteratorConfig = Struct.new( - :step, - :has_next_field, - :total_item_count, - :per_page, - :initial_page -) do - def first_item_returned - if step - (initial_page || 0) + 1 - else - (((initial_page || 1) - 1) * per_page) + 1 - end - end -end - -LAZY_TEST_ITERATOR_CONFIG = TestIteratorConfig.new(initial_page: 1, step: false, has_next_field: :has_next, total_item_count: 65, per_page: 10) -ALL_TEST_ITERATOR_CONFIGS = [true, false].map do |step| - [:has_next, nil].map do |has_next_field| - [0, 5, 10, 60, 63].map do |total_item_count| - [5, 10].map do |per_page| - initial_pages = [nil, 3, 100] - initial_pages << (step ? 0 : 1) - - initial_pages.map do |initial_page| - TestIteratorConfig.new( - step: step, - has_next_field: has_next_field, - total_item_count: total_item_count, - per_page: per_page, - initial_page: initial_page - ) - end - end - end - end -end.flatten - -class OffsetItemIteratorTest < Minitest::Test - def make_iterator(config) - @times_called = 0 - - items = (1..config.total_item_count).to_a - - Seed::Internal::OffsetItemIterator.new( - initial_page: config.initial_page, - item_field: :items, - has_next_field: config.has_next_field, - step: config.step - ) do |page| - @times_called += 1 - - slice_start = config.step ? page : (page - 1) * config.per_page - slice_end = slice_start + config.per_page - - output = { - items: items[slice_start...slice_end] - } - output[config.has_next_field] = slice_end < items.length if config.has_next_field - - OffsetPageResponse.new(**output) - end - end - - def test_item_iterator_can_iterate_to_exhaustion - ALL_TEST_ITERATOR_CONFIGS.each do |config| - iterator = make_iterator(config) - - assert_equal (config.first_item_returned..config.total_item_count).to_a, iterator.to_a - end - end - - def test_items_iterator_can_be_advanced_manually_and_has_accurate_has_next - ALL_TEST_ITERATOR_CONFIGS.each do |config| - iterator = make_iterator(config) - items = [] - - while (item = iterator.next_element) - assert_equal(item != config.total_item_count, iterator.next?, "#{item} #{iterator}") - items.push(item) - end - - assert_equal (config.first_item_returned..config.total_item_count).to_a, items - end - end - - def test_pages_iterator_can_be_advanced_manually_and_has_accurate_has_next - ALL_TEST_ITERATOR_CONFIGS.each do |config| - iterator = make_iterator(config).pages - pages = [] - - loop do - has_next_output = iterator.next? - page = iterator.next_page - - assert_equal(has_next_output, !page.nil?, "next? was inaccurate: #{config} #{iterator.inspect}") - break if page.nil? - - pages.push(page) - end - - assert_equal pages, make_iterator(config).pages.to_a - end - end - - def test_items_iterator_iterates_lazily - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) - - assert_equal 0, @times_called - assert_equal 1, iterator.first - assert_equal 1, @times_called - - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) - - assert_equal 0, @times_called - assert_equal (1..15).to_a, iterator.first(15) - assert_equal 2, @times_called - - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG) - - assert_equal 0, @times_called - iterator.each do |card| - break if card >= 15 - end - - assert_equal 2, @times_called - end - - def test_pages_iterator_iterates_lazily - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages - - assert_equal 0, @times_called - iterator.first - - assert_equal 1, @times_called - - iterator = make_iterator(LAZY_TEST_ITERATOR_CONFIG).pages - - assert_equal 0, @times_called - assert_equal 3, iterator.first(3).length - assert_equal 3, @times_called - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb deleted file mode 100644 index e7e6571f03ee..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_array.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Array do - module TestArray - StringArray = Seed::Internal::Types::Array[String] - end - - describe "#initialize" do - it "sets the type" do - assert_equal String, TestArray::StringArray.type - end - end - - describe "#coerce" do - it "does not perform coercion if not an array" do - assert_equal 1, TestArray::StringArray.coerce(1) - end - - it "raises an error if not an array and strictness is on" do - assert_raises Seed::Internal::Errors::TypeError do - TestArray::StringArray.coerce(1, strict: true) - end - end - - it "coerces the elements" do - assert_equal %w[foobar 1 true], TestArray::StringArray.coerce(["foobar", 1, true]) - end - - it "raises an error if element of array is not coercable and strictness is on" do - assert_raises Seed::Internal::Errors::TypeError do - TestArray::StringArray.coerce([Object.new], strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb deleted file mode 100644 index cba18e48765b..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_boolean.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Boolean do - describe ".coerce" do - it "coerces true/false" do - assert Seed::Internal::Types::Boolean.coerce(true) - refute Seed::Internal::Types::Boolean.coerce(false) - end - - it "coerces an Integer" do - assert Seed::Internal::Types::Boolean.coerce(1) - refute Seed::Internal::Types::Boolean.coerce(0) - end - - it "coerces a String" do - assert Seed::Internal::Types::Boolean.coerce("1") - assert Seed::Internal::Types::Boolean.coerce("true") - refute Seed::Internal::Types::Boolean.coerce("0") - end - - it "passes through other values with strictness off" do - obj = Object.new - - assert_equal obj, Seed::Internal::Types::Boolean.coerce(obj) - end - - it "raises an error with other values with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - Seed::Internal::Types::Boolean.coerce(Object.new, strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb deleted file mode 100644 index e8d89bce467f..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_enum.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Enum do - module EnumTest - module ExampleEnum - extend Seed::Internal::Types::Enum - - FOO = :foo - BAR = :bar - - finalize! - end - end - - describe "#values" do - it "defines values" do - assert_equal %i[foo bar].sort, EnumTest::ExampleEnum.values.sort - end - end - - describe "#coerce" do - it "coerces an existing member" do - assert_equal :foo, EnumTest::ExampleEnum.coerce(:foo) - end - - it "coerces a string version of a member" do - assert_equal :foo, EnumTest::ExampleEnum.coerce("foo") - end - - it "returns the value if not a member with strictness off" do - assert_equal 1, EnumTest::ExampleEnum.coerce(1) - end - - it "raises an error if value is not a member with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - EnumTest::ExampleEnum.coerce(1, strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb deleted file mode 100644 index 6c5e58a6a946..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_hash.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Hash do - module TestHash - SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] - end - - describe ".[]" do - it "defines the key and value type" do - assert_equal Symbol, TestHash::SymbolStringHash.key_type - assert_equal String, TestHash::SymbolStringHash.value_type - end - end - - describe "#coerce" do - it "coerces the keys" do - assert_equal %i[foo bar], TestHash::SymbolStringHash.coerce({ "foo" => "1", :bar => "2" }).keys - end - - it "coerces the values" do - assert_equal %w[foo 1], TestHash::SymbolStringHash.coerce({ foo: :foo, bar: 1 }).values - end - - it "passes through other values with strictness off" do - obj = Object.new - - assert_equal obj, TestHash::SymbolStringHash.coerce(obj) - end - - it "raises an error with other values with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - TestHash::SymbolStringHash.coerce(Object.new, strict: true) - end - end - - it "raises an error with non-coercable key types with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - TestHash::SymbolStringHash.coerce({ Object.new => 1 }, strict: true) - end - end - - it "raises an error with non-coercable value types with strictness on" do - assert_raises Seed::Internal::Errors::TypeError do - TestHash::SymbolStringHash.coerce({ "foobar" => Object.new }, strict: true) - end - end - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb deleted file mode 100644 index 3d87b9f5a8c7..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_model.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Model do - module StringInteger - extend Seed::Internal::Types::Union - - member String - member Integer - end - - class ExampleModel < Seed::Internal::Types::Model - field :name, String - field :rating, StringInteger, optional: true - field :year, Integer, optional: true, nullable: true, api_name: "yearOfRelease" - end - - class ExampleModelInheritance < ExampleModel - field :director, String - end - - class ExampleWithDefaults < ExampleModel - field :type, String, default: "example" - end - - class ExampleChild < Seed::Internal::Types::Model - field :value, String - end - - class ExampleParent < Seed::Internal::Types::Model - field :child, ExampleChild - end - - describe ".field" do - before do - @example = ExampleModel.new(name: "Inception", rating: 4) - end - - it "defines fields on model" do - assert_equal %i[name rating year], ExampleModel.fields.keys - end - - it "defines fields from parent models" do - assert_equal %i[name rating year director], ExampleModelInheritance.fields.keys - end - - it "sets the field's type" do - assert_equal String, ExampleModel.fields[:name].type - assert_equal StringInteger, ExampleModel.fields[:rating].type - end - - it "sets the `default` option" do - assert_equal "example", ExampleWithDefaults.fields[:type].default - end - - it "defines getters" do - assert_respond_to @example, :name - assert_respond_to @example, :rating - - assert_equal "Inception", @example.name - assert_equal 4, @example.rating - end - - it "defines setters" do - assert_respond_to @example, :name= - assert_respond_to @example, :rating= - - @example.name = "Inception 2" - @example.rating = 5 - - assert_equal "Inception 2", @example.name - assert_equal 5, @example.rating - end - end - - describe "#initialize" do - it "sets the data" do - example = ExampleModel.new(name: "Inception", rating: 4) - - assert_equal "Inception", example.name - assert_equal 4, example.rating - end - - it "allows extra fields to be set" do - example = ExampleModel.new(name: "Inception", rating: 4, director: "Christopher Nolan") - - assert_equal "Christopher Nolan", example.director - end - - it "sets the defaults where applicable" do - example_using_defaults = ExampleWithDefaults.new - - assert_equal "example", example_using_defaults.type - - example_without_defaults = ExampleWithDefaults.new(type: "not example") - - assert_equal "not example", example_without_defaults.type - end - - it "coerces child models" do - parent = ExampleParent.new(child: { value: "foobar" }) - - assert_kind_of ExampleChild, parent.child - end - - it "uses the api_name to pull the value" do - example = ExampleModel.new({ name: "Inception", yearOfRelease: 2014 }) - - assert_equal 2014, example.year - refute_respond_to example, :yearOfRelease - end - end - - describe "#inspect" do - class SensitiveModel < Seed::Internal::Types::Model - field :username, String - field :password, String - field :client_secret, String - field :access_token, String - field :api_key, String - end - - it "redacts sensitive fields" do - model = SensitiveModel.new( - username: "user123", - password: "secret123", - client_secret: "cs_abc", - access_token: "token_xyz", - api_key: "key_123" - ) - - inspect_output = model.inspect - - assert_includes inspect_output, "username=\"user123\"" - assert_includes inspect_output, "password=[REDACTED]" - assert_includes inspect_output, "client_secret=[REDACTED]" - assert_includes inspect_output, "access_token=[REDACTED]" - assert_includes inspect_output, "api_key=[REDACTED]" - refute_includes inspect_output, "secret123" - refute_includes inspect_output, "cs_abc" - refute_includes inspect_output, "token_xyz" - refute_includes inspect_output, "key_123" - end - - it "does not redact non-sensitive fields" do - example = ExampleModel.new(name: "Inception", rating: 4) - inspect_output = example.inspect - - assert_includes inspect_output, "name=\"Inception\"" - assert_includes inspect_output, "rating=4" - end - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb deleted file mode 100644 index e4e95c93139f..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_union.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Union do - class Rectangle < Seed::Internal::Types::Model - literal :type, "square" - - field :area, Float - end - - class Circle < Seed::Internal::Types::Model - literal :type, "circle" - - field :area, Float - end - - class Pineapple < Seed::Internal::Types::Model - literal :type, "pineapple" - - field :area, Float - end - - module Shape - extend Seed::Internal::Types::Union - - discriminant :type - - member -> { Rectangle }, key: "rect" - member -> { Circle }, key: "circle" - end - - module StringOrInteger - extend Seed::Internal::Types::Union - - member String - member Integer - end - - describe "#coerce" do - it "coerces hashes into member models with discriminated unions" do - circle = Shape.coerce({ type: "circle", area: 4.0 }) - - assert_instance_of Circle, circle - end - end - - describe "#type_member?" do - it "defines Model members" do - assert Shape.type_member?(Rectangle) - assert Shape.type_member?(Circle) - refute Shape.type_member?(Pineapple) - end - - it "defines other members" do - assert StringOrInteger.type_member?(String) - assert StringOrInteger.type_member?(Integer) - refute StringOrInteger.type_member?(Float) - refute StringOrInteger.type_member?(Pineapple) - end - end -end diff --git a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb b/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb deleted file mode 100644 index 29d14621a229..000000000000 --- a/seed/ruby-sdk-v2/allof/test/unit/internal/types/test_utils.rb +++ /dev/null @@ -1,212 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Seed::Internal::Types::Utils do - Utils = Seed::Internal::Types::Utils - - module TestUtils - class M < Seed::Internal::Types::Model - field :value, String - end - - class UnionMemberA < Seed::Internal::Types::Model - literal :type, "A" - field :only_on_a, String - end - - class UnionMemberB < Seed::Internal::Types::Model - literal :type, "B" - field :only_on_b, String - end - - module U - extend Seed::Internal::Types::Union - - discriminant :type - - member -> { UnionMemberA }, key: "A" - member -> { UnionMemberB }, key: "B" - end - - SymbolStringHash = Seed::Internal::Types::Hash[Symbol, String] - SymbolModelHash = -> { Seed::Internal::Types::Hash[Symbol, TestUtils::M] } - end - - describe ".coerce" do - describe "NilClass" do - it "always returns nil" do - assert_nil Utils.coerce(NilClass, "foobar") - assert_nil Utils.coerce(NilClass, 1) - assert_nil Utils.coerce(NilClass, Object.new) - end - end - - describe "String" do - it "coerces from String, Symbol, Numeric, or Boolean" do - assert_equal "foobar", Utils.coerce(String, "foobar") - assert_equal "foobar", Utils.coerce(String, :foobar) - assert_equal "1", Utils.coerce(String, 1) - assert_equal "1.0", Utils.coerce(String, 1.0) - assert_equal "true", Utils.coerce(String, true) - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(String, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(String, Object.new, strict: true) - end - end - end - - describe "Symbol" do - it "coerces from Symbol, String" do - assert_equal :foobar, Utils.coerce(Symbol, :foobar) - assert_equal :foobar, Utils.coerce(Symbol, "foobar") - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(Symbol, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(Symbol, Object.new, strict: true) - end - end - end - - describe "Integer" do - it "coerces from Numeric, String, Time" do - assert_equal 1, Utils.coerce(Integer, 1) - assert_equal 1, Utils.coerce(Integer, 1.0) - assert_equal 1, Utils.coerce(Integer, Complex.rect(1)) - assert_equal 1, Utils.coerce(Integer, Rational(1)) - assert_equal 1, Utils.coerce(Integer, "1") - assert_equal 1_713_916_800, Utils.coerce(Integer, Time.utc(2024, 4, 24)) - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(Integer, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(Integer, Object.new, strict: true) - end - end - end - - describe "Float" do - it "coerces from Numeric, Time" do - assert_in_delta(1.0, Utils.coerce(Float, 1.0)) - assert_in_delta(1.0, Utils.coerce(Float, 1)) - assert_in_delta(1.0, Utils.coerce(Float, Complex.rect(1))) - assert_in_delta(1.0, Utils.coerce(Float, Rational(1))) - assert_in_delta(1_713_916_800.0, Utils.coerce(Integer, Time.utc(2024, 4, 24))) - end - - it "passes through value if it cannot be coerced and not strict" do - obj = Object.new - - assert_equal obj, Utils.coerce(Float, obj) - end - - it "raises an error if value cannot be coerced and strict" do - assert_raises Seed::Internal::Errors::TypeError do - Utils.coerce(Float, Object.new, strict: true) - end - end - end - - describe "Model" do - it "coerces a hash" do - result = Utils.coerce(TestUtils::M, { value: "foobar" }) - - assert_kind_of TestUtils::M, result - assert_equal "foobar", result.value - end - - it "coerces a hash when the target is a type function" do - result = Utils.coerce(-> { TestUtils::M }, { value: "foobar" }) - - assert_kind_of TestUtils::M, result - assert_equal "foobar", result.value - end - - it "will not coerce non-hashes" do - assert_equal "foobar", Utils.coerce(TestUtils::M, "foobar") - end - end - - describe "Enum" do - module ExampleEnum - extend Seed::Internal::Types::Enum - - FOO = :FOO - BAR = :BAR - - finalize! - end - - it "coerces into a Symbol version of the member value" do - assert_equal :FOO, Utils.coerce(ExampleEnum, "FOO") - end - - it "returns given value if not a member" do - assert_equal "NOPE", Utils.coerce(ExampleEnum, "NOPE") - end - end - - describe "Array" do - StringArray = Seed::Internal::Types::Array[String] - ModelArray = -> { Seed::Internal::Types::Array[TestUtils::M] } - UnionArray = -> { Seed::Internal::Types::Array[TestUtils::U] } - - it "coerces an array of literals" do - assert_equal %w[a b c], Utils.coerce(StringArray, %w[a b c]) - assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, [1, 2.0, true]) - assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, Set.new([1, 2.0, true])) - end - - it "coerces an array of Models" do - assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], - Utils.coerce(ModelArray, [{ value: "foobar" }, { value: "bizbaz" }]) - - assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], - Utils.coerce(ModelArray, [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")]) - end - - it "coerces an array of model unions" do - assert_equal [TestUtils::UnionMemberA.new(type: "A", only_on_a: "A"), TestUtils::UnionMemberB.new(type: "B", only_on_b: "B")], - Utils.coerce(UnionArray, [{ type: "A", only_on_a: "A" }, { type: "B", only_on_b: "B" }]) - end - - it "returns given value if not an array" do - assert_equal 1, Utils.coerce(StringArray, 1) - end - end - - describe "Hash" do - it "coerces the keys and values" do - ssh_res = Utils.coerce(TestUtils::SymbolStringHash, { "foo" => "bar", "biz" => "2" }) - - assert_equal "bar", ssh_res[:foo] - assert_equal "2", ssh_res[:biz] - - smh_res = Utils.coerce(TestUtils::SymbolModelHash, { "foo" => { "value" => "foo" } }) - - assert_equal TestUtils::M.new(value: "foo"), smh_res[:foo] - end - end - end -end diff --git a/seed/rust-model/allof-inline/.fern/metadata.json b/seed/rust-model/allof-inline/.fern/metadata.json deleted file mode 100644 index a9c2c174fe54..000000000000 --- a/seed/rust-model/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-rust-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/rust-model/allof-inline/snippet.json b/seed/rust-model/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/rust-model/allof-inline/src/audit_info.rs b/seed/rust-model/allof-inline/src/audit_info.rs deleted file mode 100644 index a958df434a50..000000000000 --- a/seed/rust-model/allof-inline/src/audit_info.rs +++ /dev/null @@ -1,73 +0,0 @@ -pub use crate::prelude::*; - -/// Common audit metadata. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct AuditInfo { - /// The user who created this resource. - #[serde(rename = "createdBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub created_by: Option, - /// When this resource was created. - #[serde(rename = "createdDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub created_date_time: Option>, - /// The user who last modified this resource. - #[serde(rename = "modifiedBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub modified_by: Option, - /// When this resource was last modified. - #[serde(rename = "modifiedDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub modified_date_time: Option>, -} - -impl AuditInfo { - pub fn builder() -> AuditInfoBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct AuditInfoBuilder { - created_by: Option, - created_date_time: Option>, - modified_by: Option, - modified_date_time: Option>, -} - -impl AuditInfoBuilder { - pub fn created_by(mut self, value: impl Into) -> Self { - self.created_by = Some(value.into()); - self - } - - pub fn created_date_time(mut self, value: DateTime) -> Self { - self.created_date_time = Some(value); - self - } - - pub fn modified_by(mut self, value: impl Into) -> Self { - self.modified_by = Some(value.into()); - self - } - - pub fn modified_date_time(mut self, value: DateTime) -> Self { - self.modified_date_time = Some(value); - self - } - - /// Consumes the builder and constructs a [`AuditInfo`]. - pub fn build(self) -> Result { - Ok(AuditInfo { - created_by: self.created_by, - created_date_time: self.created_date_time, - modified_by: self.modified_by, - modified_date_time: self.modified_date_time, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/base_org.rs b/seed/rust-model/allof-inline/src/base_org.rs deleted file mode 100644 index 1747ce8ded53..000000000000 --- a/seed/rust-model/allof-inline/src/base_org.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrg { - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl BaseOrg { - pub fn builder() -> BaseOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgBuilder { - id: Option, - metadata: Option, -} - -impl BaseOrgBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`BaseOrg`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](BaseOrgBuilder::id) - pub fn build(self) -> Result { - Ok(BaseOrg { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/base_org_metadata.rs b/seed/rust-model/allof-inline/src/base_org_metadata.rs deleted file mode 100644 index 797f4434eb49..000000000000 --- a/seed/rust-model/allof-inline/src/base_org_metadata.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrgMetadata { - /// Deployment region from BaseOrg. - #[serde(default)] - pub region: String, - /// Subscription tier. - #[serde(skip_serializing_if = "Option::is_none")] - pub tier: Option, -} - -impl BaseOrgMetadata { - pub fn builder() -> BaseOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgMetadataBuilder { - region: Option, - tier: Option, -} - -impl BaseOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn tier(mut self, value: impl Into) -> Self { - self.tier = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`BaseOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](BaseOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(BaseOrgMetadata { - region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, - tier: self.tier, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/combined_entity.rs b/seed/rust-model/allof-inline/src/combined_entity.rs deleted file mode 100644 index ded16140ef34..000000000000 --- a/seed/rust-model/allof-inline/src/combined_entity.rs +++ /dev/null @@ -1,65 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct CombinedEntity { - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Describable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, - pub status: CombinedEntityStatus, -} - -impl CombinedEntity { - pub fn builder() -> CombinedEntityBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct CombinedEntityBuilder { - id: Option, - name: Option, - summary: Option, - status: Option, -} - -impl CombinedEntityBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - pub fn status(mut self, value: CombinedEntityStatus) -> Self { - self.status = Some(value); - self - } - - /// Consumes the builder and constructs a [`CombinedEntity`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](CombinedEntityBuilder::id) - /// - [`status`](CombinedEntityBuilder::status) - pub fn build(self) -> Result { - Ok(CombinedEntity { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - summary: self.summary, - status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/combined_entity_status.rs b/seed/rust-model/allof-inline/src/combined_entity_status.rs deleted file mode 100644 index f46cb23eecb2..000000000000 --- a/seed/rust-model/allof-inline/src/combined_entity_status.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum CombinedEntityStatus { - Active, - Archived, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for CombinedEntityStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Archived => serializer.serialize_str("archived"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for CombinedEntityStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "archived" => Ok(Self::Archived), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for CombinedEntityStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Archived => write!(f, "archived"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-model/allof-inline/src/describable.rs b/seed/rust-model/allof-inline/src/describable.rs deleted file mode 100644 index 6a8a527bd844..000000000000 --- a/seed/rust-model/allof-inline/src/describable.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Describable { - /// Display name from Describable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, -} - -impl Describable { - pub fn builder() -> DescribableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DescribableBuilder { - name: Option, - summary: Option, -} - -impl DescribableBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Describable`]. - pub fn build(self) -> Result { - Ok(Describable { - name: self.name, - summary: self.summary, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/detailed_org.rs b/seed/rust-model/allof-inline/src/detailed_org.rs deleted file mode 100644 index fac068d8a2ee..000000000000 --- a/seed/rust-model/allof-inline/src/detailed_org.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrg { - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl DetailedOrg { - pub fn builder() -> DetailedOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgBuilder { - metadata: Option, -} - -impl DetailedOrgBuilder { - pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`DetailedOrg`]. - pub fn build(self) -> Result { - Ok(DetailedOrg { - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/detailed_org_metadata.rs b/seed/rust-model/allof-inline/src/detailed_org_metadata.rs deleted file mode 100644 index 06ec63051131..000000000000 --- a/seed/rust-model/allof-inline/src/detailed_org_metadata.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrgMetadata { - /// Deployment region from DetailedOrg. - #[serde(default)] - pub region: String, - /// Custom domain name. - #[serde(skip_serializing_if = "Option::is_none")] - pub domain: Option, -} - -impl DetailedOrgMetadata { - pub fn builder() -> DetailedOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgMetadataBuilder { - region: Option, - domain: Option, -} - -impl DetailedOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn domain(mut self, value: impl Into) -> Self { - self.domain = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](DetailedOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(DetailedOrgMetadata { - region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, - domain: self.domain, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/error.rs b/seed/rust-model/allof-inline/src/error.rs deleted file mode 100644 index 0966ed354a0f..000000000000 --- a/seed/rust-model/allof-inline/src/error.rs +++ /dev/null @@ -1,19 +0,0 @@ -/// Error returned when a required field was not set on a builder. -#[derive(Debug)] -pub struct BuildError { - field: &'static str, -} - -impl BuildError { - pub fn missing_field(field: &'static str) -> Self { - Self { field } - } -} - -impl std::fmt::Display for BuildError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "`{}` was not set but is required", self.field) - } -} - -impl std::error::Error for BuildError {} diff --git a/seed/rust-model/allof-inline/src/identifiable.rs b/seed/rust-model/allof-inline/src/identifiable.rs deleted file mode 100644 index 58fb3eb70734..000000000000 --- a/seed/rust-model/allof-inline/src/identifiable.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Identifiable { - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Identifiable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, -} - -impl Identifiable { - pub fn builder() -> IdentifiableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct IdentifiableBuilder { - id: Option, - name: Option, -} - -impl IdentifiableBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Identifiable`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](IdentifiableBuilder::id) - pub fn build(self) -> Result { - Ok(Identifiable { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/lib.rs b/seed/rust-model/allof-inline/src/lib.rs deleted file mode 100644 index 077ee6dee849..000000000000 --- a/seed/rust-model/allof-inline/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Generated models by Fern - -pub mod error; - -pub mod types; - -pub use types::*; diff --git a/seed/rust-model/allof-inline/src/mod.rs b/seed/rust-model/allof-inline/src/mod.rs deleted file mode 100644 index c51911de6f27..000000000000 --- a/seed/rust-model/allof-inline/src/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Request and response types for the allOf Composition -//! -//! This module contains all data structures used for API communication, -//! including request bodies, response types, and shared models. -//! -//! ## Type Categories -//! -//! - **Request/Response Types**: 6 types for API operations -//! - **Model Types**: 16 types for data representation - -pub mod paginated_result; -pub mod paging_cursors; -pub mod rule_execution_context; -pub mod audit_info; -pub mod rule_type; -pub mod rule_type_search_response; -pub mod user; -pub mod user_search_response; -pub mod rule_response_status; -pub mod rule_response; -pub mod identifiable; -pub mod describable; -pub mod combined_entity_status; -pub mod combined_entity; -pub mod base_org_metadata; -pub mod base_org; -pub mod detailed_org_metadata; -pub mod detailed_org; -pub mod organization_metadata; -pub mod organization; -pub mod rule_create_request; -pub mod search_rule_types_query_request; - -pub use paginated_result::PaginatedResult; -pub use paging_cursors::PagingCursors; -pub use rule_execution_context::RuleExecutionContext; -pub use audit_info::AuditInfo; -pub use rule_type::RuleType; -pub use rule_type_search_response::RuleTypeSearchResponse; -pub use user::User; -pub use user_search_response::UserSearchResponse; -pub use rule_response_status::RuleResponseStatus; -pub use rule_response::RuleResponse; -pub use identifiable::Identifiable; -pub use describable::Describable; -pub use combined_entity_status::CombinedEntityStatus; -pub use combined_entity::CombinedEntity; -pub use base_org_metadata::BaseOrgMetadata; -pub use base_org::BaseOrg; -pub use detailed_org_metadata::DetailedOrgMetadata; -pub use detailed_org::DetailedOrg; -pub use organization_metadata::OrganizationMetadata; -pub use organization::Organization; -pub use rule_create_request::RuleCreateRequest; -pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; - diff --git a/seed/rust-model/allof-inline/src/organization.rs b/seed/rust-model/allof-inline/src/organization.rs deleted file mode 100644 index 3f727a6b8212..000000000000 --- a/seed/rust-model/allof-inline/src/organization.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Organization { - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - #[serde(default)] - pub name: String, -} - -impl Organization { - pub fn builder() -> OrganizationBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct OrganizationBuilder { - id: Option, - metadata: Option, - name: Option, -} - -impl OrganizationBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: OrganizationMetadata) -> Self { - self.metadata = Some(value); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Organization`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](OrganizationBuilder::id) - /// - [`name`](OrganizationBuilder::name) - pub fn build(self) -> Result { - Ok(Organization { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/organization_metadata.rs b/seed/rust-model/allof-inline/src/organization_metadata.rs deleted file mode 100644 index b77a61312aa1..000000000000 --- a/seed/rust-model/allof-inline/src/organization_metadata.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct OrganizationMetadata { - /// Deployment region from DetailedOrg. - #[serde(default)] - pub region: String, - /// Custom domain name. - #[serde(skip_serializing_if = "Option::is_none")] - pub domain: Option, -} - -impl OrganizationMetadata { - pub fn builder() -> OrganizationMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct OrganizationMetadataBuilder { - region: Option, - domain: Option, -} - -impl OrganizationMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn domain(mut self, value: impl Into) -> Self { - self.domain = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`OrganizationMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](OrganizationMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(OrganizationMetadata { - region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, - domain: self.domain, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/paginated_result.rs b/seed/rust-model/allof-inline/src/paginated_result.rs deleted file mode 100644 index d91039ad7253..000000000000 --- a/seed/rust-model/allof-inline/src/paginated_result.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] -pub struct PaginatedResult { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(default)] - pub results: Vec, -} - -impl PaginatedResult { - pub fn builder() -> PaginatedResultBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PaginatedResultBuilder { - paging: Option, - results: Option>, -} - -impl PaginatedResultBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`PaginatedResult`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](PaginatedResultBuilder::paging) - /// - [`results`](PaginatedResultBuilder::results) - pub fn build(self) -> Result { - Ok(PaginatedResult { - paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, - results: self.results.ok_or_else(|| BuildError::missing_field("results"))?, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/paging_cursors.rs b/seed/rust-model/allof-inline/src/paging_cursors.rs deleted file mode 100644 index a3a785119b57..000000000000 --- a/seed/rust-model/allof-inline/src/paging_cursors.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct PagingCursors { - /// Cursor for the next page of results. - #[serde(default)] - pub next: String, - /// Cursor for the previous page of results. - #[serde(skip_serializing_if = "Option::is_none")] - pub previous: Option, -} - -impl PagingCursors { - pub fn builder() -> PagingCursorsBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PagingCursorsBuilder { - next: Option, - previous: Option, -} - -impl PagingCursorsBuilder { - pub fn next(mut self, value: impl Into) -> Self { - self.next = Some(value.into()); - self - } - - pub fn previous(mut self, value: impl Into) -> Self { - self.previous = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`PagingCursors`]. - /// This method will fail if any of the following fields are not set: - /// - [`next`](PagingCursorsBuilder::next) - pub fn build(self) -> Result { - Ok(PagingCursors { - next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, - previous: self.previous, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/rule_create_request.rs b/seed/rust-model/allof-inline/src/rule_create_request.rs deleted file mode 100644 index 3401318222ee..000000000000 --- a/seed/rust-model/allof-inline/src/rule_create_request.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleCreateRequest { - #[serde(default)] - pub name: String, - #[serde(rename = "executionContext")] - pub execution_context: RuleExecutionContext, -} - -impl RuleCreateRequest { - pub fn builder() -> RuleCreateRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleCreateRequestBuilder { - name: Option, - execution_context: Option, -} - -impl RuleCreateRequestBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleCreateRequest`]. - /// This method will fail if any of the following fields are not set: - /// - [`name`](RuleCreateRequestBuilder::name) - /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) - pub fn build(self) -> Result { - Ok(RuleCreateRequest { - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - execution_context: self.execution_context.ok_or_else(|| BuildError::missing_field("execution_context"))?, - }) - } -} - diff --git a/seed/rust-model/allof-inline/src/rule_execution_context.rs b/seed/rust-model/allof-inline/src/rule_execution_context.rs deleted file mode 100644 index 7f8fa5bd56e1..000000000000 --- a/seed/rust-model/allof-inline/src/rule_execution_context.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -/// Execution environment for a rule. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleExecutionContext { - Prod, - Staging, - Dev, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleExecutionContext { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Prod => serializer.serialize_str("prod"), - Self::Staging => serializer.serialize_str("staging"), - Self::Dev => serializer.serialize_str("dev"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleExecutionContext { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "prod" => Ok(Self::Prod), - "staging" => Ok(Self::Staging), - "dev" => Ok(Self::Dev), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleExecutionContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Prod => write!(f, "prod"), - Self::Staging => write!(f, "staging"), - Self::Dev => write!(f, "dev"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-model/allof-inline/src/rule_response.rs b/seed/rust-model/allof-inline/src/rule_response.rs deleted file mode 100644 index c9aa002691af..000000000000 --- a/seed/rust-model/allof-inline/src/rule_response.rs +++ /dev/null @@ -1,112 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleResponse { - /// The user who created this resource. - #[serde(rename = "createdBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub created_by: Option, - /// When this resource was created. - #[serde(rename = "createdDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub created_date_time: Option>, - /// The user who last modified this resource. - #[serde(rename = "modifiedBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub modified_by: Option, - /// When this resource was last modified. - #[serde(rename = "modifiedDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub modified_date_time: Option>, - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - pub status: RuleResponseStatus, - #[serde(rename = "executionContext")] - #[serde(skip_serializing_if = "Option::is_none")] - pub execution_context: Option, -} - -impl RuleResponse { - pub fn builder() -> RuleResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleResponseBuilder { - created_by: Option, - created_date_time: Option>, - modified_by: Option, - modified_date_time: Option>, - id: Option, - name: Option, - status: Option, - execution_context: Option, -} - -impl RuleResponseBuilder { - pub fn created_by(mut self, value: impl Into) -> Self { - self.created_by = Some(value.into()); - self - } - - pub fn created_date_time(mut self, value: DateTime) -> Self { - self.created_date_time = Some(value); - self - } - - pub fn modified_by(mut self, value: impl Into) -> Self { - self.modified_by = Some(value.into()); - self - } - - pub fn modified_date_time(mut self, value: DateTime) -> Self { - self.modified_date_time = Some(value); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn status(mut self, value: RuleResponseStatus) -> Self { - self.status = Some(value); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](RuleResponseBuilder::id) - /// - [`name`](RuleResponseBuilder::name) - /// - [`status`](RuleResponseBuilder::status) - pub fn build(self) -> Result { - Ok(RuleResponse { - created_by: self.created_by, - created_date_time: self.created_date_time, - modified_by: self.modified_by, - modified_date_time: self.modified_date_time, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, - execution_context: self.execution_context, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/rule_response_status.rs b/seed/rust-model/allof-inline/src/rule_response_status.rs deleted file mode 100644 index 5d2462ecd349..000000000000 --- a/seed/rust-model/allof-inline/src/rule_response_status.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleResponseStatus { - Active, - Inactive, - Draft, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleResponseStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Inactive => serializer.serialize_str("inactive"), - Self::Draft => serializer.serialize_str("draft"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleResponseStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "inactive" => Ok(Self::Inactive), - "draft" => Ok(Self::Draft), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleResponseStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Inactive => write!(f, "inactive"), - Self::Draft => write!(f, "draft"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-model/allof-inline/src/rule_type.rs b/seed/rust-model/allof-inline/src/rule_type.rs deleted file mode 100644 index 69f0317c1923..000000000000 --- a/seed/rust-model/allof-inline/src/rule_type.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleType { - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, -} - -impl RuleType { - pub fn builder() -> RuleTypeBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeBuilder { - id: Option, - name: Option, - description: Option, -} - -impl RuleTypeBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn description(mut self, value: impl Into) -> Self { - self.description = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`RuleType`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](RuleTypeBuilder::id) - /// - [`name`](RuleTypeBuilder::name) - pub fn build(self) -> Result { - Ok(RuleType { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - description: self.description, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/rule_type_search_response.rs b/seed/rust-model/allof-inline/src/rule_type_search_response.rs deleted file mode 100644 index d56fa0fa7a8d..000000000000 --- a/seed/rust-model/allof-inline/src/rule_type_search_response.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleTypeSearchResponse { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, -} - -impl RuleTypeSearchResponse { - pub fn builder() -> RuleTypeSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeSearchResponseBuilder { - paging: Option, - results: Option>, -} - -impl RuleTypeSearchResponseBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](RuleTypeSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(RuleTypeSearchResponse { - paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, - results: self.results, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/search_rule_types_query_request.rs b/seed/rust-model/allof-inline/src/search_rule_types_query_request.rs deleted file mode 100644 index ae31f5e24826..000000000000 --- a/seed/rust-model/allof-inline/src/search_rule_types_query_request.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub use crate::prelude::*; - -/// Query parameters for searchRuleTypes -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct SearchRuleTypesQueryRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub query: Option, -} - -impl SearchRuleTypesQueryRequest { - pub fn builder() -> SearchRuleTypesQueryRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct SearchRuleTypesQueryRequestBuilder { - query: Option, -} - -impl SearchRuleTypesQueryRequestBuilder { - pub fn query(mut self, value: impl Into) -> Self { - self.query = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. - pub fn build(self) -> Result { - Ok(SearchRuleTypesQueryRequest { - query: self.query, - }) - } -} - diff --git a/seed/rust-model/allof-inline/src/user.rs b/seed/rust-model/allof-inline/src/user.rs deleted file mode 100644 index 347520d397a2..000000000000 --- a/seed/rust-model/allof-inline/src/user.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct User { - #[serde(default)] - pub id: String, - #[serde(default)] - pub email: String, -} - -impl User { - pub fn builder() -> UserBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserBuilder { - id: Option, - email: Option, -} - -impl UserBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn email(mut self, value: impl Into) -> Self { - self.email = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`User`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](UserBuilder::id) - /// - [`email`](UserBuilder::email) - pub fn build(self) -> Result { - Ok(User { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - email: self.email.ok_or_else(|| BuildError::missing_field("email"))?, - }) - } -} diff --git a/seed/rust-model/allof-inline/src/user_search_response.rs b/seed/rust-model/allof-inline/src/user_search_response.rs deleted file mode 100644 index 599feca7ab64..000000000000 --- a/seed/rust-model/allof-inline/src/user_search_response.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct UserSearchResponse { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, -} - -impl UserSearchResponse { - pub fn builder() -> UserSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserSearchResponseBuilder { - paging: Option, - results: Option>, -} - -impl UserSearchResponseBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`UserSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](UserSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(UserSearchResponse { - paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, - results: self.results, - }) - } -} diff --git a/seed/rust-model/allof/.fern/metadata.json b/seed/rust-model/allof/.fern/metadata.json deleted file mode 100644 index a9c2c174fe54..000000000000 --- a/seed/rust-model/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-rust-model", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/rust-model/allof/snippet.json b/seed/rust-model/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/rust-model/allof/src/audit_info.rs b/seed/rust-model/allof/src/audit_info.rs deleted file mode 100644 index a958df434a50..000000000000 --- a/seed/rust-model/allof/src/audit_info.rs +++ /dev/null @@ -1,73 +0,0 @@ -pub use crate::prelude::*; - -/// Common audit metadata. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct AuditInfo { - /// The user who created this resource. - #[serde(rename = "createdBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub created_by: Option, - /// When this resource was created. - #[serde(rename = "createdDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub created_date_time: Option>, - /// The user who last modified this resource. - #[serde(rename = "modifiedBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub modified_by: Option, - /// When this resource was last modified. - #[serde(rename = "modifiedDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub modified_date_time: Option>, -} - -impl AuditInfo { - pub fn builder() -> AuditInfoBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct AuditInfoBuilder { - created_by: Option, - created_date_time: Option>, - modified_by: Option, - modified_date_time: Option>, -} - -impl AuditInfoBuilder { - pub fn created_by(mut self, value: impl Into) -> Self { - self.created_by = Some(value.into()); - self - } - - pub fn created_date_time(mut self, value: DateTime) -> Self { - self.created_date_time = Some(value); - self - } - - pub fn modified_by(mut self, value: impl Into) -> Self { - self.modified_by = Some(value.into()); - self - } - - pub fn modified_date_time(mut self, value: DateTime) -> Self { - self.modified_date_time = Some(value); - self - } - - /// Consumes the builder and constructs a [`AuditInfo`]. - pub fn build(self) -> Result { - Ok(AuditInfo { - created_by: self.created_by, - created_date_time: self.created_date_time, - modified_by: self.modified_by, - modified_date_time: self.modified_date_time, - }) - } -} diff --git a/seed/rust-model/allof/src/base_org.rs b/seed/rust-model/allof/src/base_org.rs deleted file mode 100644 index 1747ce8ded53..000000000000 --- a/seed/rust-model/allof/src/base_org.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrg { - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl BaseOrg { - pub fn builder() -> BaseOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgBuilder { - id: Option, - metadata: Option, -} - -impl BaseOrgBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`BaseOrg`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](BaseOrgBuilder::id) - pub fn build(self) -> Result { - Ok(BaseOrg { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-model/allof/src/base_org_metadata.rs b/seed/rust-model/allof/src/base_org_metadata.rs deleted file mode 100644 index 797f4434eb49..000000000000 --- a/seed/rust-model/allof/src/base_org_metadata.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrgMetadata { - /// Deployment region from BaseOrg. - #[serde(default)] - pub region: String, - /// Subscription tier. - #[serde(skip_serializing_if = "Option::is_none")] - pub tier: Option, -} - -impl BaseOrgMetadata { - pub fn builder() -> BaseOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgMetadataBuilder { - region: Option, - tier: Option, -} - -impl BaseOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn tier(mut self, value: impl Into) -> Self { - self.tier = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`BaseOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](BaseOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(BaseOrgMetadata { - region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, - tier: self.tier, - }) - } -} diff --git a/seed/rust-model/allof/src/combined_entity.rs b/seed/rust-model/allof/src/combined_entity.rs deleted file mode 100644 index 0b567ccb3ee5..000000000000 --- a/seed/rust-model/allof/src/combined_entity.rs +++ /dev/null @@ -1,65 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct CombinedEntity { - pub status: CombinedEntityStatus, - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Identifiable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, -} - -impl CombinedEntity { - pub fn builder() -> CombinedEntityBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct CombinedEntityBuilder { - status: Option, - id: Option, - name: Option, - summary: Option, -} - -impl CombinedEntityBuilder { - pub fn status(mut self, value: CombinedEntityStatus) -> Self { - self.status = Some(value); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`CombinedEntity`]. - /// This method will fail if any of the following fields are not set: - /// - [`status`](CombinedEntityBuilder::status) - /// - [`id`](CombinedEntityBuilder::id) - pub fn build(self) -> Result { - Ok(CombinedEntity { - status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - summary: self.summary, - }) - } -} diff --git a/seed/rust-model/allof/src/combined_entity_status.rs b/seed/rust-model/allof/src/combined_entity_status.rs deleted file mode 100644 index f46cb23eecb2..000000000000 --- a/seed/rust-model/allof/src/combined_entity_status.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum CombinedEntityStatus { - Active, - Archived, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for CombinedEntityStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Archived => serializer.serialize_str("archived"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for CombinedEntityStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "archived" => Ok(Self::Archived), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for CombinedEntityStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Archived => write!(f, "archived"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-model/allof/src/describable.rs b/seed/rust-model/allof/src/describable.rs deleted file mode 100644 index 6a8a527bd844..000000000000 --- a/seed/rust-model/allof/src/describable.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Describable { - /// Display name from Describable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, -} - -impl Describable { - pub fn builder() -> DescribableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DescribableBuilder { - name: Option, - summary: Option, -} - -impl DescribableBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Describable`]. - pub fn build(self) -> Result { - Ok(Describable { - name: self.name, - summary: self.summary, - }) - } -} diff --git a/seed/rust-model/allof/src/detailed_org.rs b/seed/rust-model/allof/src/detailed_org.rs deleted file mode 100644 index fac068d8a2ee..000000000000 --- a/seed/rust-model/allof/src/detailed_org.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrg { - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl DetailedOrg { - pub fn builder() -> DetailedOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgBuilder { - metadata: Option, -} - -impl DetailedOrgBuilder { - pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`DetailedOrg`]. - pub fn build(self) -> Result { - Ok(DetailedOrg { - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-model/allof/src/detailed_org_metadata.rs b/seed/rust-model/allof/src/detailed_org_metadata.rs deleted file mode 100644 index 06ec63051131..000000000000 --- a/seed/rust-model/allof/src/detailed_org_metadata.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrgMetadata { - /// Deployment region from DetailedOrg. - #[serde(default)] - pub region: String, - /// Custom domain name. - #[serde(skip_serializing_if = "Option::is_none")] - pub domain: Option, -} - -impl DetailedOrgMetadata { - pub fn builder() -> DetailedOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgMetadataBuilder { - region: Option, - domain: Option, -} - -impl DetailedOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn domain(mut self, value: impl Into) -> Self { - self.domain = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](DetailedOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(DetailedOrgMetadata { - region: self.region.ok_or_else(|| BuildError::missing_field("region"))?, - domain: self.domain, - }) - } -} diff --git a/seed/rust-model/allof/src/error.rs b/seed/rust-model/allof/src/error.rs deleted file mode 100644 index 0966ed354a0f..000000000000 --- a/seed/rust-model/allof/src/error.rs +++ /dev/null @@ -1,19 +0,0 @@ -/// Error returned when a required field was not set on a builder. -#[derive(Debug)] -pub struct BuildError { - field: &'static str, -} - -impl BuildError { - pub fn missing_field(field: &'static str) -> Self { - Self { field } - } -} - -impl std::fmt::Display for BuildError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "`{}` was not set but is required", self.field) - } -} - -impl std::error::Error for BuildError {} diff --git a/seed/rust-model/allof/src/identifiable.rs b/seed/rust-model/allof/src/identifiable.rs deleted file mode 100644 index 58fb3eb70734..000000000000 --- a/seed/rust-model/allof/src/identifiable.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Identifiable { - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Identifiable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, -} - -impl Identifiable { - pub fn builder() -> IdentifiableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct IdentifiableBuilder { - id: Option, - name: Option, -} - -impl IdentifiableBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Identifiable`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](IdentifiableBuilder::id) - pub fn build(self) -> Result { - Ok(Identifiable { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - }) - } -} diff --git a/seed/rust-model/allof/src/lib.rs b/seed/rust-model/allof/src/lib.rs deleted file mode 100644 index 077ee6dee849..000000000000 --- a/seed/rust-model/allof/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Generated models by Fern - -pub mod error; - -pub mod types; - -pub use types::*; diff --git a/seed/rust-model/allof/src/mod.rs b/seed/rust-model/allof/src/mod.rs deleted file mode 100644 index 564b9380a75b..000000000000 --- a/seed/rust-model/allof/src/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Request and response types for the allOf Composition -//! -//! This module contains all data structures used for API communication, -//! including request bodies, response types, and shared models. -//! -//! ## Type Categories -//! -//! - **Request/Response Types**: 6 types for API operations -//! - **Model Types**: 15 types for data representation - -pub mod paginated_result; -pub mod paging_cursors; -pub mod rule_execution_context; -pub mod audit_info; -pub mod rule_type; -pub mod rule_type_search_response; -pub mod user; -pub mod user_search_response; -pub mod rule_response_status; -pub mod rule_response; -pub mod identifiable; -pub mod describable; -pub mod combined_entity_status; -pub mod combined_entity; -pub mod base_org_metadata; -pub mod base_org; -pub mod detailed_org_metadata; -pub mod detailed_org; -pub mod organization; -pub mod rule_create_request; -pub mod search_rule_types_query_request; - -pub use paginated_result::PaginatedResult; -pub use paging_cursors::PagingCursors; -pub use rule_execution_context::RuleExecutionContext; -pub use audit_info::AuditInfo; -pub use rule_type::RuleType; -pub use rule_type_search_response::RuleTypeSearchResponse; -pub use user::User; -pub use user_search_response::UserSearchResponse; -pub use rule_response_status::RuleResponseStatus; -pub use rule_response::RuleResponse; -pub use identifiable::Identifiable; -pub use describable::Describable; -pub use combined_entity_status::CombinedEntityStatus; -pub use combined_entity::CombinedEntity; -pub use base_org_metadata::BaseOrgMetadata; -pub use base_org::BaseOrg; -pub use detailed_org_metadata::DetailedOrgMetadata; -pub use detailed_org::DetailedOrg; -pub use organization::Organization; -pub use rule_create_request::RuleCreateRequest; -pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; - diff --git a/seed/rust-model/allof/src/organization.rs b/seed/rust-model/allof/src/organization.rs deleted file mode 100644 index ec2ed3f22d66..000000000000 --- a/seed/rust-model/allof/src/organization.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Organization { - #[serde(default)] - pub name: String, - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl Organization { - pub fn builder() -> OrganizationBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct OrganizationBuilder { - name: Option, - id: Option, - metadata: Option, -} - -impl OrganizationBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`Organization`]. - /// This method will fail if any of the following fields are not set: - /// - [`name`](OrganizationBuilder::name) - /// - [`id`](OrganizationBuilder::id) - pub fn build(self) -> Result { - Ok(Organization { - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-model/allof/src/paginated_result.rs b/seed/rust-model/allof/src/paginated_result.rs deleted file mode 100644 index d91039ad7253..000000000000 --- a/seed/rust-model/allof/src/paginated_result.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] -pub struct PaginatedResult { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(default)] - pub results: Vec, -} - -impl PaginatedResult { - pub fn builder() -> PaginatedResultBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PaginatedResultBuilder { - paging: Option, - results: Option>, -} - -impl PaginatedResultBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`PaginatedResult`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](PaginatedResultBuilder::paging) - /// - [`results`](PaginatedResultBuilder::results) - pub fn build(self) -> Result { - Ok(PaginatedResult { - paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, - results: self.results.ok_or_else(|| BuildError::missing_field("results"))?, - }) - } -} diff --git a/seed/rust-model/allof/src/paging_cursors.rs b/seed/rust-model/allof/src/paging_cursors.rs deleted file mode 100644 index a3a785119b57..000000000000 --- a/seed/rust-model/allof/src/paging_cursors.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct PagingCursors { - /// Cursor for the next page of results. - #[serde(default)] - pub next: String, - /// Cursor for the previous page of results. - #[serde(skip_serializing_if = "Option::is_none")] - pub previous: Option, -} - -impl PagingCursors { - pub fn builder() -> PagingCursorsBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PagingCursorsBuilder { - next: Option, - previous: Option, -} - -impl PagingCursorsBuilder { - pub fn next(mut self, value: impl Into) -> Self { - self.next = Some(value.into()); - self - } - - pub fn previous(mut self, value: impl Into) -> Self { - self.previous = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`PagingCursors`]. - /// This method will fail if any of the following fields are not set: - /// - [`next`](PagingCursorsBuilder::next) - pub fn build(self) -> Result { - Ok(PagingCursors { - next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, - previous: self.previous, - }) - } -} diff --git a/seed/rust-model/allof/src/rule_create_request.rs b/seed/rust-model/allof/src/rule_create_request.rs deleted file mode 100644 index 3401318222ee..000000000000 --- a/seed/rust-model/allof/src/rule_create_request.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleCreateRequest { - #[serde(default)] - pub name: String, - #[serde(rename = "executionContext")] - pub execution_context: RuleExecutionContext, -} - -impl RuleCreateRequest { - pub fn builder() -> RuleCreateRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleCreateRequestBuilder { - name: Option, - execution_context: Option, -} - -impl RuleCreateRequestBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleCreateRequest`]. - /// This method will fail if any of the following fields are not set: - /// - [`name`](RuleCreateRequestBuilder::name) - /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) - pub fn build(self) -> Result { - Ok(RuleCreateRequest { - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - execution_context: self.execution_context.ok_or_else(|| BuildError::missing_field("execution_context"))?, - }) - } -} - diff --git a/seed/rust-model/allof/src/rule_execution_context.rs b/seed/rust-model/allof/src/rule_execution_context.rs deleted file mode 100644 index 7f8fa5bd56e1..000000000000 --- a/seed/rust-model/allof/src/rule_execution_context.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -/// Execution environment for a rule. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleExecutionContext { - Prod, - Staging, - Dev, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleExecutionContext { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Prod => serializer.serialize_str("prod"), - Self::Staging => serializer.serialize_str("staging"), - Self::Dev => serializer.serialize_str("dev"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleExecutionContext { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "prod" => Ok(Self::Prod), - "staging" => Ok(Self::Staging), - "dev" => Ok(Self::Dev), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleExecutionContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Prod => write!(f, "prod"), - Self::Staging => write!(f, "staging"), - Self::Dev => write!(f, "dev"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-model/allof/src/rule_response.rs b/seed/rust-model/allof/src/rule_response.rs deleted file mode 100644 index 53159e627a3f..000000000000 --- a/seed/rust-model/allof/src/rule_response.rs +++ /dev/null @@ -1,74 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleResponse { - #[serde(flatten)] - pub audit_info_fields: AuditInfo, - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - pub status: RuleResponseStatus, - #[serde(rename = "executionContext")] - #[serde(skip_serializing_if = "Option::is_none")] - pub execution_context: Option, -} - -impl RuleResponse { - pub fn builder() -> RuleResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleResponseBuilder { - audit_info_fields: Option, - id: Option, - name: Option, - status: Option, - execution_context: Option, -} - -impl RuleResponseBuilder { - pub fn audit_info_fields(mut self, value: AuditInfo) -> Self { - self.audit_info_fields = Some(value); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn status(mut self, value: RuleResponseStatus) -> Self { - self.status = Some(value); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`audit_info_fields`](RuleResponseBuilder::audit_info_fields) - /// - [`id`](RuleResponseBuilder::id) - /// - [`name`](RuleResponseBuilder::name) - /// - [`status`](RuleResponseBuilder::status) - pub fn build(self) -> Result { - Ok(RuleResponse { - audit_info_fields: self.audit_info_fields.ok_or_else(|| BuildError::missing_field("audit_info_fields"))?, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - status: self.status.ok_or_else(|| BuildError::missing_field("status"))?, - execution_context: self.execution_context, - }) - } -} diff --git a/seed/rust-model/allof/src/rule_response_status.rs b/seed/rust-model/allof/src/rule_response_status.rs deleted file mode 100644 index 5d2462ecd349..000000000000 --- a/seed/rust-model/allof/src/rule_response_status.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleResponseStatus { - Active, - Inactive, - Draft, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleResponseStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Inactive => serializer.serialize_str("inactive"), - Self::Draft => serializer.serialize_str("draft"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleResponseStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "inactive" => Ok(Self::Inactive), - "draft" => Ok(Self::Draft), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleResponseStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Inactive => write!(f, "inactive"), - Self::Draft => write!(f, "draft"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-model/allof/src/rule_type.rs b/seed/rust-model/allof/src/rule_type.rs deleted file mode 100644 index 69f0317c1923..000000000000 --- a/seed/rust-model/allof/src/rule_type.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleType { - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, -} - -impl RuleType { - pub fn builder() -> RuleTypeBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeBuilder { - id: Option, - name: Option, - description: Option, -} - -impl RuleTypeBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn description(mut self, value: impl Into) -> Self { - self.description = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`RuleType`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](RuleTypeBuilder::id) - /// - [`name`](RuleTypeBuilder::name) - pub fn build(self) -> Result { - Ok(RuleType { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - description: self.description, - }) - } -} diff --git a/seed/rust-model/allof/src/rule_type_search_response.rs b/seed/rust-model/allof/src/rule_type_search_response.rs deleted file mode 100644 index 9d9cd3e185c3..000000000000 --- a/seed/rust-model/allof/src/rule_type_search_response.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleTypeSearchResponse { - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, - #[serde(default)] - pub paging: PagingCursors, -} - -impl RuleTypeSearchResponse { - pub fn builder() -> RuleTypeSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeSearchResponseBuilder { - results: Option>, - paging: Option, -} - -impl RuleTypeSearchResponseBuilder { - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](RuleTypeSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(RuleTypeSearchResponse { - results: self.results, - paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, - }) - } -} diff --git a/seed/rust-model/allof/src/search_rule_types_query_request.rs b/seed/rust-model/allof/src/search_rule_types_query_request.rs deleted file mode 100644 index ae31f5e24826..000000000000 --- a/seed/rust-model/allof/src/search_rule_types_query_request.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub use crate::prelude::*; - -/// Query parameters for searchRuleTypes -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct SearchRuleTypesQueryRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub query: Option, -} - -impl SearchRuleTypesQueryRequest { - pub fn builder() -> SearchRuleTypesQueryRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct SearchRuleTypesQueryRequestBuilder { - query: Option, -} - -impl SearchRuleTypesQueryRequestBuilder { - pub fn query(mut self, value: impl Into) -> Self { - self.query = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. - pub fn build(self) -> Result { - Ok(SearchRuleTypesQueryRequest { - query: self.query, - }) - } -} - diff --git a/seed/rust-model/allof/src/user.rs b/seed/rust-model/allof/src/user.rs deleted file mode 100644 index 347520d397a2..000000000000 --- a/seed/rust-model/allof/src/user.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct User { - #[serde(default)] - pub id: String, - #[serde(default)] - pub email: String, -} - -impl User { - pub fn builder() -> UserBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserBuilder { - id: Option, - email: Option, -} - -impl UserBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn email(mut self, value: impl Into) -> Self { - self.email = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`User`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](UserBuilder::id) - /// - [`email`](UserBuilder::email) - pub fn build(self) -> Result { - Ok(User { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - email: self.email.ok_or_else(|| BuildError::missing_field("email"))?, - }) - } -} diff --git a/seed/rust-model/allof/src/user_search_response.rs b/seed/rust-model/allof/src/user_search_response.rs deleted file mode 100644 index 820735acecb4..000000000000 --- a/seed/rust-model/allof/src/user_search_response.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct UserSearchResponse { - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, - #[serde(default)] - pub paging: PagingCursors, -} - -impl UserSearchResponse { - pub fn builder() -> UserSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserSearchResponseBuilder { - results: Option>, - paging: Option, -} - -impl UserSearchResponseBuilder { - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - /// Consumes the builder and constructs a [`UserSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](UserSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(UserSearchResponse { - results: self.results, - paging: self.paging.ok_or_else(|| BuildError::missing_field("paging"))?, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/.fern/metadata.json b/seed/rust-sdk/allof-inline/.fern/metadata.json deleted file mode 100644 index 8cc2145de5c0..000000000000 --- a/seed/rust-sdk/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-rust-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/rust-sdk/allof-inline/.github/workflows/ci.yml b/seed/rust-sdk/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 4e582a9fa4e3..000000000000 --- a/seed/rust-sdk/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -env: - RUSTFLAGS: "-A warnings" - -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Check - run: cargo check - - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Compile - run: cargo build - - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Test - run: cargo test - - publish: - needs: [check, compile, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Publish - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish diff --git a/seed/rust-sdk/allof-inline/.gitignore b/seed/rust-sdk/allof-inline/.gitignore deleted file mode 100644 index f75d27799da1..000000000000 --- a/seed/rust-sdk/allof-inline/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/target -**/*.rs.bk -Cargo.lock -.DS_Store -*.swp \ No newline at end of file diff --git a/seed/rust-sdk/allof-inline/Cargo.toml b/seed/rust-sdk/allof-inline/Cargo.toml deleted file mode 100644 index ce5837604a74..000000000000 --- a/seed/rust-sdk/allof-inline/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "seed_api" -version = "0.0.1" -edition = "2021" -description = "Rust SDK for seed_api generated by Fern" -license = "MIT" -repository = "https://github.com/fern-api/fern" -documentation = "https://docs.rs/seed_api" - -[lib] -doctest = false - -[dependencies] -bytes = "1.0" -chrono = { version = "0.4", features = ["serde"] } -futures = "0.3" -reqwest = { version = "0.12", features = ["json", "stream"], default-features = false } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" -tokio = { version = "1.0", features = ["full"] } - -[dev-dependencies] -tokio-test = "0.4" - diff --git a/seed/rust-sdk/allof-inline/README.md b/seed/rust-sdk/allof-inline/README.md deleted file mode 100644 index 73c2a4769236..000000000000 --- a/seed/rust-sdk/allof-inline/README.md +++ /dev/null @@ -1,181 +0,0 @@ -# Seed Rust Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRust) -[![crates.io shield](https://img.shields.io/crates/v/seed_api)](https://crates.io/crates/seed_api) - -The Seed Rust library provides convenient access to the Seed APIs from Rust. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Request Types](#request-types) -- [Advanced](#advanced) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) -- [Contributing](#contributing) - -## Installation - -Add this to your `Cargo.toml`: - -```toml -[dependencies] -seed_api = "0.0.1" -``` - -Or install via cargo: - -```sh -cargo add seed_api -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```rust -use seed_api::prelude::{*}; - -let config = ClientConfig { - base_url: Environment::Default.url().to_string(), - ..Default::default() -}; -let client = Client::new(config).expect("Failed to build client"); -``` - -## Errors - -When the API returns a non-success status code (4xx or 5xx response), an error will be returned. - -```rust -match client.create_rule(None)?.await { - Ok(response) => { - println!("Success: {:?}", response); - }, - Err(ApiError::HTTP { status, message }) => { - println!("API Error {}: {:?}", status, message); - }, - Err(e) => { - println!("Other error: {:?}", e); - } -} -``` - -## Request Types - -The SDK exports all request types as Rust structs. Simply import them from the crate to access them: - -```rust -use seed_api::prelude::{*}; - -let request = RuleCreateRequest { - ... -}; -``` - -## Advanced - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `max_retries` method to configure this behavior. - -```rust -let response = client.create_rule( - Some(RequestOptions::new().max_retries(3)) -)?.await; -``` - -### Timeouts - -The SDK defaults to a 30 second timeout. Use the `timeout` method to configure this behavior. - -```rust -let response = client.create_rule( - Some(RequestOptions::new().timeout_seconds(30)) -)?.await; -``` - -### Additional Headers - -You can add custom headers to requests using `RequestOptions`. - -```rust -let response = client.create_rule( - Some( - RequestOptions::new() - .additional_header("X-Custom-Header", "custom-value") - .additional_header("X-Another-Header", "another-value") - ) -)? -.await; -``` - -### Additional Query String Parameters - -You can add custom query parameters to requests using `RequestOptions`. - -```rust -let response = client.create_rule( - Some( - RequestOptions::new() - .additional_query_param("filter", "active") - .additional_query_param("sort", "desc") - ) -)? -.await; -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs deleted file mode 100644 index 6968951d8813..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example0.rs +++ /dev/null @@ -1,18 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .search_rule_types( - &SearchRuleTypesQueryRequest { - ..Default::default() - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs deleted file mode 100644 index cf7e959c8d85..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example1.rs +++ /dev/null @@ -1,19 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .search_rule_types( - &SearchRuleTypesQueryRequest { - query: Some("query".to_string()), - ..Default::default() - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs deleted file mode 100644 index db52ba2800ca..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example2.rs +++ /dev/null @@ -1,19 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs deleted file mode 100644 index db52ba2800ca..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example3.rs +++ /dev/null @@ -1,19 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs deleted file mode 100644 index 5afd9b2dd1e9..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example4.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.list_users(None).await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs deleted file mode 100644 index 5afd9b2dd1e9..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example5.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.list_users(None).await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs deleted file mode 100644 index 0f9ad4c50ad6..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example6.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_entity(None).await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs deleted file mode 100644 index 0f9ad4c50ad6..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example7.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_entity(None).await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs deleted file mode 100644 index 9025e9975095..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example8.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_organization(None).await; -} diff --git a/seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs b/seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs deleted file mode 100644 index 9025e9975095..000000000000 --- a/seed/rust-sdk/allof-inline/dynamic-snippets/example9.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_organization(None).await; -} diff --git a/seed/rust-sdk/allof-inline/reference.md b/seed/rust-sdk/allof-inline/reference.md deleted file mode 100644 index 482a8295a3fb..000000000000 --- a/seed/rust-sdk/allof-inline/reference.md +++ /dev/null @@ -1,224 +0,0 @@ -# Reference -
client.search_rule_types(query: Option<Option<String>>) -> Result<RuleTypeSearchResponse, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .search_rule_types( - &SearchRuleTypesQueryRequest { - ..Default::default() - }, - None, - ) - .await; -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `Option` - -
-
-
-
- - -
-
-
- -
client.create_rule(request: RuleCreateRequest) -> Result<RuleResponse, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `String` - -
-
- -
-
- -**execution_context:** `RuleExecutionContext` - -
-
-
-
- - -
-
-
- -
client.list_users() -> Result<UserSearchResponse, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.list_users(None).await; -} -``` -
-
-
-
- - -
-
-
- -
client.get_entity() -> Result<CombinedEntity, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_entity(None).await; -} -``` -
-
-
-
- - -
-
-
- -
client.get_organization() -> Result<Organization, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_organization(None).await; -} -``` -
-
-
-
- - -
-
-
- diff --git a/seed/rust-sdk/allof-inline/rustfmt.toml b/seed/rust-sdk/allof-inline/rustfmt.toml deleted file mode 100644 index 872221fb31fe..000000000000 --- a/seed/rust-sdk/allof-inline/rustfmt.toml +++ /dev/null @@ -1,4 +0,0 @@ -# Generated by Fern -edition = "2021" -max_width = 100 -use_small_heuristics = "Default" \ No newline at end of file diff --git a/seed/rust-sdk/allof-inline/snippet.json b/seed/rust-sdk/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/rust-sdk/allof-inline/src/api/mod.rs b/seed/rust-sdk/allof-inline/src/api/mod.rs deleted file mode 100644 index 83cb9e2c92d4..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! API client and types for the allOf Composition -//! -//! This module contains all the API definitions including request/response types -//! and client implementations for interacting with the API. -//! -//! ## Modules -//! -//! - [`resources`] - Service clients and endpoints -//! - [`types`] - Request, response, and model types - -pub mod resources; -pub mod types; - -pub use resources::ApiClient; -pub use types::*; diff --git a/seed/rust-sdk/allof-inline/src/api/resources/mod.rs b/seed/rust-sdk/allof-inline/src/api/resources/mod.rs deleted file mode 100644 index 543ca7436f76..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/resources/mod.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! Service clients and API endpoints -//! -//! This module provides the client implementations for all available services. - -use crate::api::*; -use crate::{ApiError, ClientConfig, HttpClient, RequestOptions}; -use reqwest::Method; - -pub struct ApiClient { - pub config: ClientConfig, - pub http_client: HttpClient, -} - -impl ApiClient { - pub fn new(config: ClientConfig) -> Result { - Ok(Self { - config: config.clone(), - http_client: HttpClient::new(config.clone())?, - }) - } - - pub async fn search_rule_types( - &self, - request: &SearchRuleTypesQueryRequest, - options: Option, - ) -> Result { - self.http_client - .execute_request( - Method::GET, - "rule-types", - None, - QueryBuilder::new() - .structured_query("query", request.query.clone()) - .build(), - options, - ) - .await - } - - pub async fn create_rule( - &self, - request: &RuleCreateRequest, - options: Option, - ) -> Result { - self.http_client - .execute_request( - Method::POST, - "rules", - Some(serde_json::to_value(request).map_err(ApiError::Serialization)?), - None, - options, - ) - .await - } - - pub async fn list_users( - &self, - options: Option, - ) -> Result { - self.http_client - .execute_request(Method::GET, "users", None, None, options) - .await - } - - pub async fn get_entity( - &self, - options: Option, - ) -> Result { - self.http_client - .execute_request(Method::GET, "entities", None, None, options) - .await - } - - pub async fn get_organization( - &self, - options: Option, - ) -> Result { - self.http_client - .execute_request(Method::GET, "organizations", None, None, options) - .await - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/audit_info.rs b/seed/rust-sdk/allof-inline/src/api/types/audit_info.rs deleted file mode 100644 index a958df434a50..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/audit_info.rs +++ /dev/null @@ -1,73 +0,0 @@ -pub use crate::prelude::*; - -/// Common audit metadata. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct AuditInfo { - /// The user who created this resource. - #[serde(rename = "createdBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub created_by: Option, - /// When this resource was created. - #[serde(rename = "createdDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub created_date_time: Option>, - /// The user who last modified this resource. - #[serde(rename = "modifiedBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub modified_by: Option, - /// When this resource was last modified. - #[serde(rename = "modifiedDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub modified_date_time: Option>, -} - -impl AuditInfo { - pub fn builder() -> AuditInfoBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct AuditInfoBuilder { - created_by: Option, - created_date_time: Option>, - modified_by: Option, - modified_date_time: Option>, -} - -impl AuditInfoBuilder { - pub fn created_by(mut self, value: impl Into) -> Self { - self.created_by = Some(value.into()); - self - } - - pub fn created_date_time(mut self, value: DateTime) -> Self { - self.created_date_time = Some(value); - self - } - - pub fn modified_by(mut self, value: impl Into) -> Self { - self.modified_by = Some(value.into()); - self - } - - pub fn modified_date_time(mut self, value: DateTime) -> Self { - self.modified_date_time = Some(value); - self - } - - /// Consumes the builder and constructs a [`AuditInfo`]. - pub fn build(self) -> Result { - Ok(AuditInfo { - created_by: self.created_by, - created_date_time: self.created_date_time, - modified_by: self.modified_by, - modified_date_time: self.modified_date_time, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/base_org.rs b/seed/rust-sdk/allof-inline/src/api/types/base_org.rs deleted file mode 100644 index 1747ce8ded53..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/base_org.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrg { - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl BaseOrg { - pub fn builder() -> BaseOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgBuilder { - id: Option, - metadata: Option, -} - -impl BaseOrgBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`BaseOrg`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](BaseOrgBuilder::id) - pub fn build(self) -> Result { - Ok(BaseOrg { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs b/seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs deleted file mode 100644 index d1182fd85504..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/base_org_metadata.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrgMetadata { - /// Deployment region from BaseOrg. - #[serde(default)] - pub region: String, - /// Subscription tier. - #[serde(skip_serializing_if = "Option::is_none")] - pub tier: Option, -} - -impl BaseOrgMetadata { - pub fn builder() -> BaseOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgMetadataBuilder { - region: Option, - tier: Option, -} - -impl BaseOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn tier(mut self, value: impl Into) -> Self { - self.tier = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`BaseOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](BaseOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(BaseOrgMetadata { - region: self - .region - .ok_or_else(|| BuildError::missing_field("region"))?, - tier: self.tier, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs b/seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs deleted file mode 100644 index fb6b31b1293e..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/combined_entity.rs +++ /dev/null @@ -1,67 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct CombinedEntity { - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Describable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, - pub status: CombinedEntityStatus, -} - -impl CombinedEntity { - pub fn builder() -> CombinedEntityBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct CombinedEntityBuilder { - id: Option, - name: Option, - summary: Option, - status: Option, -} - -impl CombinedEntityBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - pub fn status(mut self, value: CombinedEntityStatus) -> Self { - self.status = Some(value); - self - } - - /// Consumes the builder and constructs a [`CombinedEntity`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](CombinedEntityBuilder::id) - /// - [`status`](CombinedEntityBuilder::status) - pub fn build(self) -> Result { - Ok(CombinedEntity { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - summary: self.summary, - status: self - .status - .ok_or_else(|| BuildError::missing_field("status"))?, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs b/seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs deleted file mode 100644 index f46cb23eecb2..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/combined_entity_status.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum CombinedEntityStatus { - Active, - Archived, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for CombinedEntityStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Archived => serializer.serialize_str("archived"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for CombinedEntityStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "archived" => Ok(Self::Archived), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for CombinedEntityStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Archived => write!(f, "archived"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/describable.rs b/seed/rust-sdk/allof-inline/src/api/types/describable.rs deleted file mode 100644 index 6a8a527bd844..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/describable.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Describable { - /// Display name from Describable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, -} - -impl Describable { - pub fn builder() -> DescribableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DescribableBuilder { - name: Option, - summary: Option, -} - -impl DescribableBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Describable`]. - pub fn build(self) -> Result { - Ok(Describable { - name: self.name, - summary: self.summary, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs b/seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs deleted file mode 100644 index fac068d8a2ee..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/detailed_org.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrg { - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl DetailedOrg { - pub fn builder() -> DetailedOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgBuilder { - metadata: Option, -} - -impl DetailedOrgBuilder { - pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`DetailedOrg`]. - pub fn build(self) -> Result { - Ok(DetailedOrg { - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs b/seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs deleted file mode 100644 index bbbeda49c417..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/detailed_org_metadata.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrgMetadata { - /// Deployment region from DetailedOrg. - #[serde(default)] - pub region: String, - /// Custom domain name. - #[serde(skip_serializing_if = "Option::is_none")] - pub domain: Option, -} - -impl DetailedOrgMetadata { - pub fn builder() -> DetailedOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgMetadataBuilder { - region: Option, - domain: Option, -} - -impl DetailedOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn domain(mut self, value: impl Into) -> Self { - self.domain = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](DetailedOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(DetailedOrgMetadata { - region: self - .region - .ok_or_else(|| BuildError::missing_field("region"))?, - domain: self.domain, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/identifiable.rs b/seed/rust-sdk/allof-inline/src/api/types/identifiable.rs deleted file mode 100644 index 58fb3eb70734..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/identifiable.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Identifiable { - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Identifiable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, -} - -impl Identifiable { - pub fn builder() -> IdentifiableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct IdentifiableBuilder { - id: Option, - name: Option, -} - -impl IdentifiableBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Identifiable`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](IdentifiableBuilder::id) - pub fn build(self) -> Result { - Ok(Identifiable { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/mod.rs b/seed/rust-sdk/allof-inline/src/api/types/mod.rs deleted file mode 100644 index a61bbc01e128..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub mod audit_info; -pub mod base_org; -pub mod base_org_metadata; -pub mod combined_entity; -pub mod combined_entity_status; -pub mod describable; -pub mod detailed_org; -pub mod detailed_org_metadata; -pub mod identifiable; -pub mod organization; -pub mod organization_metadata; -pub mod paginated_result; -pub mod paging_cursors; -pub mod rule_create_request; -pub mod rule_execution_context; -pub mod rule_response; -pub mod rule_response_status; -pub mod rule_type; -pub mod rule_type_search_response; -pub mod search_rule_types_query_request; -pub mod user; -pub mod user_search_response; - -pub use audit_info::AuditInfo; -pub use base_org::BaseOrg; -pub use base_org_metadata::BaseOrgMetadata; -pub use combined_entity::CombinedEntity; -pub use combined_entity_status::CombinedEntityStatus; -pub use describable::Describable; -pub use detailed_org::DetailedOrg; -pub use detailed_org_metadata::DetailedOrgMetadata; -pub use identifiable::Identifiable; -pub use organization::Organization; -pub use organization_metadata::OrganizationMetadata; -pub use paginated_result::PaginatedResult; -pub use paging_cursors::PagingCursors; -pub use rule_create_request::RuleCreateRequest; -pub use rule_execution_context::RuleExecutionContext; -pub use rule_response::RuleResponse; -pub use rule_response_status::RuleResponseStatus; -pub use rule_type::RuleType; -pub use rule_type_search_response::RuleTypeSearchResponse; -pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; -pub use user::User; -pub use user_search_response::UserSearchResponse; diff --git a/seed/rust-sdk/allof-inline/src/api/types/organization.rs b/seed/rust-sdk/allof-inline/src/api/types/organization.rs deleted file mode 100644 index 3f727a6b8212..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/organization.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Organization { - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - #[serde(default)] - pub name: String, -} - -impl Organization { - pub fn builder() -> OrganizationBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct OrganizationBuilder { - id: Option, - metadata: Option, - name: Option, -} - -impl OrganizationBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: OrganizationMetadata) -> Self { - self.metadata = Some(value); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Organization`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](OrganizationBuilder::id) - /// - [`name`](OrganizationBuilder::name) - pub fn build(self) -> Result { - Ok(Organization { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs b/seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs deleted file mode 100644 index b73e932e873d..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/organization_metadata.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct OrganizationMetadata { - /// Deployment region from DetailedOrg. - #[serde(default)] - pub region: String, - /// Custom domain name. - #[serde(skip_serializing_if = "Option::is_none")] - pub domain: Option, -} - -impl OrganizationMetadata { - pub fn builder() -> OrganizationMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct OrganizationMetadataBuilder { - region: Option, - domain: Option, -} - -impl OrganizationMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn domain(mut self, value: impl Into) -> Self { - self.domain = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`OrganizationMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](OrganizationMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(OrganizationMetadata { - region: self - .region - .ok_or_else(|| BuildError::missing_field("region"))?, - domain: self.domain, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs b/seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs deleted file mode 100644 index 1f7073c5844e..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/paginated_result.rs +++ /dev/null @@ -1,50 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] -pub struct PaginatedResult { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(default)] - pub results: Vec, -} - -impl PaginatedResult { - pub fn builder() -> PaginatedResultBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PaginatedResultBuilder { - paging: Option, - results: Option>, -} - -impl PaginatedResultBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`PaginatedResult`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](PaginatedResultBuilder::paging) - /// - [`results`](PaginatedResultBuilder::results) - pub fn build(self) -> Result { - Ok(PaginatedResult { - paging: self - .paging - .ok_or_else(|| BuildError::missing_field("paging"))?, - results: self - .results - .ok_or_else(|| BuildError::missing_field("results"))?, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs b/seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs deleted file mode 100644 index a3a785119b57..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/paging_cursors.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct PagingCursors { - /// Cursor for the next page of results. - #[serde(default)] - pub next: String, - /// Cursor for the previous page of results. - #[serde(skip_serializing_if = "Option::is_none")] - pub previous: Option, -} - -impl PagingCursors { - pub fn builder() -> PagingCursorsBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PagingCursorsBuilder { - next: Option, - previous: Option, -} - -impl PagingCursorsBuilder { - pub fn next(mut self, value: impl Into) -> Self { - self.next = Some(value.into()); - self - } - - pub fn previous(mut self, value: impl Into) -> Self { - self.previous = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`PagingCursors`]. - /// This method will fail if any of the following fields are not set: - /// - [`next`](PagingCursorsBuilder::next) - pub fn build(self) -> Result { - Ok(PagingCursors { - next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, - previous: self.previous, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs deleted file mode 100644 index 913f6f08eabd..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/rule_create_request.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleCreateRequest { - #[serde(default)] - pub name: String, - #[serde(rename = "executionContext")] - pub execution_context: RuleExecutionContext, -} - -impl RuleCreateRequest { - pub fn builder() -> RuleCreateRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleCreateRequestBuilder { - name: Option, - execution_context: Option, -} - -impl RuleCreateRequestBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleCreateRequest`]. - /// This method will fail if any of the following fields are not set: - /// - [`name`](RuleCreateRequestBuilder::name) - /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) - pub fn build(self) -> Result { - Ok(RuleCreateRequest { - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - execution_context: self - .execution_context - .ok_or_else(|| BuildError::missing_field("execution_context"))?, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs deleted file mode 100644 index 7f8fa5bd56e1..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/rule_execution_context.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -/// Execution environment for a rule. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleExecutionContext { - Prod, - Staging, - Dev, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleExecutionContext { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Prod => serializer.serialize_str("prod"), - Self::Staging => serializer.serialize_str("staging"), - Self::Dev => serializer.serialize_str("dev"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleExecutionContext { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "prod" => Ok(Self::Prod), - "staging" => Ok(Self::Staging), - "dev" => Ok(Self::Dev), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleExecutionContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Prod => write!(f, "prod"), - Self::Staging => write!(f, "staging"), - Self::Dev => write!(f, "dev"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_response.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_response.rs deleted file mode 100644 index 9977ccb1acfa..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/rule_response.rs +++ /dev/null @@ -1,114 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleResponse { - /// The user who created this resource. - #[serde(rename = "createdBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub created_by: Option, - /// When this resource was created. - #[serde(rename = "createdDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub created_date_time: Option>, - /// The user who last modified this resource. - #[serde(rename = "modifiedBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub modified_by: Option, - /// When this resource was last modified. - #[serde(rename = "modifiedDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub modified_date_time: Option>, - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - pub status: RuleResponseStatus, - #[serde(rename = "executionContext")] - #[serde(skip_serializing_if = "Option::is_none")] - pub execution_context: Option, -} - -impl RuleResponse { - pub fn builder() -> RuleResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleResponseBuilder { - created_by: Option, - created_date_time: Option>, - modified_by: Option, - modified_date_time: Option>, - id: Option, - name: Option, - status: Option, - execution_context: Option, -} - -impl RuleResponseBuilder { - pub fn created_by(mut self, value: impl Into) -> Self { - self.created_by = Some(value.into()); - self - } - - pub fn created_date_time(mut self, value: DateTime) -> Self { - self.created_date_time = Some(value); - self - } - - pub fn modified_by(mut self, value: impl Into) -> Self { - self.modified_by = Some(value.into()); - self - } - - pub fn modified_date_time(mut self, value: DateTime) -> Self { - self.modified_date_time = Some(value); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn status(mut self, value: RuleResponseStatus) -> Self { - self.status = Some(value); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](RuleResponseBuilder::id) - /// - [`name`](RuleResponseBuilder::name) - /// - [`status`](RuleResponseBuilder::status) - pub fn build(self) -> Result { - Ok(RuleResponse { - created_by: self.created_by, - created_date_time: self.created_date_time, - modified_by: self.modified_by, - modified_date_time: self.modified_date_time, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - status: self - .status - .ok_or_else(|| BuildError::missing_field("status"))?, - execution_context: self.execution_context, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs deleted file mode 100644 index 5d2462ecd349..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/rule_response_status.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleResponseStatus { - Active, - Inactive, - Draft, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleResponseStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Inactive => serializer.serialize_str("inactive"), - Self::Draft => serializer.serialize_str("draft"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleResponseStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "inactive" => Ok(Self::Inactive), - "draft" => Ok(Self::Draft), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleResponseStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Inactive => write!(f, "inactive"), - Self::Draft => write!(f, "draft"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_type.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_type.rs deleted file mode 100644 index 69f0317c1923..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/rule_type.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleType { - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, -} - -impl RuleType { - pub fn builder() -> RuleTypeBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeBuilder { - id: Option, - name: Option, - description: Option, -} - -impl RuleTypeBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn description(mut self, value: impl Into) -> Self { - self.description = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`RuleType`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](RuleTypeBuilder::id) - /// - [`name`](RuleTypeBuilder::name) - pub fn build(self) -> Result { - Ok(RuleType { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - description: self.description, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs b/seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs deleted file mode 100644 index 0f1fbcdeb7dc..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/rule_type_search_response.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleTypeSearchResponse { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, -} - -impl RuleTypeSearchResponse { - pub fn builder() -> RuleTypeSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeSearchResponseBuilder { - paging: Option, - results: Option>, -} - -impl RuleTypeSearchResponseBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](RuleTypeSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(RuleTypeSearchResponse { - paging: self - .paging - .ok_or_else(|| BuildError::missing_field("paging"))?, - results: self.results, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs b/seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs deleted file mode 100644 index 37158173a08c..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/search_rule_types_query_request.rs +++ /dev/null @@ -1,32 +0,0 @@ -pub use crate::prelude::*; - -/// Query parameters for searchRuleTypes -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct SearchRuleTypesQueryRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub query: Option, -} - -impl SearchRuleTypesQueryRequest { - pub fn builder() -> SearchRuleTypesQueryRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct SearchRuleTypesQueryRequestBuilder { - query: Option, -} - -impl SearchRuleTypesQueryRequestBuilder { - pub fn query(mut self, value: impl Into) -> Self { - self.query = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. - pub fn build(self) -> Result { - Ok(SearchRuleTypesQueryRequest { query: self.query }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/user.rs b/seed/rust-sdk/allof-inline/src/api/types/user.rs deleted file mode 100644 index cce3febf2dbe..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/user.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct User { - #[serde(default)] - pub id: String, - #[serde(default)] - pub email: String, -} - -impl User { - pub fn builder() -> UserBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserBuilder { - id: Option, - email: Option, -} - -impl UserBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn email(mut self, value: impl Into) -> Self { - self.email = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`User`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](UserBuilder::id) - /// - [`email`](UserBuilder::email) - pub fn build(self) -> Result { - Ok(User { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - email: self - .email - .ok_or_else(|| BuildError::missing_field("email"))?, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs b/seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs deleted file mode 100644 index 08e7814bee1b..000000000000 --- a/seed/rust-sdk/allof-inline/src/api/types/user_search_response.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct UserSearchResponse { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, -} - -impl UserSearchResponse { - pub fn builder() -> UserSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserSearchResponseBuilder { - paging: Option, - results: Option>, -} - -impl UserSearchResponseBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`UserSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](UserSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(UserSearchResponse { - paging: self - .paging - .ok_or_else(|| BuildError::missing_field("paging"))?, - results: self.results, - }) - } -} diff --git a/seed/rust-sdk/allof-inline/src/client.rs b/seed/rust-sdk/allof-inline/src/client.rs deleted file mode 100644 index 139cebf898d7..000000000000 --- a/seed/rust-sdk/allof-inline/src/client.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::api::resources::ApiClient; -use crate::Environment; -use crate::{ApiError, ClientConfig}; -use std::collections::HashMap; -use std::time::Duration; -/// Builder for creating API clients with custom configuration -pub struct ApiClientBuilder { - config: ClientConfig, -} -impl Default for ApiClientBuilder { - fn default() -> Self { - Self { - config: ClientConfig::default(), - } - } -} -impl ApiClientBuilder { - /// Create a new builder with the specified base URL - pub fn new(base_url: impl Into) -> Self { - let mut config = ClientConfig::default(); - config.base_url = base_url.into(); - Self { config } - } - - /// Set the environment, updating the base URL - pub fn environment(mut self, environment: Environment) -> Self { - self.config.base_url = environment.url().to_string(); - self - } - - /// Set the API key for authentication - pub fn api_key(mut self, key: impl Into) -> Self { - self.config.api_key = Some(key.into()); - self - } - - /// Set the bearer token for authentication - pub fn token(mut self, token: impl Into) -> Self { - self.config.token = Some(token.into()); - self - } - - /// Set the username for basic authentication - pub fn username(mut self, username: impl Into) -> Self { - self.config.username = Some(username.into()); - self - } - - /// Set the password for basic authentication - pub fn password(mut self, password: impl Into) -> Self { - self.config.password = Some(password.into()); - self - } - - /// Set the OAuth client ID for client credentials authentication - pub fn client_id(mut self, client_id: impl Into) -> Self { - self.config.client_id = Some(client_id.into()); - self - } - - /// Set the OAuth client secret for client credentials authentication - pub fn client_secret(mut self, client_secret: impl Into) -> Self { - self.config.client_secret = Some(client_secret.into()); - self - } - - /// Set OAuth credentials (client_id and client_secret) for client credentials authentication - pub fn oauth_credentials( - mut self, - client_id: impl Into, - client_secret: impl Into, - ) -> Self { - self.config.client_id = Some(client_id.into()); - self.config.client_secret = Some(client_secret.into()); - self - } - - /// Set the request timeout - pub fn timeout(mut self, timeout: Duration) -> Self { - self.config.timeout = timeout; - self - } - - /// Set the maximum number of retries - pub fn max_retries(mut self, retries: u32) -> Self { - self.config.max_retries = retries; - self - } - - /// Add a custom header - pub fn custom_header(mut self, key: impl Into, value: impl Into) -> Self { - self.config.custom_headers.insert(key.into(), value.into()); - self - } - - /// Add multiple custom headers - pub fn custom_headers(mut self, headers: HashMap) -> Self { - self.config.custom_headers.extend(headers); - self - } - - /// Set the user agent - pub fn user_agent(mut self, user_agent: impl Into) -> Self { - self.config.user_agent = user_agent.into(); - self - } - - /// Build the client with validation - pub fn build(self) -> Result { - ApiClient::new(self.config) - } -} -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_environment() { - let builder = ApiClientBuilder::default().environment(Environment::default()); - assert_eq!( - builder.config.base_url, - Environment::default().url().to_string() - ); - } - - #[test] - fn test_new_sets_base_url() { - let builder = ApiClientBuilder::new("https://api.example.com"); - assert_eq!(builder.config.base_url, "https://api.example.com"); - } - - #[test] - fn test_api_key() { - let builder = ApiClientBuilder::new("https://api.example.com").api_key("my-key"); - assert_eq!(builder.config.api_key, Some("my-key".to_string())); - } - - #[test] - fn test_token() { - let builder = ApiClientBuilder::new("https://api.example.com").token("my-token"); - assert_eq!(builder.config.token, Some("my-token".to_string())); - } - - #[test] - fn test_username() { - let builder = ApiClientBuilder::new("https://api.example.com").username("user"); - assert_eq!(builder.config.username, Some("user".to_string())); - } - - #[test] - fn test_password() { - let builder = ApiClientBuilder::new("https://api.example.com").password("pass"); - assert_eq!(builder.config.password, Some("pass".to_string())); - } - - #[test] - fn test_client_id() { - let builder = ApiClientBuilder::new("https://api.example.com").client_id("cid"); - assert_eq!(builder.config.client_id, Some("cid".to_string())); - } - - #[test] - fn test_client_secret() { - let builder = ApiClientBuilder::new("https://api.example.com").client_secret("secret"); - assert_eq!(builder.config.client_secret, Some("secret".to_string())); - } - - #[test] - fn test_oauth_credentials() { - let builder = - ApiClientBuilder::new("https://api.example.com").oauth_credentials("cid", "secret"); - assert_eq!(builder.config.client_id, Some("cid".to_string())); - assert_eq!(builder.config.client_secret, Some("secret".to_string())); - } - - #[test] - fn test_timeout() { - let builder = - ApiClientBuilder::new("https://api.example.com").timeout(Duration::from_secs(120)); - assert_eq!(builder.config.timeout, Duration::from_secs(120)); - } - - #[test] - fn test_max_retries() { - let builder = ApiClientBuilder::new("https://api.example.com").max_retries(5); - assert_eq!(builder.config.max_retries, 5); - } - - #[test] - fn test_custom_header() { - let builder = - ApiClientBuilder::new("https://api.example.com").custom_header("X-Custom", "value"); - assert_eq!( - builder.config.custom_headers.get("X-Custom"), - Some(&"value".to_string()) - ); - } - - #[test] - fn test_custom_headers_multiple() { - let mut headers = HashMap::new(); - headers.insert("X-One".to_string(), "1".to_string()); - headers.insert("X-Two".to_string(), "2".to_string()); - let builder = ApiClientBuilder::new("https://api.example.com").custom_headers(headers); - assert_eq!( - builder.config.custom_headers.get("X-One"), - Some(&"1".to_string()) - ); - assert_eq!( - builder.config.custom_headers.get("X-Two"), - Some(&"2".to_string()) - ); - } - - #[test] - fn test_user_agent() { - let builder = ApiClientBuilder::new("https://api.example.com").user_agent("my-sdk/1.0"); - assert_eq!(builder.config.user_agent, "my-sdk/1.0"); - } - - #[test] - fn test_full_builder_chain() { - let builder = ApiClientBuilder::new("https://api.example.com") - .api_key("key") - .token("tok") - .username("user") - .password("pass") - .timeout(Duration::from_secs(60)) - .max_retries(3) - .custom_header("X-Foo", "bar") - .user_agent("test/1.0"); - assert_eq!(builder.config.base_url, "https://api.example.com"); - assert_eq!(builder.config.api_key, Some("key".to_string())); - assert_eq!(builder.config.token, Some("tok".to_string())); - assert_eq!(builder.config.username, Some("user".to_string())); - assert_eq!(builder.config.password, Some("pass".to_string())); - assert_eq!(builder.config.timeout, Duration::from_secs(60)); - assert_eq!(builder.config.max_retries, 3); - assert_eq!(builder.config.user_agent, "test/1.0"); - } - - #[test] - fn test_build_succeeds() { - let result = ApiClientBuilder::new("https://api.example.com").build(); - assert!(result.is_ok()); - } -} diff --git a/seed/rust-sdk/allof-inline/src/config.rs b/seed/rust-sdk/allof-inline/src/config.rs deleted file mode 100644 index 466af37454a1..000000000000 --- a/seed/rust-sdk/allof-inline/src/config.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::Environment; -use std::collections::HashMap; -use std::time::Duration; - -#[derive(Debug, Clone)] -pub struct ClientConfig { - pub base_url: String, - pub api_key: Option, - pub token: Option, - pub username: Option, - pub password: Option, - pub client_id: Option, - pub client_secret: Option, - pub timeout: Duration, - pub max_retries: u32, - pub custom_headers: HashMap, - pub user_agent: String, -} -impl Default for ClientConfig { - fn default() -> Self { - Self { - base_url: Environment::default().url().to_string(), - api_key: None, - token: None, - username: None, - password: None, - client_id: None, - client_secret: None, - timeout: Duration::from_secs(60), - max_retries: 3, - custom_headers: HashMap::from([ - ("X-Fern-Language".to_string(), "Rust".to_string()), - ("X-Fern-SDK-Name".to_string(), "seed_api".to_string()), - ("X-Fern-SDK-Version".to_string(), "0.0.1".to_string()), - ]), - user_agent: "Api Rust SDK".to_string(), - } - } -} diff --git a/seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs b/seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs deleted file mode 100644 index e5572d11b299..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/flexible_datetime.rs +++ /dev/null @@ -1,270 +0,0 @@ -//! Flexible datetime parsing module -//! -//! This module provides serde helpers for parsing datetime strings that may or may not -//! include a timezone suffix. It accepts both RFC3339 format (with Z or +00:00 suffix) -//! and ISO 8601 format without timezone (assuming UTC). -//! -//! Supported formats: -//! - `2024-01-15T09:30:00Z` (RFC3339 with Z) -//! - `2024-01-15T09:30:00+00:00` (RFC3339 with offset) -//! - `2024-01-15T09:30:00` (ISO 8601 without timezone, assumes UTC) -//! - `2024-01-15T09:30:00.123Z` (with fractional seconds and Z) -//! - `2024-01-15T09:30:00.123` (with fractional seconds, no timezone) -//! -//! Two submodules are provided: -//! - `utc`: Parses into `DateTime`, converting all datetimes to UTC -//! - `offset`: Parses into `DateTime`, preserving original timezone - -/// Module for DateTime with flexible parsing - converts all datetimes to UTC -pub mod utc { - use chrono::{DateTime, NaiveDateTime, Utc}; - use serde::{self, Deserialize, Deserializer, Serializer}; - - /// Serialize a DateTime to RFC3339 format - pub fn serialize(date: &DateTime, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&date.to_rfc3339()) - } - - /// Deserialize a datetime string that may or may not include a timezone suffix. - /// If no timezone is present, UTC is assumed. All datetimes are converted to UTC. - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - parse_flexible_datetime(&s).map_err(serde::de::Error::custom) - } - - /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. - fn parse_flexible_datetime(s: &str) -> Result, String> { - // First, try parsing as RFC3339 (with timezone) - if let Ok(dt) = DateTime::parse_from_rfc3339(s) { - return Ok(dt.with_timezone(&Utc)); - } - - // Try parsing as NaiveDateTime (without timezone) and assume UTC - // Try with fractional seconds first - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { - return Ok(naive.and_utc()); - } - - // Try without fractional seconds - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { - return Ok(naive.and_utc()); - } - - Err(format!( - "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ - or ISO 8601 format (e.g., '2024-01-15T09:30:00')", - s - )) - } - - /// Module for optional DateTime fields with flexible parsing - pub mod option { - use super::*; - - pub fn serialize(date: &Option>, serializer: S) -> Result - where - S: Serializer, - { - match date { - Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let opt: Option = Option::deserialize(deserializer)?; - match opt { - Some(s) => parse_flexible_datetime(&s) - .map(Some) - .map_err(serde::de::Error::custom), - None => Ok(None), - } - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_parse_rfc3339_with_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_rfc3339_with_offset() { - let result = parse_flexible_datetime("2024-01-15T09:30:00+00:00"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_without_timezone() { - let result = parse_flexible_datetime("2024-01-15T09:30:00"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_with_fractional_seconds() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_with_fractional_seconds_and_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); - assert!(result.is_ok()); - } - } -} - -/// Module for DateTime with flexible parsing - preserves original timezone -pub mod offset { - use chrono::{DateTime, FixedOffset, NaiveDateTime}; - use serde::{self, Deserialize, Deserializer, Serializer}; - - /// Serialize a DateTime to RFC3339 format - pub fn serialize(date: &DateTime, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&date.to_rfc3339()) - } - - /// Deserialize a datetime string that may or may not include a timezone suffix. - /// If no timezone is present, UTC (+00:00) is assumed. - /// The original timezone offset is preserved when present. - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - parse_flexible_datetime(&s).map_err(serde::de::Error::custom) - } - - /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. - /// Preserves the original timezone offset when present, assumes UTC when not. - fn parse_flexible_datetime(s: &str) -> Result, String> { - // First, try parsing as RFC3339 (with timezone) - this preserves the original offset - if let Ok(dt) = DateTime::parse_from_rfc3339(s) { - return Ok(dt); - } - - // Try parsing as NaiveDateTime (without timezone) and assume UTC (+00:00) - let utc_offset = FixedOffset::east_opt(0).unwrap(); - - // Try with fractional seconds first - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { - return Ok(naive.and_local_timezone(utc_offset).unwrap()); - } - - // Try without fractional seconds - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { - return Ok(naive.and_local_timezone(utc_offset).unwrap()); - } - - Err(format!( - "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ - or ISO 8601 format (e.g., '2024-01-15T09:30:00')", - s - )) - } - - /// Module for optional DateTime fields with flexible parsing - pub mod option { - use super::*; - - pub fn serialize( - date: &Option>, - serializer: S, - ) -> Result - where - S: Serializer, - { - match date { - Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let opt: Option = Option::deserialize(deserializer)?; - match opt { - Some(s) => parse_flexible_datetime(&s) - .map(Some) - .map_err(serde::de::Error::custom), - None => Ok(None), - } - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_parse_rfc3339_with_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); - assert!(result.is_ok()); - let dt = result.unwrap(); - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_parse_rfc3339_with_offset() { - let result = parse_flexible_datetime("2024-01-15T09:30:00-05:00"); - assert!(result.is_ok()); - let dt = result.unwrap(); - // -05:00 = -5 * 3600 = -18000 seconds - assert_eq!(dt.offset().local_minus_utc(), -18000); - } - - #[test] - fn test_parse_without_timezone() { - let result = parse_flexible_datetime("2024-01-15T09:30:00"); - assert!(result.is_ok()); - let dt = result.unwrap(); - // Should assume UTC (+00:00) - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_parse_with_fractional_seconds() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); - assert!(result.is_ok()); - let dt = result.unwrap(); - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_parse_with_fractional_seconds_and_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); - assert!(result.is_ok()); - let dt = result.unwrap(); - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_preserves_positive_offset() { - let result = parse_flexible_datetime("2024-01-15T09:30:00+09:00"); - assert!(result.is_ok()); - let dt = result.unwrap(); - // +09:00 = 9 * 3600 = 32400 seconds - assert_eq!(dt.offset().local_minus_utc(), 32400); - } - } -} diff --git a/seed/rust-sdk/allof-inline/src/core/http_client.rs b/seed/rust-sdk/allof-inline/src/core/http_client.rs deleted file mode 100644 index d61f8b46a3a1..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/http_client.rs +++ /dev/null @@ -1,569 +0,0 @@ -use crate::{join_url, ApiError, ClientConfig, OAuthTokenProvider, RequestOptions}; -use futures::{Stream, StreamExt}; -use reqwest::{ - header::{HeaderName, HeaderValue}, - Client, Method, Request, Response, -}; -use serde::de::DeserializeOwned; - -use std::{ - pin::Pin, - str::FromStr, - sync::Arc, - task::{Context, Poll}, -}; - -/// A streaming byte stream for downloading files efficiently -pub struct ByteStream { - content_length: Option, - inner: Pin> + Send>>, -} - -impl ByteStream { - /// Create a new ByteStream from a Response - pub(crate) fn new(response: Response) -> Self { - let content_length = response.content_length(); - let stream = response.bytes_stream(); - - Self { - content_length, - inner: Box::pin(stream), - } - } - - /// Collect the entire stream into a `Vec` - /// - /// This consumes the stream and buffers all data into memory. - /// For large files, prefer using `try_next()` to process chunks incrementally. - /// - /// # Example - /// ```no_run - /// let stream = client.download_file().await?; - /// let bytes = stream.collect().await?; - /// ``` - pub async fn collect(mut self) -> Result, ApiError> { - let mut result = Vec::new(); - while let Some(chunk) = self.inner.next().await { - result.extend_from_slice(&chunk.map_err(ApiError::Network)?); - } - Ok(result) - } - - /// Get the next chunk from the stream - /// - /// Returns `Ok(Some(bytes))` if a chunk is available, - /// `Ok(None)` if the stream is finished, or an error. - /// - /// # Example - /// ```no_run - /// let mut stream = client.download_file().await?; - /// while let Some(chunk) = stream.try_next().await? { - /// process_chunk(&chunk); - /// } - /// ``` - pub async fn try_next(&mut self) -> Result, ApiError> { - match self.inner.next().await { - Some(Ok(bytes)) => Ok(Some(bytes)), - Some(Err(e)) => Err(ApiError::Network(e)), - None => Ok(None), - } - } - - /// Get the content length from response headers if available - pub fn content_length(&self) -> Option { - self.content_length - } -} - -impl Stream for ByteStream { - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.inner.as_mut().poll_next(cx) { - Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))), - Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ApiError::Network(e)))), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -/// Configuration for OAuth token fetching. -/// -/// This struct contains all the information needed to automatically fetch -/// and refresh OAuth tokens. -#[derive(Clone)] -pub struct OAuthConfig { - /// The OAuth token provider that manages token caching and refresh - pub token_provider: Arc, - /// The token endpoint path (e.g., "/token") - pub token_endpoint: String, -} - -/// Response from an OAuth token endpoint. -#[derive(Debug, Clone, serde::Deserialize)] -struct OAuthTokenResponse { - access_token: String, - #[serde(default)] - expires_in: Option, -} - -/// Internal HTTP client that handles requests with authentication and retries -#[derive(Clone)] -pub struct HttpClient { - client: Client, - config: ClientConfig, - /// Optional OAuth configuration for automatic token management - oauth_config: Option, -} - -impl HttpClient { - /// Creates a new HttpClient without OAuth support. - pub fn new(config: ClientConfig) -> Result { - Self::new_with_oauth(config, None) - } - - /// Creates a new HttpClient with optional OAuth support. - /// - /// When `oauth_config` is provided, the client will automatically fetch and refresh - /// OAuth tokens before making requests. - pub fn new_with_oauth( - config: ClientConfig, - oauth_config: Option, - ) -> Result { - let client = Client::builder() - .timeout(config.timeout) - .user_agent(&config.user_agent) - .build() - .map_err(ApiError::Network)?; - - Ok(Self { - client, - config, - oauth_config, - }) - } - - /// Returns the configured base URL. - pub fn base_url(&self) -> &str { - &self.config.base_url - } - - /// Returns a reference to the client configuration. - pub fn config(&self) -> &ClientConfig { - &self.config - } - - /// Execute a request with the given method, path, and options - pub async fn execute_request( - &self, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result - where - T: DeserializeOwned, // Generic T: DeserializeOwned means the response will be automatically deserialized into whatever type you specify: - { - let url = join_url(&self.config.base_url, path); - let mut request = self.client.request(method, &url); - - // Apply query parameters if provided - if let Some(params) = query_params { - request = request.query(¶ms); - } - - // Apply additional query parameters from options - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - // Apply body if provided - if let Some(body) = body { - request = request.json(&body); - } - - // Build the request - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - // Apply authentication and headers - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - // Execute with retries - let response = self.execute_with_retries(req, &options).await?; - self.parse_response(response).await - } - - /// Execute a request with an explicit base URL override. - /// - /// Used for multi-URL environments where different endpoints - /// resolve to different base URLs. - pub async fn execute_request_with_base_url( - &self, - base_url: &str, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result - where - T: DeserializeOwned, - { - let url = join_url(base_url, path); - let mut request = self.client.request(method, &url); - - if let Some(params) = query_params { - request = request.query(¶ms); - } - - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - if let Some(body) = body { - request = request.json(&body); - } - - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - let response = self.execute_with_retries(req, &options).await?; - self.parse_response(response).await - } - - async fn apply_auth_headers( - &self, - request: &mut Request, - options: &Option, - ) -> Result<(), ApiError> { - let headers = request.headers_mut(); - - // Apply API key (request options override config) - let api_key = options - .as_ref() - .and_then(|opts| opts.api_key.as_ref()) - .or(self.config.api_key.as_ref()); - - if let Some(key) = api_key { - let header_value = key.to_string(); - headers.insert( - "api_key", - header_value.parse().map_err(|_| ApiError::InvalidHeader)?, - ); - } - - // Apply bearer token - priority: request options > OAuth > config - let token = if let Some(opts) = options.as_ref() { - if opts.token.is_some() { - opts.token.clone() - } else { - None - } - } else { - None - }; - - let token = match token { - Some(t) => Some(t), - None => { - // Try OAuth token provider if configured - if let Some(oauth_config) = &self.oauth_config { - Some(self.get_oauth_token(oauth_config).await?) - } else { - // Fall back to static token from config - self.config.token.clone() - } - } - }; - - if let Some(token) = token { - let auth_value = format!("Bearer {}", token); - headers.insert( - "Authorization", - auth_value.parse().map_err(|_| ApiError::InvalidHeader)?, - ); - } - - Ok(()) - } - - /// Fetches an OAuth token, using the cached token if valid or fetching a new one. - async fn get_oauth_token(&self, oauth_config: &OAuthConfig) -> Result { - let token_provider = &oauth_config.token_provider; - let token_endpoint = &oauth_config.token_endpoint; - let client_id = token_provider.client_id().to_string(); - let client_secret = token_provider.client_secret().to_string(); - let base_url = self.config.base_url.clone(); - - // Use the async get_or_fetch method with a closure that fetches the token - token_provider - .get_or_fetch_async(|| async { - self.fetch_oauth_token(&base_url, token_endpoint, &client_id, &client_secret) - .await - }) - .await - } - - /// Makes an HTTP request to the OAuth token endpoint to fetch a new token. - async fn fetch_oauth_token( - &self, - base_url: &str, - token_endpoint: &str, - client_id: &str, - client_secret: &str, - ) -> Result<(String, u64), ApiError> { - let url = join_url(base_url, token_endpoint); - - // Build the token request body - let body = serde_json::json!({ - "client_id": client_id, - "client_secret": client_secret, - "grant_type": "client_credentials" - }); - - let response = self - .client - .request(Method::POST, &url) - .json(&body) - .send() - .await - .map_err(ApiError::Network)?; - - if !response.status().is_success() { - let status_code = response.status().as_u16(); - let body = response.text().await.ok(); - return Err(ApiError::from_response(status_code, body.as_deref())); - } - - // Parse the token response - let token_response: OAuthTokenResponse = - response.json().await.map_err(ApiError::Network)?; - - let expires_in = token_response.expires_in.unwrap_or(3600) as u64; - Ok((token_response.access_token, expires_in)) - } - - fn apply_custom_headers( - &self, - request: &mut Request, - options: &Option, - ) -> Result<(), ApiError> { - let headers = request.headers_mut(); - - // Apply config-level custom headers - for (key, value) in &self.config.custom_headers { - headers.insert( - HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, - HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, - ); - } - - // Apply request-level custom headers (override config) - if let Some(options) = options { - for (key, value) in &options.additional_headers { - headers.insert( - HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, - HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, - ); - } - } - - Ok(()) - } - - async fn execute_with_retries( - &self, - request: Request, - options: &Option, - ) -> Result { - let max_retries = options - .as_ref() - .and_then(|opts| opts.max_retries) - .unwrap_or(self.config.max_retries); - - let mut last_error = None; - - for attempt in 0..=max_retries { - let cloned_request = request.try_clone().ok_or(ApiError::RequestClone)?; - - match self.client.execute(cloned_request).await { - Ok(response) if response.status().is_success() => return Ok(response), - Ok(response) => { - let status_code = response.status().as_u16(); - let body = response.text().await.ok(); - return Err(ApiError::from_response(status_code, body.as_deref())); - } - Err(e) if attempt < max_retries => { - last_error = Some(e); - // Exponential backoff - let delay = std::time::Duration::from_millis(100 * 2_u64.pow(attempt)); - tokio::time::sleep(delay).await; - } - Err(e) => return Err(ApiError::Network(e)), - } - } - - Err(ApiError::Network(last_error.unwrap())) - } - - async fn parse_response(&self, response: Response) -> Result - where - T: DeserializeOwned, - { - let status = response.status().as_u16(); - let text = response.text().await.map_err(ApiError::Network)?; - - // Handle empty response bodies (e.g., 202 Accepted for deferred requests) - if text.is_empty() { - return Err(ApiError::Http { - status, - message: String::new(), - }); - } - - serde_json::from_str(&text).map_err(ApiError::Serialization) - } - - /// Execute a request and return a streaming response (for large file downloads) - /// - /// This method returns a `ByteStream` that can be used to download large files - /// efficiently without loading the entire content into memory. The stream can be - /// consumed chunk by chunk, written directly to disk, or collected into bytes. - /// - /// # Examples - /// - /// **Option 1: Collect all bytes into memory** - /// ```no_run - /// let stream = client.execute_stream_request( - /// Method::GET, - /// "/file", - /// None, - /// None, - /// None, - /// ).await?; - /// - /// let bytes = stream.collect().await?; - /// ``` - /// - /// **Option 2: Process chunks with try_next()** - /// ```no_run - /// let mut stream = client.execute_stream_request( - /// Method::GET, - /// "/large-file", - /// None, - /// None, - /// None, - /// ).await?; - /// - /// while let Some(chunk) = stream.try_next().await? { - /// process_chunk(&chunk); - /// } - /// ``` - /// - /// **Option 3: Stream with futures::Stream trait** - /// ```no_run - /// use futures::StreamExt; - /// - /// let stream = client.execute_stream_request( - /// Method::GET, - /// "/large-file", - /// None, - /// None, - /// None, - /// ).await?; - /// - /// let mut file = tokio::fs::File::create("output.mp4").await?; - /// let mut stream = std::pin::pin!(stream); - /// while let Some(chunk) = stream.next().await { - /// let chunk = chunk?; - /// tokio::io::AsyncWriteExt::write_all(&mut file, &chunk).await?; - /// } - /// ``` - pub async fn execute_stream_request( - &self, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result { - let url = join_url(&self.config.base_url, path); - let mut request = self.client.request(method, &url); - - // Apply query parameters if provided - if let Some(params) = query_params { - request = request.query(¶ms); - } - - // Apply additional query parameters from options - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - // Apply body if provided - if let Some(body) = body { - request = request.json(&body); - } - - // Build the request - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - // Apply authentication and headers - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - // Execute with retries - let response = self.execute_with_retries(req, &options).await?; - - // Return streaming response - Ok(ByteStream::new(response)) - } - - /// Execute a streaming request with an explicit base URL override. - pub async fn execute_stream_request_with_base_url( - &self, - base_url: &str, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result { - let url = join_url(base_url, path); - let mut request = self.client.request(method, &url); - - if let Some(params) = query_params { - request = request.query(¶ms); - } - - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - if let Some(body) = body { - request = request.json(&body); - } - - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - let response = self.execute_with_retries(req, &options).await?; - - Ok(ByteStream::new(response)) - } -} diff --git a/seed/rust-sdk/allof-inline/src/core/mod.rs b/seed/rust-sdk/allof-inline/src/core/mod.rs deleted file mode 100644 index 71a75dbb141b..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Core client infrastructure - -pub mod flexible_datetime; -mod http_client; -mod oauth_token_provider; -mod query_parameter_builder; -mod request_options; -mod utils; - -pub use http_client::{ByteStream, HttpClient, OAuthConfig}; -pub use oauth_token_provider::OAuthTokenProvider; -pub use query_parameter_builder::{parse_structured_query, QueryBuilder, QueryBuilderError}; -pub use request_options::RequestOptions; -pub use utils::join_url; diff --git a/seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs b/seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs deleted file mode 100644 index 09222bedd8e0..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/oauth_token_provider.rs +++ /dev/null @@ -1,363 +0,0 @@ -use std::future::Future; -use std::sync::Mutex; -use std::time::{Duration, Instant}; -use tokio::sync::Mutex as AsyncMutex; - -/// Buffer time in seconds subtracted from token expiration to ensure -/// we refresh the token before it actually expires. -const EXPIRATION_BUFFER_SECONDS: u64 = 120; // 2 minutes - -/// Default expiry time in seconds used when the OAuth response doesn't include an expires_in value. -const DEFAULT_EXPIRY_SECONDS: u64 = 3600; // 1 hour fallback - -/// Manages OAuth access tokens, including caching and automatic refresh. -/// -/// This provider implements thread-safe token management with automatic expiration -/// handling. It uses a double-checked locking pattern to minimize lock contention -/// while ensuring only one thread fetches a new token at a time. -/// -/// # Example -/// -/// ```rust,ignore -/// use crate::OAuthTokenProvider; -/// -/// let provider = OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); -/// -/// // Get or fetch a token (sync) -/// let token = provider.get_or_fetch(|| { -/// // Your token fetching logic here -/// // Returns (access_token, expires_in_seconds) -/// Ok(("token".to_string(), Some(3600))) -/// })?; -/// -/// // Get or fetch a token (async) -/// let token = provider.get_or_fetch_async(|| async { -/// // Your async token fetching logic here -/// Ok(("token".to_string(), Some(3600))) -/// }).await?; -/// ``` -pub struct OAuthTokenProvider { - client_id: String, - client_secret: String, - inner: Mutex, - /// Separate mutex to ensure only one thread fetches a new token at a time (sync) - fetch_lock: Mutex<()>, - /// Async mutex for async token fetching - async_fetch_lock: AsyncMutex<()>, -} - -struct OAuthTokenProviderInner { - access_token: Option, - expires_at: Option, -} - -impl OAuthTokenProvider { - /// Creates a new OAuthTokenProvider with the given credentials. - pub fn new(client_id: String, client_secret: String) -> Self { - Self { - client_id, - client_secret, - inner: Mutex::new(OAuthTokenProviderInner { - access_token: None, - expires_at: None, - }), - fetch_lock: Mutex::new(()), - async_fetch_lock: AsyncMutex::new(()), - } - } - - /// Returns the client ID. - pub fn client_id(&self) -> &str { - &self.client_id - } - - /// Returns the client secret. - pub fn client_secret(&self) -> &str { - &self.client_secret - } - - /// Sets the cached access token and its expiration time. - /// - /// The `expires_in` parameter is the number of seconds until the token expires. - /// A buffer is applied to refresh before actual expiration. - pub fn set_token(&self, access_token: String, expires_in: u64) { - let mut inner = self.inner.lock().unwrap(); - inner.access_token = Some(access_token); - - if expires_in > 0 { - // Apply buffer to refresh before actual expiration - let effective_expires_in = expires_in.saturating_sub(EXPIRATION_BUFFER_SECONDS); - inner.expires_at = Some(Instant::now() + Duration::from_secs(effective_expires_in)); - } else { - // No expiration info, token won't auto-refresh based on time - inner.expires_at = None; - } - } - - /// Returns the cached access token if it's still valid. - /// - /// Returns `None` if the token is expired or not set. - pub fn get_token(&self) -> Option { - let inner = self.inner.lock().unwrap(); - - if let Some(ref token) = inner.access_token { - // Check if token is still valid - if let Some(expires_at) = inner.expires_at { - if Instant::now() < expires_at { - return Some(token.clone()); - } - } else { - // No expiration set, token is always valid - return Some(token.clone()); - } - } - - None - } - - /// Returns a valid token, fetching a new one if necessary (synchronous version). - /// - /// The `fetch_func` is called at most once even if multiple threads call `get_or_fetch` - /// concurrently when the token is expired. It should return `(access_token, expires_in_seconds)`. - /// - /// # Arguments - /// - /// * `fetch_func` - A function that fetches a new token. Returns `Result<(String, u64), E>` - /// where the tuple contains (access_token, expires_in_seconds). - /// - /// # Example - /// - /// ```rust,ignore - /// let token = provider.get_or_fetch(|| { - /// // Call your OAuth endpoint here (sync) - /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret())?; - /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) - /// })?; - /// ``` - pub fn get_or_fetch(&self, fetch_func: F) -> Result - where - F: FnOnce() -> Result<(String, u64), E>, - { - // Fast path: check if we have a valid token - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Slow path: acquire fetch lock to ensure only one thread fetches - let _fetch_guard = self.fetch_lock.lock().unwrap(); - - // Double-check after acquiring lock (another thread may have fetched) - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Fetch new token - let (access_token, expires_in) = fetch_func()?; - - // Use default expiry if not provided - let effective_expires_in = if expires_in > 0 { - expires_in - } else { - DEFAULT_EXPIRY_SECONDS - }; - - self.set_token(access_token.clone(), effective_expires_in); - Ok(access_token) - } - - /// Returns a valid token, fetching a new one if necessary (async version). - /// - /// This is the async version of `get_or_fetch` for use with async token fetching. - /// The `fetch_func` is called at most once even if multiple tasks call `get_or_fetch_async` - /// concurrently when the token is expired. - /// - /// # Arguments - /// - /// * `fetch_func` - An async function that fetches a new token. Returns `Result<(String, u64), E>` - /// where the tuple contains (access_token, expires_in_seconds). - /// - /// # Example - /// - /// ```rust,ignore - /// let token = provider.get_or_fetch_async(|| async { - /// // Call your OAuth endpoint here (async) - /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret()).await?; - /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) - /// }).await?; - /// ``` - pub async fn get_or_fetch_async(&self, fetch_func: F) -> Result - where - F: FnOnce() -> Fut, - Fut: Future>, - { - // Fast path: check if we have a valid token - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Slow path: acquire async fetch lock to ensure only one task fetches - let _fetch_guard = self.async_fetch_lock.lock().await; - - // Double-check after acquiring lock (another task may have fetched) - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Fetch new token - let (access_token, expires_in) = fetch_func().await?; - - // Use default expiry if not provided - let effective_expires_in = if expires_in > 0 { - expires_in - } else { - DEFAULT_EXPIRY_SECONDS - }; - - self.set_token(access_token.clone(), effective_expires_in); - Ok(access_token) - } - - /// Returns `true` if the token needs to be refreshed. - /// - /// This is useful for proactively refreshing tokens before they expire. - pub fn needs_refresh(&self) -> bool { - let inner = self.inner.lock().unwrap(); - - if inner.access_token.is_none() { - return true; - } - - if let Some(expires_at) = inner.expires_at { - if Instant::now() >= expires_at { - return true; - } - } - - false - } - - /// Clears the cached token. - /// - /// This can be used to force a token refresh on the next request. - pub fn reset(&self) { - let mut inner = self.inner.lock().unwrap(); - inner.access_token = None; - inner.expires_at = None; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::Arc; - use std::thread; - - #[test] - fn test_new_provider() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - assert_eq!(provider.client_id(), "client_id"); - assert_eq!(provider.client_secret(), "client_secret"); - assert!(provider.get_token().is_none()); - assert!(provider.needs_refresh()); - } - - #[test] - fn test_set_and_get_token() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - provider.set_token("test_token".to_string(), 3600); - - let token = provider.get_token(); - assert!(token.is_some()); - assert_eq!(token.unwrap(), "test_token"); - assert!(!provider.needs_refresh()); - } - - #[test] - fn test_expired_token() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - // Set token with 0 expiry (will be expired immediately due to buffer) - provider.set_token("test_token".to_string(), 1); - - // Token should be expired (1 second - 120 second buffer = expired) - assert!(provider.get_token().is_none()); - assert!(provider.needs_refresh()); - } - - #[test] - fn test_get_or_fetch() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - let result: Result = - provider.get_or_fetch(|| Ok(("fetched_token".to_string(), 3600))); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "fetched_token"); - - // Second call should return cached token - let result2: Result = provider.get_or_fetch(|| { - panic!("Should not be called - token is cached"); - }); - - assert!(result2.is_ok()); - assert_eq!(result2.unwrap(), "fetched_token"); - } - - #[test] - fn test_reset() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - provider.set_token("test_token".to_string(), 3600); - assert!(provider.get_token().is_some()); - - provider.reset(); - assert!(provider.get_token().is_none()); - assert!(provider.needs_refresh()); - } - - #[test] - fn test_concurrent_access() { - let provider = Arc::new(OAuthTokenProvider::new( - "client_id".to_string(), - "client_secret".to_string(), - )); - let fetch_count = Arc::new(AtomicUsize::new(0)); - - let mut handles = vec![]; - - for _ in 0..10 { - let provider_clone = Arc::clone(&provider); - let fetch_count_clone = Arc::clone(&fetch_count); - - let handle = thread::spawn(move || { - let result: Result = provider_clone.get_or_fetch(|| { - fetch_count_clone.fetch_add(1, Ordering::SeqCst); - // Simulate some work - thread::sleep(Duration::from_millis(10)); - Ok(("concurrent_token".to_string(), 3600)) - }); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "concurrent_token"); - }); - - handles.push(handle); - } - - for handle in handles { - handle.join().unwrap(); - } - - // Due to double-checked locking, fetch should only be called once - // (or at most a few times if threads race before the first fetch completes) - let count = fetch_count.load(Ordering::SeqCst); - assert!(count >= 1 && count <= 3, "Fetch was called {} times", count); - } -} diff --git a/seed/rust-sdk/allof-inline/src/core/pagination.rs b/seed/rust-sdk/allof-inline/src/core/pagination.rs deleted file mode 100644 index 6b41f78a4858..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/pagination.rs +++ /dev/null @@ -1,541 +0,0 @@ -use std::collections::VecDeque; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; - -use futures::Stream; -use serde_json::Value; - -use crate::{ApiError, HttpClient}; - -/// Result of a pagination request -#[derive(Debug)] -pub struct PaginationResult { - pub items: Vec, - pub next_cursor: Option, - pub has_next_page: bool, -} - -/// Async paginator that implements Stream for iterating over paginated results -pub struct AsyncPaginator { - http_client: Arc, - page_loader: Box< - dyn Fn( - Arc, - Option, - ) - -> Pin, ApiError>> + Send>> - + Send - + Sync, - >, - current_page: VecDeque, - current_cursor: Option, - has_next_page: bool, - loading_next: - Option, ApiError>> + Send>>>, -} - -impl AsyncPaginator { - pub fn new( - http_client: Arc, - page_loader: F, - initial_cursor: Option, - ) -> Result - where - F: Fn(Arc, Option) -> Fut + Send + Sync + 'static, - Fut: Future, ApiError>> + Send + 'static, - { - Ok(Self { - http_client, - page_loader: Box::new(move |client, cursor| Box::pin(page_loader(client, cursor))), - current_page: VecDeque::new(), - current_cursor: initial_cursor, - has_next_page: true, // Assume true initially, will be updated after first request - loading_next: None, - }) - } - - /// Check if there are more pages available - pub fn has_next_page(&self) -> bool { - !self.current_page.is_empty() || self.has_next_page - } - - /// Load the next page explicitly - pub async fn next_page(&mut self) -> Result, ApiError> { - if !self.has_next_page { - return Ok(Vec::new()); - } - - let result = - (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()).await?; - - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - - Ok(result.items) - } -} - -impl Stream for AsyncPaginator -where - T: Unpin, -{ - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - // If we have items in the current page, return the next one - if let Some(item) = self.current_page.pop_front() { - return Poll::Ready(Some(Ok(item))); - } - - // If we're already loading the next page, poll that future - if let Some(ref mut loading_future) = self.loading_next { - match loading_future.as_mut().poll(cx) { - Poll::Ready(Ok(result)) => { - self.current_page.extend(result.items); - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - self.loading_next = None; - - // Try to get the next item from the newly loaded page - if let Some(item) = self.current_page.pop_front() { - return Poll::Ready(Some(Ok(item))); - } else if !self.has_next_page { - return Poll::Ready(None); - } - // Fall through to start loading next page - } - Poll::Ready(Err(e)) => { - self.loading_next = None; - return Poll::Ready(Some(Err(e))); - } - Poll::Pending => return Poll::Pending, - } - } - - // If we have no more pages to load, we're done - if !self.has_next_page { - return Poll::Ready(None); - } - - // Start loading the next page - let future = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()); - self.loading_next = Some(future); - - // Poll the future immediately - if let Some(ref mut loading_future) = self.loading_next { - match loading_future.as_mut().poll(cx) { - Poll::Ready(Ok(result)) => { - self.current_page.extend(result.items); - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - self.loading_next = None; - - if let Some(item) = self.current_page.pop_front() { - Poll::Ready(Some(Ok(item))) - } else if !self.has_next_page { - Poll::Ready(None) - } else { - // This shouldn't happen, but just in case - cx.waker().wake_by_ref(); - Poll::Pending - } - } - Poll::Ready(Err(e)) => { - self.loading_next = None; - Poll::Ready(Some(Err(e))) - } - Poll::Pending => Poll::Pending, - } - } else { - Poll::Pending - } - } -} - -/// Synchronous paginator for blocking iteration -pub struct SyncPaginator { - http_client: Arc, - page_loader: Box< - dyn Fn(Arc, Option) -> Result, ApiError> - + Send - + Sync, - >, - current_page: VecDeque, - current_cursor: Option, - has_next_page: bool, -} - -impl SyncPaginator { - pub fn new( - http_client: Arc, - page_loader: F, - initial_cursor: Option, - ) -> Result - where - F: Fn(Arc, Option) -> Result, ApiError> - + Send - + Sync - + 'static, - { - Ok(Self { - http_client, - page_loader: Box::new(page_loader), - current_page: VecDeque::new(), - current_cursor: initial_cursor, - has_next_page: true, // Assume true initially - }) - } - - /// Check if there are more pages available - pub fn has_next_page(&self) -> bool { - !self.current_page.is_empty() || self.has_next_page - } - - /// Load the next page explicitly - pub fn next_page(&mut self) -> Result, ApiError> { - if !self.has_next_page { - return Ok(Vec::new()); - } - - let result = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone())?; - - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - - Ok(result.items) - } - - /// Get all remaining items by loading all pages - pub fn collect_all(&mut self) -> Result, ApiError> { - let mut all_items = Vec::new(); - - // Add items from current page - while let Some(item) = self.current_page.pop_front() { - all_items.push(item); - } - - // Load all remaining pages - while self.has_next_page { - let page_items = self.next_page()?; - all_items.extend(page_items); - } - - Ok(all_items) - } -} - -impl Iterator for SyncPaginator { - type Item = Result; - - fn next(&mut self) -> Option { - // If we have items in the current page, return the next one - if let Some(item) = self.current_page.pop_front() { - return Some(Ok(item)); - } - - // If we have no more pages to load, we're done - if !self.has_next_page { - return None; - } - - // Load the next page - match (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()) { - Ok(result) => { - self.current_page.extend(result.items); - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - - // Return the first item from the newly loaded page - self.current_page.pop_front().map(Ok) - } - Err(e) => Some(Err(e)), - } - } -} - -/// Trait for types that can provide pagination metadata -pub trait Paginated { - /// Extract the items from this page - fn items(&self) -> &[T]; - - /// Get the cursor for the next page, if any - fn next_cursor(&self) -> Option<&str>; - - /// Check if there's a next page available - fn has_next_page(&self) -> bool; -} - -/// Trait for types that can provide offset-based pagination metadata -pub trait OffsetPaginated { - /// Extract the items from this page - fn items(&self) -> &[T]; - - /// Check if there's a next page available - fn has_next_page(&self) -> bool; - - /// Get the current page size (for calculating next offset) - fn page_size(&self) -> usize { - self.items().len() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ClientConfig; - - fn make_http_client() -> Arc { - Arc::new(HttpClient::new(ClientConfig::default()).expect("Failed to create test HttpClient")) - } - - // =========================== - // SyncPaginator tests - // =========================== - - #[test] - fn test_sync_paginator_has_next_page_initially() { - let client = make_http_client(); - let paginator = SyncPaginator::::new(client, |_client, _cursor| { - Ok(PaginationResult { - items: vec![], - next_cursor: None, - has_next_page: false, - }) - }, None).unwrap(); - assert!(paginator.has_next_page()); - } - - #[test] - fn test_sync_paginator_single_page() { - let client = make_http_client(); - let mut paginator = SyncPaginator::new(client, |_client, _cursor| { - Ok(PaginationResult { - items: vec!["a".to_string(), "b".to_string()], - next_cursor: None, - has_next_page: false, - }) - }, None).unwrap(); - - let page = paginator.next_page().unwrap(); - assert_eq!(page, vec!["a".to_string(), "b".to_string()]); - assert!(!paginator.has_next_page()); - } - - #[test] - fn test_sync_paginator_exhausted_returns_empty() { - let client = make_http_client(); - let mut paginator = SyncPaginator::new(client, |_client, _cursor| { - Ok(PaginationResult { - items: vec!["a".to_string()], - next_cursor: None, - has_next_page: false, - }) - }, None).unwrap(); - - let _ = paginator.next_page().unwrap(); - let empty = paginator.next_page().unwrap(); - assert!(empty.is_empty()); - } - - #[test] - fn test_sync_paginator_multiple_pages() { - let client = make_http_client(); - let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let count = call_count.clone(); - - let mut paginator = SyncPaginator::new(client, move |_client, cursor| { - let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - match call { - 0 => { - assert!(cursor.is_none()); - Ok(PaginationResult { - items: vec![1, 2], - next_cursor: Some("page2".to_string()), - has_next_page: true, - }) - } - 1 => { - assert_eq!(cursor, Some("page2".to_string())); - Ok(PaginationResult { - items: vec![3, 4], - next_cursor: None, - has_next_page: false, - }) - } - _ => panic!("Unexpected call"), - } - }, None).unwrap(); - - let page1 = paginator.next_page().unwrap(); - assert_eq!(page1, vec![1, 2]); - assert!(paginator.has_next_page()); - - let page2 = paginator.next_page().unwrap(); - assert_eq!(page2, vec![3, 4]); - assert!(!paginator.has_next_page()); - } - - #[test] - fn test_sync_paginator_collect_all() { - let client = make_http_client(); - let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let count = call_count.clone(); - - let mut paginator = SyncPaginator::new(client, move |_client, _cursor| { - let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - match call { - 0 => Ok(PaginationResult { - items: vec![1, 2], - next_cursor: Some("next".to_string()), - has_next_page: true, - }), - 1 => Ok(PaginationResult { - items: vec![3], - next_cursor: None, - has_next_page: false, - }), - _ => panic!("Unexpected call"), - } - }, None).unwrap(); - - let all = paginator.collect_all().unwrap(); - assert_eq!(all, vec![1, 2, 3]); - } - - #[test] - fn test_sync_paginator_iterator() { - let client = make_http_client(); - let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let count = call_count.clone(); - - let paginator = SyncPaginator::new(client, move |_client, _cursor| { - let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - match call { - 0 => Ok(PaginationResult { - items: vec![10, 20], - next_cursor: Some("p2".to_string()), - has_next_page: true, - }), - 1 => Ok(PaginationResult { - items: vec![30], - next_cursor: None, - has_next_page: false, - }), - _ => panic!("Unexpected call"), - } - }, None).unwrap(); - - let items: Vec = paginator.map(|r| r.unwrap()).collect(); - assert_eq!(items, vec![10, 20, 30]); - } - - #[test] - fn test_sync_paginator_error_propagation() { - let client = make_http_client(); - let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { - Err(ApiError::Serialization("test error".to_string())) - }, None).unwrap(); - - let result = paginator.next_page(); - assert!(result.is_err()); - } - - #[test] - fn test_sync_paginator_iterator_error() { - let client = make_http_client(); - let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { - Err(ApiError::Serialization("test error".to_string())) - }, None).unwrap(); - - let item = paginator.next(); - assert!(item.is_some()); - assert!(item.unwrap().is_err()); - } - - #[test] - fn test_sync_paginator_with_initial_cursor() { - let client = make_http_client(); - let mut paginator = SyncPaginator::new(client, |_client, cursor| { - assert_eq!(cursor, Some("start_here".to_string())); - Ok(PaginationResult { - items: vec!["item".to_string()], - next_cursor: None, - has_next_page: false, - }) - }, Some("start_here".to_string())).unwrap(); - - let page = paginator.next_page().unwrap(); - assert_eq!(page, vec!["item".to_string()]); - } - - // =========================== - // PaginationResult tests - // =========================== - - #[test] - fn test_pagination_result_fields() { - let result = PaginationResult { - items: vec![1, 2, 3], - next_cursor: Some("abc".to_string()), - has_next_page: true, - }; - assert_eq!(result.items.len(), 3); - assert_eq!(result.next_cursor, Some("abc".to_string())); - assert!(result.has_next_page); - } - - // =========================== - // Trait tests - // =========================== - - struct MockPage { - data: Vec, - cursor: Option, - has_more: bool, - } - - impl Paginated for MockPage { - fn items(&self) -> &[String] { - &self.data - } - fn next_cursor(&self) -> Option<&str> { - self.cursor.as_deref() - } - fn has_next_page(&self) -> bool { - self.has_more - } - } - - impl OffsetPaginated for MockPage { - fn items(&self) -> &[String] { - &self.data - } - fn has_next_page(&self) -> bool { - self.has_more - } - } - - #[test] - fn test_paginated_trait() { - let page = MockPage { - data: vec!["a".to_string(), "b".to_string()], - cursor: Some("next".to_string()), - has_more: true, - }; - assert_eq!(Paginated::items(&page).len(), 2); - assert_eq!(page.next_cursor(), Some("next")); - assert!(Paginated::has_next_page(&page)); - } - - #[test] - fn test_offset_paginated_default_page_size() { - let page = MockPage { - data: vec!["a".to_string(), "b".to_string(), "c".to_string()], - cursor: None, - has_more: false, - }; - assert_eq!(OffsetPaginated::page_size(&page), 3); - } -} diff --git a/seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs b/seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs deleted file mode 100644 index 6f1a6976ec2b..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/query_parameter_builder.rs +++ /dev/null @@ -1,576 +0,0 @@ -use chrono::{DateTime, TimeZone}; -use serde::Serialize; - -/// Modern query builder with type-safe method chaining -/// Provides a clean, Swift-like API for building HTTP query parameters -#[derive(Debug, Default)] -pub struct QueryBuilder { - params: Vec<(String, String)>, -} - -impl QueryBuilder { - /// Create a new query parameter builder - pub fn new() -> Self { - Self::default() - } - - /// Add a string parameter (accept both required/optional) - pub fn string(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v)); - } - self - } - - /// Add multiple string parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn string_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v)); - } - } - self - } - - /// Add an integer parameter (accept both required/optional) - pub fn int(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - self - } - - /// Add multiple integer parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn int_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - } - self - } - - /// Add a float parameter - pub fn float(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - self - } - - /// Add multiple float parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn float_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - } - self - } - - /// Add a boolean parameter - pub fn bool(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - self - } - - /// Add multiple boolean parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn bool_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - } - self - } - - /// Add a datetime parameter (any DateTime timezone) - pub fn datetime( - mut self, - key: &str, - value: impl Into>>, - ) -> Self - where - Tz::Offset: std::fmt::Display, - { - if let Some(v) = value.into() { - self.params.push(( - key.to_string(), - v.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - )); - } - self - } - - /// Add a date parameter (converts NaiveDate to DateTime) - pub fn date(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - // Convert NaiveDate to DateTime at start of day - let datetime = v.and_hms_opt(0, 0, 0).unwrap().and_utc(); - self.params.push(( - key.to_string(), - datetime.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - )); - } - self - } - - /// Add any serializable parameter (for enums and complex types) - pub fn serialize(mut self, key: &str, value: Option) -> Self { - if let Some(v) = value { - // For enums that implement Display, use the Display implementation - // to avoid JSON quotes in query parameters - if let Ok(serialized) = serde_json::to_string(&v) { - // Remove JSON quotes if the value is a simple string - let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { - serialized.trim_matches('"').to_string() - } else { - serialized - }; - self.params.push((key.to_string(), cleaned)); - } - } - self - } - - /// Add multiple serializable parameters with the same key (for allow-multiple query params with enums) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn serialize_array( - mut self, - key: &str, - values: impl IntoIterator, - ) -> Self { - for value in values { - if let Ok(serialized) = serde_json::to_string(&value) { - // Skip null values (from Option::None) - if serialized == "null" { - continue; - } - // Remove JSON quotes if the value is a simple string - let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { - serialized.trim_matches('"').to_string() - } else { - serialized - }; - self.params.push((key.to_string(), cleaned)); - } - } - self - } - - /// Parse and add a structured query string - /// Handles complex query patterns like: - /// - "key:value" patterns - /// - "key:value1,value2" (comma-separated values) - /// - Quoted values: "key:\"value with spaces\"" - /// - Space-separated terms (treated as AND logic) - pub fn structured_query(mut self, key: &str, value: impl Into>) -> Self { - if let Some(query_str) = value.into() { - if let Ok(parsed_params) = parse_structured_query(&query_str) { - self.params.extend(parsed_params); - } else { - // Fall back to simple query parameter if parsing fails - self.params.push((key.to_string(), query_str)); - } - } - self - } - - /// Build the final query parameters - pub fn build(self) -> Option> { - if self.params.is_empty() { - None - } else { - Some(self.params) - } - } -} - -/// Errors that can occur during structured query parsing -#[derive(Debug)] -pub enum QueryBuilderError { - InvalidQuerySyntax(String), -} - -impl std::fmt::Display for QueryBuilderError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - QueryBuilderError::InvalidQuerySyntax(msg) => { - write!(f, "Invalid query syntax: {}", msg) - } - } - } -} - -impl std::error::Error for QueryBuilderError {} - -/// Parse structured query strings like "key:value key2:value1,value2" -/// Used for complex filtering patterns in APIs like Foxglove -/// -/// Supported patterns: -/// - Simple: "status:active" -/// - Multiple values: "type:sensor,camera" -/// - Quoted values: "location:\"New York\"" -/// - Complex: "status:active type:sensor location:\"San Francisco\"" -pub fn parse_structured_query(query: &str) -> Result, QueryBuilderError> { - let mut params = Vec::new(); - let terms = tokenize_query(query); - - for term in terms { - if let Some((key, values)) = term.split_once(':') { - // Handle comma-separated values - for value in values.split(',') { - let clean_value = value.trim_matches('"'); // Remove quotes - params.push((key.to_string(), clean_value.to_string())); - } - } else { - // For terms without colons, return error to be explicit about expected format - return Err(QueryBuilderError::InvalidQuerySyntax(format!( - "Cannot parse term '{}' - expected 'key:value' format for structured queries", - term - ))); - } - } - - Ok(params) -} - -/// Tokenize a query string, properly handling quoted strings -fn tokenize_query(input: &str) -> Vec { - let mut tokens = Vec::new(); - let mut current_token = String::new(); - let mut in_quotes = false; - let mut chars = input.chars().peekable(); - - while let Some(c) = chars.next() { - match c { - '"' => { - // Toggle quote state and include the quote in the token - in_quotes = !in_quotes; - current_token.push(c); - } - ' ' if !in_quotes => { - // Space outside quotes - end current token - if !current_token.is_empty() { - tokens.push(current_token.trim().to_string()); - current_token.clear(); - } - } - _ => { - // Any other character (including spaces inside quotes) - current_token.push(c); - } - } - } - - // Add the last token if there is one - if !current_token.is_empty() { - tokens.push(current_token.trim().to_string()); - } - - tokens -} - -#[cfg(test)] -mod tests { - use super::*; - use chrono::{NaiveDate, TimeZone, Utc}; - - // =========================== - // QueryBuilder tests - // =========================== - - #[test] - fn test_empty_builder_returns_none() { - let result = QueryBuilder::new().build(); - assert!(result.is_none()); - } - - #[test] - fn test_string_param_some() { - let result = QueryBuilder::new() - .string("name", Some("alice".to_string())) - .build(); - assert_eq!( - result, - Some(vec![("name".to_string(), "alice".to_string())]) - ); - } - - #[test] - fn test_string_param_none_skipped() { - let result = QueryBuilder::new().string("name", None::).build(); - assert!(result.is_none()); - } - - #[test] - fn test_int_param() { - let result = QueryBuilder::new().int("page", Some(42i64)).build(); - assert_eq!(result, Some(vec![("page".to_string(), "42".to_string())])); - } - - #[test] - fn test_int_param_none_skipped() { - let result = QueryBuilder::new().int("page", None::).build(); - assert!(result.is_none()); - } - - #[test] - fn test_float_param() { - let result = QueryBuilder::new().float("score", Some(3.14f64)).build(); - assert_eq!( - result, - Some(vec![("score".to_string(), "3.14".to_string())]) - ); - } - - #[test] - fn test_bool_param() { - let result = QueryBuilder::new().bool("active", Some(true)).build(); - assert_eq!( - result, - Some(vec![("active".to_string(), "true".to_string())]) - ); - } - - #[test] - fn test_datetime_param_formats_rfc3339() { - let dt = Utc.with_ymd_and_hms(2024, 1, 15, 9, 30, 0).unwrap(); - let result = QueryBuilder::new().datetime("since", Some(dt)).build(); - assert_eq!( - result, - Some(vec![( - "since".to_string(), - "2024-01-15T09:30:00Z".to_string() - )]) - ); - } - - #[test] - fn test_date_param_converts_to_midnight_utc() { - let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(); - let result = QueryBuilder::new().date("on", Some(date)).build(); - assert_eq!( - result, - Some(vec![("on".to_string(), "2024-01-15T00:00:00Z".to_string())]) - ); - } - - #[test] - fn test_string_array_multiple_entries() { - let result = QueryBuilder::new() - .string_array( - "tag", - vec!["a".to_string(), "b".to_string(), "c".to_string()], - ) - .build(); - assert_eq!( - result, - Some(vec![ - ("tag".to_string(), "a".to_string()), - ("tag".to_string(), "b".to_string()), - ("tag".to_string(), "c".to_string()), - ]) - ); - } - - #[test] - fn test_int_array() { - let result = QueryBuilder::new() - .int_array("ids", vec![1i64, 2, 3]) - .build(); - assert_eq!( - result, - Some(vec![ - ("ids".to_string(), "1".to_string()), - ("ids".to_string(), "2".to_string()), - ("ids".to_string(), "3".to_string()), - ]) - ); - } - - #[test] - fn test_float_array() { - let result = QueryBuilder::new() - .float_array("scores", vec![1.1f64, 2.2]) - .build(); - assert_eq!( - result, - Some(vec![ - ("scores".to_string(), "1.1".to_string()), - ("scores".to_string(), "2.2".to_string()), - ]) - ); - } - - #[test] - fn test_bool_array() { - let result = QueryBuilder::new() - .bool_array("flags", vec![true, false]) - .build(); - assert_eq!( - result, - Some(vec![ - ("flags".to_string(), "true".to_string()), - ("flags".to_string(), "false".to_string()), - ]) - ); - } - - #[test] - fn test_serialize_strips_json_quotes() { - let result = QueryBuilder::new() - .serialize("status", Some("active")) - .build(); - assert_eq!( - result, - Some(vec![("status".to_string(), "active".to_string())]) - ); - } - - #[test] - fn test_serialize_none_skipped() { - let result = QueryBuilder::new() - .serialize::("status", None) - .build(); - assert!(result.is_none()); - } - - #[test] - fn test_serialize_numeric_no_quotes() { - let result = QueryBuilder::new().serialize("count", Some(42)).build(); - assert_eq!(result, Some(vec![("count".to_string(), "42".to_string())])); - } - - #[test] - fn test_serialize_array_skips_null() { - let values: Vec> = vec![Some("a"), None, Some("b")]; - let result = QueryBuilder::new().serialize_array("items", values).build(); - assert_eq!( - result, - Some(vec![ - ("items".to_string(), "a".to_string()), - ("items".to_string(), "b".to_string()), - ]) - ); - } - - #[test] - fn test_method_chaining() { - let result = QueryBuilder::new() - .string("name", Some("alice".to_string())) - .int("page", Some(1i64)) - .bool("active", Some(true)) - .build(); - assert_eq!( - result, - Some(vec![ - ("name".to_string(), "alice".to_string()), - ("page".to_string(), "1".to_string()), - ("active".to_string(), "true".to_string()), - ]) - ); - } - - // =========================== - // parse_structured_query tests - // =========================== - - #[test] - fn test_parse_simple_key_value() { - let result = parse_structured_query("status:active").unwrap(); - assert_eq!(result, vec![("status".to_string(), "active".to_string())]); - } - - #[test] - fn test_parse_comma_separated_values() { - let result = parse_structured_query("type:sensor,camera").unwrap(); - assert_eq!( - result, - vec![ - ("type".to_string(), "sensor".to_string()), - ("type".to_string(), "camera".to_string()), - ] - ); - } - - #[test] - fn test_parse_multiple_terms() { - let result = parse_structured_query("status:active type:sensor").unwrap(); - assert_eq!( - result, - vec![ - ("status".to_string(), "active".to_string()), - ("type".to_string(), "sensor".to_string()), - ] - ); - } - - #[test] - fn test_parse_quoted_value() { - let result = parse_structured_query("location:\"New York\"").unwrap(); - assert_eq!( - result, - vec![("location".to_string(), "New York".to_string())] - ); - } - - #[test] - fn test_parse_bare_word_returns_error() { - let result = parse_structured_query("bareword"); - assert!(result.is_err()); - } - - #[test] - fn test_structured_query_builder_fallback() { - // When parsing fails, structured_query falls back to simple param - let result = QueryBuilder::new() - .structured_query("q", Some("bareword".to_string())) - .build(); - assert_eq!( - result, - Some(vec![("q".to_string(), "bareword".to_string())]) - ); - } - - #[test] - fn test_structured_query_builder_parses() { - let result = QueryBuilder::new() - .structured_query("q", Some("status:active".to_string())) - .build(); - assert_eq!( - result, - Some(vec![("status".to_string(), "active".to_string())]) - ); - } - - #[test] - fn test_structured_query_none_skipped() { - let result = QueryBuilder::new() - .structured_query("q", None::) - .build(); - assert!(result.is_none()); - } -} diff --git a/seed/rust-sdk/allof-inline/src/core/request_options.rs b/seed/rust-sdk/allof-inline/src/core/request_options.rs deleted file mode 100644 index 80508c9dd6c3..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/request_options.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::collections::HashMap; -/// Options for customizing individual requests -#[derive(Debug, Clone, Default)] -pub struct RequestOptions { - /// API key for authentication (overrides client-level API key) - pub api_key: Option, - /// Bearer token for authentication (overrides client-level token) - pub token: Option, - /// Maximum number of retry attempts for failed requests - pub max_retries: Option, - /// Request timeout in seconds (overrides client-level timeout) - pub timeout_seconds: Option, - /// Additional headers to include in the request - pub additional_headers: HashMap, - /// Additional query parameters to include in the request - pub additional_query_params: HashMap, -} - -impl RequestOptions { - pub fn new() -> Self { - Self::default() - } - - pub fn api_key(mut self, key: impl Into) -> Self { - self.api_key = Some(key.into()); - self - } - - pub fn token(mut self, token: impl Into) -> Self { - self.token = Some(token.into()); - self - } - - pub fn max_retries(mut self, retries: u32) -> Self { - self.max_retries = Some(retries); - self - } - - pub fn timeout_seconds(mut self, timeout: u64) -> Self { - self.timeout_seconds = Some(timeout); - self - } - - pub fn additional_header(mut self, key: impl Into, value: impl Into) -> Self { - self.additional_headers.insert(key.into(), value.into()); - self - } - - pub fn additional_query_param( - mut self, - key: impl Into, - value: impl Into, - ) -> Self { - self.additional_query_params - .insert(key.into(), value.into()); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_default_has_no_values() { - let opts = RequestOptions::default(); - assert!(opts.api_key.is_none()); - assert!(opts.token.is_none()); - assert!(opts.max_retries.is_none()); - assert!(opts.timeout_seconds.is_none()); - assert!(opts.additional_headers.is_empty()); - assert!(opts.additional_query_params.is_empty()); - } - - #[test] - fn test_new_equals_default() { - let opts = RequestOptions::new(); - assert!(opts.api_key.is_none()); - assert!(opts.token.is_none()); - assert!(opts.max_retries.is_none()); - assert!(opts.timeout_seconds.is_none()); - assert!(opts.additional_headers.is_empty()); - assert!(opts.additional_query_params.is_empty()); - } - - #[test] - fn test_api_key() { - let opts = RequestOptions::new().api_key("my-key"); - assert_eq!(opts.api_key, Some("my-key".to_string())); - } - - #[test] - fn test_token() { - let opts = RequestOptions::new().token("my-token"); - assert_eq!(opts.token, Some("my-token".to_string())); - } - - #[test] - fn test_max_retries() { - let opts = RequestOptions::new().max_retries(3); - assert_eq!(opts.max_retries, Some(3)); - } - - #[test] - fn test_timeout_seconds() { - let opts = RequestOptions::new().timeout_seconds(30); - assert_eq!(opts.timeout_seconds, Some(30)); - } - - #[test] - fn test_additional_header() { - let opts = RequestOptions::new().additional_header("X-Custom", "value"); - assert_eq!( - opts.additional_headers.get("X-Custom"), - Some(&"value".to_string()) - ); - } - - #[test] - fn test_additional_headers_accumulate() { - let opts = RequestOptions::new() - .additional_header("X-First", "1") - .additional_header("X-Second", "2"); - assert_eq!(opts.additional_headers.len(), 2); - assert_eq!( - opts.additional_headers.get("X-First"), - Some(&"1".to_string()) - ); - assert_eq!( - opts.additional_headers.get("X-Second"), - Some(&"2".to_string()) - ); - } - - #[test] - fn test_additional_query_param() { - let opts = RequestOptions::new().additional_query_param("page", "1"); - assert_eq!( - opts.additional_query_params.get("page"), - Some(&"1".to_string()) - ); - } - - #[test] - fn test_additional_query_params_accumulate() { - let opts = RequestOptions::new() - .additional_query_param("page", "1") - .additional_query_param("limit", "10"); - assert_eq!(opts.additional_query_params.len(), 2); - assert_eq!( - opts.additional_query_params.get("page"), - Some(&"1".to_string()) - ); - assert_eq!( - opts.additional_query_params.get("limit"), - Some(&"10".to_string()) - ); - } - - #[test] - fn test_full_method_chaining() { - let opts = RequestOptions::new() - .api_key("key") - .token("tok") - .max_retries(5) - .timeout_seconds(60) - .additional_header("X-Foo", "bar") - .additional_query_param("q", "search"); - assert_eq!(opts.api_key, Some("key".to_string())); - assert_eq!(opts.token, Some("tok".to_string())); - assert_eq!(opts.max_retries, Some(5)); - assert_eq!(opts.timeout_seconds, Some(60)); - assert_eq!(opts.additional_headers.len(), 1); - assert_eq!(opts.additional_query_params.len(), 1); - } -} diff --git a/seed/rust-sdk/allof-inline/src/core/utils.rs b/seed/rust-sdk/allof-inline/src/core/utils.rs deleted file mode 100644 index 323676f39ea2..000000000000 --- a/seed/rust-sdk/allof-inline/src/core/utils.rs +++ /dev/null @@ -1,77 +0,0 @@ -/// URL building utilities -/// Safely join a base URL with a path, handling slashes properly -/// -/// # Examples -/// ``` -/// use example_api::utils::url::join_url; -/// -/// assert_eq!(join_url("https://api.example.com", "users"), "https://api.example.com/users"); -/// assert_eq!(join_url("https://api.example.com/", "users"), "https://api.example.com/users"); -/// assert_eq!(join_url("https://api.example.com", "/users"), "https://api.example.com/users"); -/// assert_eq!(join_url("https://api.example.com/", "/users"), "https://api.example.com/users"); -/// ``` -pub fn join_url(base_url: &str, path: &str) -> String { - format!( - "{}/{}", - base_url.trim_end_matches('/'), - path.trim_start_matches('/') - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_join_url_no_slashes() { - assert_eq!( - join_url("https://api.example.com", "users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_trailing_slash_on_base() { - assert_eq!( - join_url("https://api.example.com/", "users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_leading_slash_on_path() { - assert_eq!( - join_url("https://api.example.com", "/users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_both_slashes() { - assert_eq!( - join_url("https://api.example.com/", "/users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_multi_segment_path() { - assert_eq!( - join_url("https://api.example.com", "v1/users/123"), - "https://api.example.com/v1/users/123" - ); - } - - #[test] - fn test_join_url_empty_path() { - assert_eq!( - join_url("https://api.example.com", ""), - "https://api.example.com/" - ); - } - - #[test] - fn test_join_url_empty_base() { - assert_eq!(join_url("", "users"), "/users"); - } -} diff --git a/seed/rust-sdk/allof-inline/src/environment.rs b/seed/rust-sdk/allof-inline/src/environment.rs deleted file mode 100644 index 9034ff803954..000000000000 --- a/seed/rust-sdk/allof-inline/src/environment.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum Environment { - #[serde(rename = "default")] - Default, -} -impl Environment { - pub fn url(&self) -> &'static str { - match self { - Self::Default => "https://api.example.com", - } - } -} -impl Default for Environment { - fn default() -> Self { - Self::Default - } -} diff --git a/seed/rust-sdk/allof-inline/src/error.rs b/seed/rust-sdk/allof-inline/src/error.rs deleted file mode 100644 index 655d945d71f1..000000000000 --- a/seed/rust-sdk/allof-inline/src/error.rs +++ /dev/null @@ -1,54 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ApiError { - #[error("HTTP error {status}: {message}")] - Http { status: u16, message: String }, - #[error("Network error: {0}")] - Network(reqwest::Error), - #[error("Serialization error: {0}")] - Serialization(serde_json::Error), - #[error("Configuration error: {0}")] - Configuration(String), - #[error("Invalid header value")] - InvalidHeader, - #[error("Could not clone request for retry")] - RequestClone, - #[error("SSE stream terminated")] - StreamTerminated, - #[error("SSE stream timed out waiting for next event")] - StreamTimeout, - #[error("SSE parse error: {0}")] - SseParseError(String), -} - -impl ApiError { - pub fn from_response(status_code: u16, body: Option<&str>) -> Self { - match status_code { - _ => Self::Http { - status: status_code, - message: body.unwrap_or("Unknown error").to_string(), - }, - } - } -} - -/// Error returned when a required field was not set on a builder. -#[derive(Debug)] -pub struct BuildError { - field: &'static str, -} - -impl BuildError { - pub fn missing_field(field: &'static str) -> Self { - Self { field } - } -} - -impl std::fmt::Display for BuildError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "`{}` was not set but is required", self.field) - } -} - -impl std::error::Error for BuildError {} diff --git a/seed/rust-sdk/allof-inline/src/lib.rs b/seed/rust-sdk/allof-inline/src/lib.rs deleted file mode 100644 index 626894135f95..000000000000 --- a/seed/rust-sdk/allof-inline/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! # allOf Composition SDK -//! -//! The official Rust SDK for the allOf Composition. -//! -//! ## Getting Started -//! -//! ```rust -//! use seed_api::prelude::*; -//! -//! #[tokio::main] -//! async fn main() { -//! let config = ClientConfig { -//! ..Default::default() -//! }; -//! let client = ApiClient::new(config).expect("Failed to build client"); -//! client -//! .search_rule_types( -//! &SearchRuleTypesQueryRequest { -//! ..Default::default() -//! }, -//! None, -//! ) -//! .await; -//! } -//! ``` -//! -//! ## Modules -//! -//! - [`api`] - Core API types and models -//! - [`client`] - Client implementations -//! - [`config`] - Configuration options -//! - [`core`] - Core utilities and infrastructure -//! - [`error`] - Error types and handling -//! - [`prelude`] - Common imports for convenience - -pub mod api; -pub mod client; -pub mod config; -pub mod core; -pub mod environment; -pub mod error; -pub mod prelude; - -pub use api::*; -pub use client::*; -pub use config::*; -pub use core::*; -pub use environment::*; -pub use error::{ApiError, BuildError}; diff --git a/seed/rust-sdk/allof-inline/src/prelude.rs b/seed/rust-sdk/allof-inline/src/prelude.rs deleted file mode 100644 index 5656ffc7e1b7..000000000000 --- a/seed/rust-sdk/allof-inline/src/prelude.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Prelude module for convenient imports -//! -//! This module re-exports the most commonly used types and traits. -//! Import it with: `use seed_api::prelude::*;` - -// Client and configuration -pub use crate::config::ClientConfig; -pub use crate::core::{HttpClient, RequestOptions}; -pub use crate::error::{ApiError, BuildError}; - -// Main client and resource clients -pub use crate::api::*; - -// Re-export commonly used external types -pub use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, Utc}; -pub use serde::{Deserialize, Serialize}; -pub use serde_json::{json, Value}; -pub use std::collections::{HashMap, HashSet}; -pub use std::fmt; diff --git a/seed/rust-sdk/allof/.fern/metadata.json b/seed/rust-sdk/allof/.fern/metadata.json deleted file mode 100644 index 8cc2145de5c0..000000000000 --- a/seed/rust-sdk/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-rust-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/rust-sdk/allof/.github/workflows/ci.yml b/seed/rust-sdk/allof/.github/workflows/ci.yml deleted file mode 100644 index 4e582a9fa4e3..000000000000 --- a/seed/rust-sdk/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -env: - RUSTFLAGS: "-A warnings" - -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Check - run: cargo check - - compile: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Compile - run: cargo build - - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Test - run: cargo test - - publish: - needs: [check, compile, test] - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Publish - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish diff --git a/seed/rust-sdk/allof/.gitignore b/seed/rust-sdk/allof/.gitignore deleted file mode 100644 index f75d27799da1..000000000000 --- a/seed/rust-sdk/allof/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/target -**/*.rs.bk -Cargo.lock -.DS_Store -*.swp \ No newline at end of file diff --git a/seed/rust-sdk/allof/Cargo.toml b/seed/rust-sdk/allof/Cargo.toml deleted file mode 100644 index ce5837604a74..000000000000 --- a/seed/rust-sdk/allof/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "seed_api" -version = "0.0.1" -edition = "2021" -description = "Rust SDK for seed_api generated by Fern" -license = "MIT" -repository = "https://github.com/fern-api/fern" -documentation = "https://docs.rs/seed_api" - -[lib] -doctest = false - -[dependencies] -bytes = "1.0" -chrono = { version = "0.4", features = ["serde"] } -futures = "0.3" -reqwest = { version = "0.12", features = ["json", "stream"], default-features = false } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" -tokio = { version = "1.0", features = ["full"] } - -[dev-dependencies] -tokio-test = "0.4" - diff --git a/seed/rust-sdk/allof/README.md b/seed/rust-sdk/allof/README.md deleted file mode 100644 index 73c2a4769236..000000000000 --- a/seed/rust-sdk/allof/README.md +++ /dev/null @@ -1,181 +0,0 @@ -# Seed Rust Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FRust) -[![crates.io shield](https://img.shields.io/crates/v/seed_api)](https://crates.io/crates/seed_api) - -The Seed Rust library provides convenient access to the Seed APIs from Rust. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Request Types](#request-types) -- [Advanced](#advanced) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) -- [Contributing](#contributing) - -## Installation - -Add this to your `Cargo.toml`: - -```toml -[dependencies] -seed_api = "0.0.1" -``` - -Or install via cargo: - -```sh -cargo add seed_api -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```rust -use seed_api::prelude::{*}; - -let config = ClientConfig { - base_url: Environment::Default.url().to_string(), - ..Default::default() -}; -let client = Client::new(config).expect("Failed to build client"); -``` - -## Errors - -When the API returns a non-success status code (4xx or 5xx response), an error will be returned. - -```rust -match client.create_rule(None)?.await { - Ok(response) => { - println!("Success: {:?}", response); - }, - Err(ApiError::HTTP { status, message }) => { - println!("API Error {}: {:?}", status, message); - }, - Err(e) => { - println!("Other error: {:?}", e); - } -} -``` - -## Request Types - -The SDK exports all request types as Rust structs. Simply import them from the crate to access them: - -```rust -use seed_api::prelude::{*}; - -let request = RuleCreateRequest { - ... -}; -``` - -## Advanced - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `max_retries` method to configure this behavior. - -```rust -let response = client.create_rule( - Some(RequestOptions::new().max_retries(3)) -)?.await; -``` - -### Timeouts - -The SDK defaults to a 30 second timeout. Use the `timeout` method to configure this behavior. - -```rust -let response = client.create_rule( - Some(RequestOptions::new().timeout_seconds(30)) -)?.await; -``` - -### Additional Headers - -You can add custom headers to requests using `RequestOptions`. - -```rust -let response = client.create_rule( - Some( - RequestOptions::new() - .additional_header("X-Custom-Header", "custom-value") - .additional_header("X-Another-Header", "another-value") - ) -)? -.await; -``` - -### Additional Query String Parameters - -You can add custom query parameters to requests using `RequestOptions`. - -```rust -let response = client.create_rule( - Some( - RequestOptions::new() - .additional_query_param("filter", "active") - .additional_query_param("sort", "desc") - ) -)? -.await; -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/rust-sdk/allof/dynamic-snippets/example0.rs b/seed/rust-sdk/allof/dynamic-snippets/example0.rs deleted file mode 100644 index 6968951d8813..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example0.rs +++ /dev/null @@ -1,18 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .search_rule_types( - &SearchRuleTypesQueryRequest { - ..Default::default() - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example1.rs b/seed/rust-sdk/allof/dynamic-snippets/example1.rs deleted file mode 100644 index cf7e959c8d85..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example1.rs +++ /dev/null @@ -1,19 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .search_rule_types( - &SearchRuleTypesQueryRequest { - query: Some("query".to_string()), - ..Default::default() - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example2.rs b/seed/rust-sdk/allof/dynamic-snippets/example2.rs deleted file mode 100644 index db52ba2800ca..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example2.rs +++ /dev/null @@ -1,19 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example3.rs b/seed/rust-sdk/allof/dynamic-snippets/example3.rs deleted file mode 100644 index db52ba2800ca..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example3.rs +++ /dev/null @@ -1,19 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example4.rs b/seed/rust-sdk/allof/dynamic-snippets/example4.rs deleted file mode 100644 index 5afd9b2dd1e9..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example4.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.list_users(None).await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example5.rs b/seed/rust-sdk/allof/dynamic-snippets/example5.rs deleted file mode 100644 index 5afd9b2dd1e9..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example5.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.list_users(None).await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example6.rs b/seed/rust-sdk/allof/dynamic-snippets/example6.rs deleted file mode 100644 index 0f9ad4c50ad6..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example6.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_entity(None).await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example7.rs b/seed/rust-sdk/allof/dynamic-snippets/example7.rs deleted file mode 100644 index 0f9ad4c50ad6..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example7.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_entity(None).await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example8.rs b/seed/rust-sdk/allof/dynamic-snippets/example8.rs deleted file mode 100644 index 9025e9975095..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example8.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_organization(None).await; -} diff --git a/seed/rust-sdk/allof/dynamic-snippets/example9.rs b/seed/rust-sdk/allof/dynamic-snippets/example9.rs deleted file mode 100644 index 9025e9975095..000000000000 --- a/seed/rust-sdk/allof/dynamic-snippets/example9.rs +++ /dev/null @@ -1,11 +0,0 @@ -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - base_url: "https://api.fern.com".to_string(), - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_organization(None).await; -} diff --git a/seed/rust-sdk/allof/reference.md b/seed/rust-sdk/allof/reference.md deleted file mode 100644 index 482a8295a3fb..000000000000 --- a/seed/rust-sdk/allof/reference.md +++ /dev/null @@ -1,224 +0,0 @@ -# Reference -
client.search_rule_types(query: Option<Option<String>>) -> Result<RuleTypeSearchResponse, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .search_rule_types( - &SearchRuleTypesQueryRequest { - ..Default::default() - }, - None, - ) - .await; -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `Option` - -
-
-
-
- - -
-
-
- -
client.create_rule(request: RuleCreateRequest) -> Result<RuleResponse, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client - .create_rule( - &RuleCreateRequest { - name: "name".to_string(), - execution_context: RuleExecutionContext::Prod, - }, - None, - ) - .await; -} -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `String` - -
-
- -
-
- -**execution_context:** `RuleExecutionContext` - -
-
-
-
- - -
-
-
- -
client.list_users() -> Result<UserSearchResponse, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.list_users(None).await; -} -``` -
-
-
-
- - -
-
-
- -
client.get_entity() -> Result<CombinedEntity, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_entity(None).await; -} -``` -
-
-
-
- - -
-
-
- -
client.get_organization() -> Result<Organization, ApiError> -
-
- -#### 🔌 Usage - -
-
- -
-
- -```rust -use seed_api::prelude::*; - -#[tokio::main] -async fn main() { - let config = ClientConfig { - ..Default::default() - }; - let client = ApiClient::new(config).expect("Failed to build client"); - client.get_organization(None).await; -} -``` -
-
-
-
- - -
-
-
- diff --git a/seed/rust-sdk/allof/rustfmt.toml b/seed/rust-sdk/allof/rustfmt.toml deleted file mode 100644 index 872221fb31fe..000000000000 --- a/seed/rust-sdk/allof/rustfmt.toml +++ /dev/null @@ -1,4 +0,0 @@ -# Generated by Fern -edition = "2021" -max_width = 100 -use_small_heuristics = "Default" \ No newline at end of file diff --git a/seed/rust-sdk/allof/snippet.json b/seed/rust-sdk/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/rust-sdk/allof/src/api/mod.rs b/seed/rust-sdk/allof/src/api/mod.rs deleted file mode 100644 index 83cb9e2c92d4..000000000000 --- a/seed/rust-sdk/allof/src/api/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! API client and types for the allOf Composition -//! -//! This module contains all the API definitions including request/response types -//! and client implementations for interacting with the API. -//! -//! ## Modules -//! -//! - [`resources`] - Service clients and endpoints -//! - [`types`] - Request, response, and model types - -pub mod resources; -pub mod types; - -pub use resources::ApiClient; -pub use types::*; diff --git a/seed/rust-sdk/allof/src/api/resources/mod.rs b/seed/rust-sdk/allof/src/api/resources/mod.rs deleted file mode 100644 index 543ca7436f76..000000000000 --- a/seed/rust-sdk/allof/src/api/resources/mod.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! Service clients and API endpoints -//! -//! This module provides the client implementations for all available services. - -use crate::api::*; -use crate::{ApiError, ClientConfig, HttpClient, RequestOptions}; -use reqwest::Method; - -pub struct ApiClient { - pub config: ClientConfig, - pub http_client: HttpClient, -} - -impl ApiClient { - pub fn new(config: ClientConfig) -> Result { - Ok(Self { - config: config.clone(), - http_client: HttpClient::new(config.clone())?, - }) - } - - pub async fn search_rule_types( - &self, - request: &SearchRuleTypesQueryRequest, - options: Option, - ) -> Result { - self.http_client - .execute_request( - Method::GET, - "rule-types", - None, - QueryBuilder::new() - .structured_query("query", request.query.clone()) - .build(), - options, - ) - .await - } - - pub async fn create_rule( - &self, - request: &RuleCreateRequest, - options: Option, - ) -> Result { - self.http_client - .execute_request( - Method::POST, - "rules", - Some(serde_json::to_value(request).map_err(ApiError::Serialization)?), - None, - options, - ) - .await - } - - pub async fn list_users( - &self, - options: Option, - ) -> Result { - self.http_client - .execute_request(Method::GET, "users", None, None, options) - .await - } - - pub async fn get_entity( - &self, - options: Option, - ) -> Result { - self.http_client - .execute_request(Method::GET, "entities", None, None, options) - .await - } - - pub async fn get_organization( - &self, - options: Option, - ) -> Result { - self.http_client - .execute_request(Method::GET, "organizations", None, None, options) - .await - } -} diff --git a/seed/rust-sdk/allof/src/api/types/audit_info.rs b/seed/rust-sdk/allof/src/api/types/audit_info.rs deleted file mode 100644 index a958df434a50..000000000000 --- a/seed/rust-sdk/allof/src/api/types/audit_info.rs +++ /dev/null @@ -1,73 +0,0 @@ -pub use crate::prelude::*; - -/// Common audit metadata. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct AuditInfo { - /// The user who created this resource. - #[serde(rename = "createdBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub created_by: Option, - /// When this resource was created. - #[serde(rename = "createdDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub created_date_time: Option>, - /// The user who last modified this resource. - #[serde(rename = "modifiedBy")] - #[serde(skip_serializing_if = "Option::is_none")] - pub modified_by: Option, - /// When this resource was last modified. - #[serde(rename = "modifiedDateTime")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - #[serde(with = "crate::core::flexible_datetime::offset::option")] - pub modified_date_time: Option>, -} - -impl AuditInfo { - pub fn builder() -> AuditInfoBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct AuditInfoBuilder { - created_by: Option, - created_date_time: Option>, - modified_by: Option, - modified_date_time: Option>, -} - -impl AuditInfoBuilder { - pub fn created_by(mut self, value: impl Into) -> Self { - self.created_by = Some(value.into()); - self - } - - pub fn created_date_time(mut self, value: DateTime) -> Self { - self.created_date_time = Some(value); - self - } - - pub fn modified_by(mut self, value: impl Into) -> Self { - self.modified_by = Some(value.into()); - self - } - - pub fn modified_date_time(mut self, value: DateTime) -> Self { - self.modified_date_time = Some(value); - self - } - - /// Consumes the builder and constructs a [`AuditInfo`]. - pub fn build(self) -> Result { - Ok(AuditInfo { - created_by: self.created_by, - created_date_time: self.created_date_time, - modified_by: self.modified_by, - modified_date_time: self.modified_date_time, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/base_org.rs b/seed/rust-sdk/allof/src/api/types/base_org.rs deleted file mode 100644 index 1747ce8ded53..000000000000 --- a/seed/rust-sdk/allof/src/api/types/base_org.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrg { - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl BaseOrg { - pub fn builder() -> BaseOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgBuilder { - id: Option, - metadata: Option, -} - -impl BaseOrgBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`BaseOrg`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](BaseOrgBuilder::id) - pub fn build(self) -> Result { - Ok(BaseOrg { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/base_org_metadata.rs b/seed/rust-sdk/allof/src/api/types/base_org_metadata.rs deleted file mode 100644 index d1182fd85504..000000000000 --- a/seed/rust-sdk/allof/src/api/types/base_org_metadata.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct BaseOrgMetadata { - /// Deployment region from BaseOrg. - #[serde(default)] - pub region: String, - /// Subscription tier. - #[serde(skip_serializing_if = "Option::is_none")] - pub tier: Option, -} - -impl BaseOrgMetadata { - pub fn builder() -> BaseOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct BaseOrgMetadataBuilder { - region: Option, - tier: Option, -} - -impl BaseOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn tier(mut self, value: impl Into) -> Self { - self.tier = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`BaseOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](BaseOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(BaseOrgMetadata { - region: self - .region - .ok_or_else(|| BuildError::missing_field("region"))?, - tier: self.tier, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/combined_entity.rs b/seed/rust-sdk/allof/src/api/types/combined_entity.rs deleted file mode 100644 index 9b776e37be14..000000000000 --- a/seed/rust-sdk/allof/src/api/types/combined_entity.rs +++ /dev/null @@ -1,67 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct CombinedEntity { - pub status: CombinedEntityStatus, - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Identifiable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, -} - -impl CombinedEntity { - pub fn builder() -> CombinedEntityBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct CombinedEntityBuilder { - status: Option, - id: Option, - name: Option, - summary: Option, -} - -impl CombinedEntityBuilder { - pub fn status(mut self, value: CombinedEntityStatus) -> Self { - self.status = Some(value); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`CombinedEntity`]. - /// This method will fail if any of the following fields are not set: - /// - [`status`](CombinedEntityBuilder::status) - /// - [`id`](CombinedEntityBuilder::id) - pub fn build(self) -> Result { - Ok(CombinedEntity { - status: self - .status - .ok_or_else(|| BuildError::missing_field("status"))?, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - summary: self.summary, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/combined_entity_status.rs b/seed/rust-sdk/allof/src/api/types/combined_entity_status.rs deleted file mode 100644 index f46cb23eecb2..000000000000 --- a/seed/rust-sdk/allof/src/api/types/combined_entity_status.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum CombinedEntityStatus { - Active, - Archived, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for CombinedEntityStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Archived => serializer.serialize_str("archived"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for CombinedEntityStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "archived" => Ok(Self::Archived), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for CombinedEntityStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Archived => write!(f, "archived"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-sdk/allof/src/api/types/describable.rs b/seed/rust-sdk/allof/src/api/types/describable.rs deleted file mode 100644 index 6a8a527bd844..000000000000 --- a/seed/rust-sdk/allof/src/api/types/describable.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Describable { - /// Display name from Describable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A short summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, -} - -impl Describable { - pub fn builder() -> DescribableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DescribableBuilder { - name: Option, - summary: Option, -} - -impl DescribableBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn summary(mut self, value: impl Into) -> Self { - self.summary = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Describable`]. - pub fn build(self) -> Result { - Ok(Describable { - name: self.name, - summary: self.summary, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/detailed_org.rs b/seed/rust-sdk/allof/src/api/types/detailed_org.rs deleted file mode 100644 index fac068d8a2ee..000000000000 --- a/seed/rust-sdk/allof/src/api/types/detailed_org.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrg { - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl DetailedOrg { - pub fn builder() -> DetailedOrgBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgBuilder { - metadata: Option, -} - -impl DetailedOrgBuilder { - pub fn metadata(mut self, value: DetailedOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`DetailedOrg`]. - pub fn build(self) -> Result { - Ok(DetailedOrg { - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs b/seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs deleted file mode 100644 index bbbeda49c417..000000000000 --- a/seed/rust-sdk/allof/src/api/types/detailed_org_metadata.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct DetailedOrgMetadata { - /// Deployment region from DetailedOrg. - #[serde(default)] - pub region: String, - /// Custom domain name. - #[serde(skip_serializing_if = "Option::is_none")] - pub domain: Option, -} - -impl DetailedOrgMetadata { - pub fn builder() -> DetailedOrgMetadataBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct DetailedOrgMetadataBuilder { - region: Option, - domain: Option, -} - -impl DetailedOrgMetadataBuilder { - pub fn region(mut self, value: impl Into) -> Self { - self.region = Some(value.into()); - self - } - - pub fn domain(mut self, value: impl Into) -> Self { - self.domain = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`DetailedOrgMetadata`]. - /// This method will fail if any of the following fields are not set: - /// - [`region`](DetailedOrgMetadataBuilder::region) - pub fn build(self) -> Result { - Ok(DetailedOrgMetadata { - region: self - .region - .ok_or_else(|| BuildError::missing_field("region"))?, - domain: self.domain, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/identifiable.rs b/seed/rust-sdk/allof/src/api/types/identifiable.rs deleted file mode 100644 index 58fb3eb70734..000000000000 --- a/seed/rust-sdk/allof/src/api/types/identifiable.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Identifiable { - /// Unique identifier. - #[serde(default)] - pub id: String, - /// Display name from Identifiable. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, -} - -impl Identifiable { - pub fn builder() -> IdentifiableBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct IdentifiableBuilder { - id: Option, - name: Option, -} - -impl IdentifiableBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`Identifiable`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](IdentifiableBuilder::id) - pub fn build(self) -> Result { - Ok(Identifiable { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/mod.rs b/seed/rust-sdk/allof/src/api/types/mod.rs deleted file mode 100644 index 18089be4e67a..000000000000 --- a/seed/rust-sdk/allof/src/api/types/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub mod audit_info; -pub mod base_org; -pub mod base_org_metadata; -pub mod combined_entity; -pub mod combined_entity_status; -pub mod describable; -pub mod detailed_org; -pub mod detailed_org_metadata; -pub mod identifiable; -pub mod organization; -pub mod paginated_result; -pub mod paging_cursors; -pub mod rule_create_request; -pub mod rule_execution_context; -pub mod rule_response; -pub mod rule_response_status; -pub mod rule_type; -pub mod rule_type_search_response; -pub mod search_rule_types_query_request; -pub mod user; -pub mod user_search_response; - -pub use audit_info::AuditInfo; -pub use base_org::BaseOrg; -pub use base_org_metadata::BaseOrgMetadata; -pub use combined_entity::CombinedEntity; -pub use combined_entity_status::CombinedEntityStatus; -pub use describable::Describable; -pub use detailed_org::DetailedOrg; -pub use detailed_org_metadata::DetailedOrgMetadata; -pub use identifiable::Identifiable; -pub use organization::Organization; -pub use paginated_result::PaginatedResult; -pub use paging_cursors::PagingCursors; -pub use rule_create_request::RuleCreateRequest; -pub use rule_execution_context::RuleExecutionContext; -pub use rule_response::RuleResponse; -pub use rule_response_status::RuleResponseStatus; -pub use rule_type::RuleType; -pub use rule_type_search_response::RuleTypeSearchResponse; -pub use search_rule_types_query_request::SearchRuleTypesQueryRequest; -pub use user::User; -pub use user_search_response::UserSearchResponse; diff --git a/seed/rust-sdk/allof/src/api/types/organization.rs b/seed/rust-sdk/allof/src/api/types/organization.rs deleted file mode 100644 index ec2ed3f22d66..000000000000 --- a/seed/rust-sdk/allof/src/api/types/organization.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct Organization { - #[serde(default)] - pub name: String, - #[serde(default)] - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl Organization { - pub fn builder() -> OrganizationBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct OrganizationBuilder { - name: Option, - id: Option, - metadata: Option, -} - -impl OrganizationBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn metadata(mut self, value: BaseOrgMetadata) -> Self { - self.metadata = Some(value); - self - } - - /// Consumes the builder and constructs a [`Organization`]. - /// This method will fail if any of the following fields are not set: - /// - [`name`](OrganizationBuilder::name) - /// - [`id`](OrganizationBuilder::id) - pub fn build(self) -> Result { - Ok(Organization { - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - metadata: self.metadata, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/paginated_result.rs b/seed/rust-sdk/allof/src/api/types/paginated_result.rs deleted file mode 100644 index 1f7073c5844e..000000000000 --- a/seed/rust-sdk/allof/src/api/types/paginated_result.rs +++ /dev/null @@ -1,50 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] -pub struct PaginatedResult { - #[serde(default)] - pub paging: PagingCursors, - /// Current page of results from the requested resource. - #[serde(default)] - pub results: Vec, -} - -impl PaginatedResult { - pub fn builder() -> PaginatedResultBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PaginatedResultBuilder { - paging: Option, - results: Option>, -} - -impl PaginatedResultBuilder { - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - /// Consumes the builder and constructs a [`PaginatedResult`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](PaginatedResultBuilder::paging) - /// - [`results`](PaginatedResultBuilder::results) - pub fn build(self) -> Result { - Ok(PaginatedResult { - paging: self - .paging - .ok_or_else(|| BuildError::missing_field("paging"))?, - results: self - .results - .ok_or_else(|| BuildError::missing_field("results"))?, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/paging_cursors.rs b/seed/rust-sdk/allof/src/api/types/paging_cursors.rs deleted file mode 100644 index a3a785119b57..000000000000 --- a/seed/rust-sdk/allof/src/api/types/paging_cursors.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct PagingCursors { - /// Cursor for the next page of results. - #[serde(default)] - pub next: String, - /// Cursor for the previous page of results. - #[serde(skip_serializing_if = "Option::is_none")] - pub previous: Option, -} - -impl PagingCursors { - pub fn builder() -> PagingCursorsBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct PagingCursorsBuilder { - next: Option, - previous: Option, -} - -impl PagingCursorsBuilder { - pub fn next(mut self, value: impl Into) -> Self { - self.next = Some(value.into()); - self - } - - pub fn previous(mut self, value: impl Into) -> Self { - self.previous = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`PagingCursors`]. - /// This method will fail if any of the following fields are not set: - /// - [`next`](PagingCursorsBuilder::next) - pub fn build(self) -> Result { - Ok(PagingCursors { - next: self.next.ok_or_else(|| BuildError::missing_field("next"))?, - previous: self.previous, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/rule_create_request.rs b/seed/rust-sdk/allof/src/api/types/rule_create_request.rs deleted file mode 100644 index 913f6f08eabd..000000000000 --- a/seed/rust-sdk/allof/src/api/types/rule_create_request.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleCreateRequest { - #[serde(default)] - pub name: String, - #[serde(rename = "executionContext")] - pub execution_context: RuleExecutionContext, -} - -impl RuleCreateRequest { - pub fn builder() -> RuleCreateRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleCreateRequestBuilder { - name: Option, - execution_context: Option, -} - -impl RuleCreateRequestBuilder { - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleCreateRequest`]. - /// This method will fail if any of the following fields are not set: - /// - [`name`](RuleCreateRequestBuilder::name) - /// - [`execution_context`](RuleCreateRequestBuilder::execution_context) - pub fn build(self) -> Result { - Ok(RuleCreateRequest { - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - execution_context: self - .execution_context - .ok_or_else(|| BuildError::missing_field("execution_context"))?, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/rule_execution_context.rs b/seed/rust-sdk/allof/src/api/types/rule_execution_context.rs deleted file mode 100644 index 7f8fa5bd56e1..000000000000 --- a/seed/rust-sdk/allof/src/api/types/rule_execution_context.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -/// Execution environment for a rule. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleExecutionContext { - Prod, - Staging, - Dev, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleExecutionContext { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Prod => serializer.serialize_str("prod"), - Self::Staging => serializer.serialize_str("staging"), - Self::Dev => serializer.serialize_str("dev"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleExecutionContext { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "prod" => Ok(Self::Prod), - "staging" => Ok(Self::Staging), - "dev" => Ok(Self::Dev), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleExecutionContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Prod => write!(f, "prod"), - Self::Staging => write!(f, "staging"), - Self::Dev => write!(f, "dev"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-sdk/allof/src/api/types/rule_response.rs b/seed/rust-sdk/allof/src/api/types/rule_response.rs deleted file mode 100644 index ce3d9919a491..000000000000 --- a/seed/rust-sdk/allof/src/api/types/rule_response.rs +++ /dev/null @@ -1,78 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RuleResponse { - #[serde(flatten)] - pub audit_info_fields: AuditInfo, - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - pub status: RuleResponseStatus, - #[serde(rename = "executionContext")] - #[serde(skip_serializing_if = "Option::is_none")] - pub execution_context: Option, -} - -impl RuleResponse { - pub fn builder() -> RuleResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleResponseBuilder { - audit_info_fields: Option, - id: Option, - name: Option, - status: Option, - execution_context: Option, -} - -impl RuleResponseBuilder { - pub fn audit_info_fields(mut self, value: AuditInfo) -> Self { - self.audit_info_fields = Some(value); - self - } - - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn status(mut self, value: RuleResponseStatus) -> Self { - self.status = Some(value); - self - } - - pub fn execution_context(mut self, value: RuleExecutionContext) -> Self { - self.execution_context = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`audit_info_fields`](RuleResponseBuilder::audit_info_fields) - /// - [`id`](RuleResponseBuilder::id) - /// - [`name`](RuleResponseBuilder::name) - /// - [`status`](RuleResponseBuilder::status) - pub fn build(self) -> Result { - Ok(RuleResponse { - audit_info_fields: self - .audit_info_fields - .ok_or_else(|| BuildError::missing_field("audit_info_fields"))?, - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - status: self - .status - .ok_or_else(|| BuildError::missing_field("status"))?, - execution_context: self.execution_context, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/rule_response_status.rs b/seed/rust-sdk/allof/src/api/types/rule_response_status.rs deleted file mode 100644 index 5d2462ecd349..000000000000 --- a/seed/rust-sdk/allof/src/api/types/rule_response_status.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use crate::prelude::*; - -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RuleResponseStatus { - Active, - Inactive, - Draft, - /// This variant is used for forward compatibility. - /// If the server sends a value not recognized by the current SDK version, - /// it will be captured here with the raw string value. - __Unknown(String), -} -impl Serialize for RuleResponseStatus { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Active => serializer.serialize_str("active"), - Self::Inactive => serializer.serialize_str("inactive"), - Self::Draft => serializer.serialize_str("draft"), - Self::__Unknown(val) => serializer.serialize_str(val), - } - } -} - -impl<'de> Deserialize<'de> for RuleResponseStatus { - fn deserialize>(deserializer: D) -> Result { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "active" => Ok(Self::Active), - "inactive" => Ok(Self::Inactive), - "draft" => Ok(Self::Draft), - _ => Ok(Self::__Unknown(value)), - } - } -} - -impl fmt::Display for RuleResponseStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Active => write!(f, "active"), - Self::Inactive => write!(f, "inactive"), - Self::Draft => write!(f, "draft"), - Self::__Unknown(val) => write!(f, "{}", val), - } - } -} diff --git a/seed/rust-sdk/allof/src/api/types/rule_type.rs b/seed/rust-sdk/allof/src/api/types/rule_type.rs deleted file mode 100644 index 69f0317c1923..000000000000 --- a/seed/rust-sdk/allof/src/api/types/rule_type.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleType { - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, -} - -impl RuleType { - pub fn builder() -> RuleTypeBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeBuilder { - id: Option, - name: Option, - description: Option, -} - -impl RuleTypeBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn name(mut self, value: impl Into) -> Self { - self.name = Some(value.into()); - self - } - - pub fn description(mut self, value: impl Into) -> Self { - self.description = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`RuleType`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](RuleTypeBuilder::id) - /// - [`name`](RuleTypeBuilder::name) - pub fn build(self) -> Result { - Ok(RuleType { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - name: self.name.ok_or_else(|| BuildError::missing_field("name"))?, - description: self.description, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs b/seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs deleted file mode 100644 index 854fbd7920fc..000000000000 --- a/seed/rust-sdk/allof/src/api/types/rule_type_search_response.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct RuleTypeSearchResponse { - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, - #[serde(default)] - pub paging: PagingCursors, -} - -impl RuleTypeSearchResponse { - pub fn builder() -> RuleTypeSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct RuleTypeSearchResponseBuilder { - results: Option>, - paging: Option, -} - -impl RuleTypeSearchResponseBuilder { - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - /// Consumes the builder and constructs a [`RuleTypeSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](RuleTypeSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(RuleTypeSearchResponse { - results: self.results, - paging: self - .paging - .ok_or_else(|| BuildError::missing_field("paging"))?, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs b/seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs deleted file mode 100644 index 37158173a08c..000000000000 --- a/seed/rust-sdk/allof/src/api/types/search_rule_types_query_request.rs +++ /dev/null @@ -1,32 +0,0 @@ -pub use crate::prelude::*; - -/// Query parameters for searchRuleTypes -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct SearchRuleTypesQueryRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub query: Option, -} - -impl SearchRuleTypesQueryRequest { - pub fn builder() -> SearchRuleTypesQueryRequestBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct SearchRuleTypesQueryRequestBuilder { - query: Option, -} - -impl SearchRuleTypesQueryRequestBuilder { - pub fn query(mut self, value: impl Into) -> Self { - self.query = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`SearchRuleTypesQueryRequest`]. - pub fn build(self) -> Result { - Ok(SearchRuleTypesQueryRequest { query: self.query }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/user.rs b/seed/rust-sdk/allof/src/api/types/user.rs deleted file mode 100644 index cce3febf2dbe..000000000000 --- a/seed/rust-sdk/allof/src/api/types/user.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct User { - #[serde(default)] - pub id: String, - #[serde(default)] - pub email: String, -} - -impl User { - pub fn builder() -> UserBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserBuilder { - id: Option, - email: Option, -} - -impl UserBuilder { - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - - pub fn email(mut self, value: impl Into) -> Self { - self.email = Some(value.into()); - self - } - - /// Consumes the builder and constructs a [`User`]. - /// This method will fail if any of the following fields are not set: - /// - [`id`](UserBuilder::id) - /// - [`email`](UserBuilder::email) - pub fn build(self) -> Result { - Ok(User { - id: self.id.ok_or_else(|| BuildError::missing_field("id"))?, - email: self - .email - .ok_or_else(|| BuildError::missing_field("email"))?, - }) - } -} diff --git a/seed/rust-sdk/allof/src/api/types/user_search_response.rs b/seed/rust-sdk/allof/src/api/types/user_search_response.rs deleted file mode 100644 index bb233525225a..000000000000 --- a/seed/rust-sdk/allof/src/api/types/user_search_response.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub use crate::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct UserSearchResponse { - /// Current page of results from the requested resource. - #[serde(skip_serializing_if = "Option::is_none")] - pub results: Option>, - #[serde(default)] - pub paging: PagingCursors, -} - -impl UserSearchResponse { - pub fn builder() -> UserSearchResponseBuilder { - ::default() - } -} - -#[derive(Clone, PartialEq, Default, Debug)] -#[non_exhaustive] -pub struct UserSearchResponseBuilder { - results: Option>, - paging: Option, -} - -impl UserSearchResponseBuilder { - pub fn results(mut self, value: Vec) -> Self { - self.results = Some(value); - self - } - - pub fn paging(mut self, value: PagingCursors) -> Self { - self.paging = Some(value); - self - } - - /// Consumes the builder and constructs a [`UserSearchResponse`]. - /// This method will fail if any of the following fields are not set: - /// - [`paging`](UserSearchResponseBuilder::paging) - pub fn build(self) -> Result { - Ok(UserSearchResponse { - results: self.results, - paging: self - .paging - .ok_or_else(|| BuildError::missing_field("paging"))?, - }) - } -} diff --git a/seed/rust-sdk/allof/src/client.rs b/seed/rust-sdk/allof/src/client.rs deleted file mode 100644 index 139cebf898d7..000000000000 --- a/seed/rust-sdk/allof/src/client.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::api::resources::ApiClient; -use crate::Environment; -use crate::{ApiError, ClientConfig}; -use std::collections::HashMap; -use std::time::Duration; -/// Builder for creating API clients with custom configuration -pub struct ApiClientBuilder { - config: ClientConfig, -} -impl Default for ApiClientBuilder { - fn default() -> Self { - Self { - config: ClientConfig::default(), - } - } -} -impl ApiClientBuilder { - /// Create a new builder with the specified base URL - pub fn new(base_url: impl Into) -> Self { - let mut config = ClientConfig::default(); - config.base_url = base_url.into(); - Self { config } - } - - /// Set the environment, updating the base URL - pub fn environment(mut self, environment: Environment) -> Self { - self.config.base_url = environment.url().to_string(); - self - } - - /// Set the API key for authentication - pub fn api_key(mut self, key: impl Into) -> Self { - self.config.api_key = Some(key.into()); - self - } - - /// Set the bearer token for authentication - pub fn token(mut self, token: impl Into) -> Self { - self.config.token = Some(token.into()); - self - } - - /// Set the username for basic authentication - pub fn username(mut self, username: impl Into) -> Self { - self.config.username = Some(username.into()); - self - } - - /// Set the password for basic authentication - pub fn password(mut self, password: impl Into) -> Self { - self.config.password = Some(password.into()); - self - } - - /// Set the OAuth client ID for client credentials authentication - pub fn client_id(mut self, client_id: impl Into) -> Self { - self.config.client_id = Some(client_id.into()); - self - } - - /// Set the OAuth client secret for client credentials authentication - pub fn client_secret(mut self, client_secret: impl Into) -> Self { - self.config.client_secret = Some(client_secret.into()); - self - } - - /// Set OAuth credentials (client_id and client_secret) for client credentials authentication - pub fn oauth_credentials( - mut self, - client_id: impl Into, - client_secret: impl Into, - ) -> Self { - self.config.client_id = Some(client_id.into()); - self.config.client_secret = Some(client_secret.into()); - self - } - - /// Set the request timeout - pub fn timeout(mut self, timeout: Duration) -> Self { - self.config.timeout = timeout; - self - } - - /// Set the maximum number of retries - pub fn max_retries(mut self, retries: u32) -> Self { - self.config.max_retries = retries; - self - } - - /// Add a custom header - pub fn custom_header(mut self, key: impl Into, value: impl Into) -> Self { - self.config.custom_headers.insert(key.into(), value.into()); - self - } - - /// Add multiple custom headers - pub fn custom_headers(mut self, headers: HashMap) -> Self { - self.config.custom_headers.extend(headers); - self - } - - /// Set the user agent - pub fn user_agent(mut self, user_agent: impl Into) -> Self { - self.config.user_agent = user_agent.into(); - self - } - - /// Build the client with validation - pub fn build(self) -> Result { - ApiClient::new(self.config) - } -} -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_environment() { - let builder = ApiClientBuilder::default().environment(Environment::default()); - assert_eq!( - builder.config.base_url, - Environment::default().url().to_string() - ); - } - - #[test] - fn test_new_sets_base_url() { - let builder = ApiClientBuilder::new("https://api.example.com"); - assert_eq!(builder.config.base_url, "https://api.example.com"); - } - - #[test] - fn test_api_key() { - let builder = ApiClientBuilder::new("https://api.example.com").api_key("my-key"); - assert_eq!(builder.config.api_key, Some("my-key".to_string())); - } - - #[test] - fn test_token() { - let builder = ApiClientBuilder::new("https://api.example.com").token("my-token"); - assert_eq!(builder.config.token, Some("my-token".to_string())); - } - - #[test] - fn test_username() { - let builder = ApiClientBuilder::new("https://api.example.com").username("user"); - assert_eq!(builder.config.username, Some("user".to_string())); - } - - #[test] - fn test_password() { - let builder = ApiClientBuilder::new("https://api.example.com").password("pass"); - assert_eq!(builder.config.password, Some("pass".to_string())); - } - - #[test] - fn test_client_id() { - let builder = ApiClientBuilder::new("https://api.example.com").client_id("cid"); - assert_eq!(builder.config.client_id, Some("cid".to_string())); - } - - #[test] - fn test_client_secret() { - let builder = ApiClientBuilder::new("https://api.example.com").client_secret("secret"); - assert_eq!(builder.config.client_secret, Some("secret".to_string())); - } - - #[test] - fn test_oauth_credentials() { - let builder = - ApiClientBuilder::new("https://api.example.com").oauth_credentials("cid", "secret"); - assert_eq!(builder.config.client_id, Some("cid".to_string())); - assert_eq!(builder.config.client_secret, Some("secret".to_string())); - } - - #[test] - fn test_timeout() { - let builder = - ApiClientBuilder::new("https://api.example.com").timeout(Duration::from_secs(120)); - assert_eq!(builder.config.timeout, Duration::from_secs(120)); - } - - #[test] - fn test_max_retries() { - let builder = ApiClientBuilder::new("https://api.example.com").max_retries(5); - assert_eq!(builder.config.max_retries, 5); - } - - #[test] - fn test_custom_header() { - let builder = - ApiClientBuilder::new("https://api.example.com").custom_header("X-Custom", "value"); - assert_eq!( - builder.config.custom_headers.get("X-Custom"), - Some(&"value".to_string()) - ); - } - - #[test] - fn test_custom_headers_multiple() { - let mut headers = HashMap::new(); - headers.insert("X-One".to_string(), "1".to_string()); - headers.insert("X-Two".to_string(), "2".to_string()); - let builder = ApiClientBuilder::new("https://api.example.com").custom_headers(headers); - assert_eq!( - builder.config.custom_headers.get("X-One"), - Some(&"1".to_string()) - ); - assert_eq!( - builder.config.custom_headers.get("X-Two"), - Some(&"2".to_string()) - ); - } - - #[test] - fn test_user_agent() { - let builder = ApiClientBuilder::new("https://api.example.com").user_agent("my-sdk/1.0"); - assert_eq!(builder.config.user_agent, "my-sdk/1.0"); - } - - #[test] - fn test_full_builder_chain() { - let builder = ApiClientBuilder::new("https://api.example.com") - .api_key("key") - .token("tok") - .username("user") - .password("pass") - .timeout(Duration::from_secs(60)) - .max_retries(3) - .custom_header("X-Foo", "bar") - .user_agent("test/1.0"); - assert_eq!(builder.config.base_url, "https://api.example.com"); - assert_eq!(builder.config.api_key, Some("key".to_string())); - assert_eq!(builder.config.token, Some("tok".to_string())); - assert_eq!(builder.config.username, Some("user".to_string())); - assert_eq!(builder.config.password, Some("pass".to_string())); - assert_eq!(builder.config.timeout, Duration::from_secs(60)); - assert_eq!(builder.config.max_retries, 3); - assert_eq!(builder.config.user_agent, "test/1.0"); - } - - #[test] - fn test_build_succeeds() { - let result = ApiClientBuilder::new("https://api.example.com").build(); - assert!(result.is_ok()); - } -} diff --git a/seed/rust-sdk/allof/src/config.rs b/seed/rust-sdk/allof/src/config.rs deleted file mode 100644 index 466af37454a1..000000000000 --- a/seed/rust-sdk/allof/src/config.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::Environment; -use std::collections::HashMap; -use std::time::Duration; - -#[derive(Debug, Clone)] -pub struct ClientConfig { - pub base_url: String, - pub api_key: Option, - pub token: Option, - pub username: Option, - pub password: Option, - pub client_id: Option, - pub client_secret: Option, - pub timeout: Duration, - pub max_retries: u32, - pub custom_headers: HashMap, - pub user_agent: String, -} -impl Default for ClientConfig { - fn default() -> Self { - Self { - base_url: Environment::default().url().to_string(), - api_key: None, - token: None, - username: None, - password: None, - client_id: None, - client_secret: None, - timeout: Duration::from_secs(60), - max_retries: 3, - custom_headers: HashMap::from([ - ("X-Fern-Language".to_string(), "Rust".to_string()), - ("X-Fern-SDK-Name".to_string(), "seed_api".to_string()), - ("X-Fern-SDK-Version".to_string(), "0.0.1".to_string()), - ]), - user_agent: "Api Rust SDK".to_string(), - } - } -} diff --git a/seed/rust-sdk/allof/src/core/flexible_datetime.rs b/seed/rust-sdk/allof/src/core/flexible_datetime.rs deleted file mode 100644 index e5572d11b299..000000000000 --- a/seed/rust-sdk/allof/src/core/flexible_datetime.rs +++ /dev/null @@ -1,270 +0,0 @@ -//! Flexible datetime parsing module -//! -//! This module provides serde helpers for parsing datetime strings that may or may not -//! include a timezone suffix. It accepts both RFC3339 format (with Z or +00:00 suffix) -//! and ISO 8601 format without timezone (assuming UTC). -//! -//! Supported formats: -//! - `2024-01-15T09:30:00Z` (RFC3339 with Z) -//! - `2024-01-15T09:30:00+00:00` (RFC3339 with offset) -//! - `2024-01-15T09:30:00` (ISO 8601 without timezone, assumes UTC) -//! - `2024-01-15T09:30:00.123Z` (with fractional seconds and Z) -//! - `2024-01-15T09:30:00.123` (with fractional seconds, no timezone) -//! -//! Two submodules are provided: -//! - `utc`: Parses into `DateTime`, converting all datetimes to UTC -//! - `offset`: Parses into `DateTime`, preserving original timezone - -/// Module for DateTime with flexible parsing - converts all datetimes to UTC -pub mod utc { - use chrono::{DateTime, NaiveDateTime, Utc}; - use serde::{self, Deserialize, Deserializer, Serializer}; - - /// Serialize a DateTime to RFC3339 format - pub fn serialize(date: &DateTime, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&date.to_rfc3339()) - } - - /// Deserialize a datetime string that may or may not include a timezone suffix. - /// If no timezone is present, UTC is assumed. All datetimes are converted to UTC. - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - parse_flexible_datetime(&s).map_err(serde::de::Error::custom) - } - - /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. - fn parse_flexible_datetime(s: &str) -> Result, String> { - // First, try parsing as RFC3339 (with timezone) - if let Ok(dt) = DateTime::parse_from_rfc3339(s) { - return Ok(dt.with_timezone(&Utc)); - } - - // Try parsing as NaiveDateTime (without timezone) and assume UTC - // Try with fractional seconds first - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { - return Ok(naive.and_utc()); - } - - // Try without fractional seconds - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { - return Ok(naive.and_utc()); - } - - Err(format!( - "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ - or ISO 8601 format (e.g., '2024-01-15T09:30:00')", - s - )) - } - - /// Module for optional DateTime fields with flexible parsing - pub mod option { - use super::*; - - pub fn serialize(date: &Option>, serializer: S) -> Result - where - S: Serializer, - { - match date { - Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let opt: Option = Option::deserialize(deserializer)?; - match opt { - Some(s) => parse_flexible_datetime(&s) - .map(Some) - .map_err(serde::de::Error::custom), - None => Ok(None), - } - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_parse_rfc3339_with_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_rfc3339_with_offset() { - let result = parse_flexible_datetime("2024-01-15T09:30:00+00:00"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_without_timezone() { - let result = parse_flexible_datetime("2024-01-15T09:30:00"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_with_fractional_seconds() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); - assert!(result.is_ok()); - } - - #[test] - fn test_parse_with_fractional_seconds_and_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); - assert!(result.is_ok()); - } - } -} - -/// Module for DateTime with flexible parsing - preserves original timezone -pub mod offset { - use chrono::{DateTime, FixedOffset, NaiveDateTime}; - use serde::{self, Deserialize, Deserializer, Serializer}; - - /// Serialize a DateTime to RFC3339 format - pub fn serialize(date: &DateTime, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&date.to_rfc3339()) - } - - /// Deserialize a datetime string that may or may not include a timezone suffix. - /// If no timezone is present, UTC (+00:00) is assumed. - /// The original timezone offset is preserved when present. - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - parse_flexible_datetime(&s).map_err(serde::de::Error::custom) - } - - /// Parse a datetime string flexibly, accepting both RFC3339 and plain ISO 8601 formats. - /// Preserves the original timezone offset when present, assumes UTC when not. - fn parse_flexible_datetime(s: &str) -> Result, String> { - // First, try parsing as RFC3339 (with timezone) - this preserves the original offset - if let Ok(dt) = DateTime::parse_from_rfc3339(s) { - return Ok(dt); - } - - // Try parsing as NaiveDateTime (without timezone) and assume UTC (+00:00) - let utc_offset = FixedOffset::east_opt(0).unwrap(); - - // Try with fractional seconds first - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") { - return Ok(naive.and_local_timezone(utc_offset).unwrap()); - } - - // Try without fractional seconds - if let Ok(naive) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { - return Ok(naive.and_local_timezone(utc_offset).unwrap()); - } - - Err(format!( - "Failed to parse datetime '{}'. Expected RFC3339 format (e.g., '2024-01-15T09:30:00Z') \ - or ISO 8601 format (e.g., '2024-01-15T09:30:00')", - s - )) - } - - /// Module for optional DateTime fields with flexible parsing - pub mod option { - use super::*; - - pub fn serialize( - date: &Option>, - serializer: S, - ) -> Result - where - S: Serializer, - { - match date { - Some(dt) => serializer.serialize_some(&dt.to_rfc3339()), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let opt: Option = Option::deserialize(deserializer)?; - match opt { - Some(s) => parse_flexible_datetime(&s) - .map(Some) - .map_err(serde::de::Error::custom), - None => Ok(None), - } - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_parse_rfc3339_with_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00Z"); - assert!(result.is_ok()); - let dt = result.unwrap(); - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_parse_rfc3339_with_offset() { - let result = parse_flexible_datetime("2024-01-15T09:30:00-05:00"); - assert!(result.is_ok()); - let dt = result.unwrap(); - // -05:00 = -5 * 3600 = -18000 seconds - assert_eq!(dt.offset().local_minus_utc(), -18000); - } - - #[test] - fn test_parse_without_timezone() { - let result = parse_flexible_datetime("2024-01-15T09:30:00"); - assert!(result.is_ok()); - let dt = result.unwrap(); - // Should assume UTC (+00:00) - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_parse_with_fractional_seconds() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123"); - assert!(result.is_ok()); - let dt = result.unwrap(); - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_parse_with_fractional_seconds_and_z() { - let result = parse_flexible_datetime("2024-01-15T09:30:00.123Z"); - assert!(result.is_ok()); - let dt = result.unwrap(); - assert_eq!(dt.offset().local_minus_utc(), 0); - } - - #[test] - fn test_preserves_positive_offset() { - let result = parse_flexible_datetime("2024-01-15T09:30:00+09:00"); - assert!(result.is_ok()); - let dt = result.unwrap(); - // +09:00 = 9 * 3600 = 32400 seconds - assert_eq!(dt.offset().local_minus_utc(), 32400); - } - } -} diff --git a/seed/rust-sdk/allof/src/core/http_client.rs b/seed/rust-sdk/allof/src/core/http_client.rs deleted file mode 100644 index d61f8b46a3a1..000000000000 --- a/seed/rust-sdk/allof/src/core/http_client.rs +++ /dev/null @@ -1,569 +0,0 @@ -use crate::{join_url, ApiError, ClientConfig, OAuthTokenProvider, RequestOptions}; -use futures::{Stream, StreamExt}; -use reqwest::{ - header::{HeaderName, HeaderValue}, - Client, Method, Request, Response, -}; -use serde::de::DeserializeOwned; - -use std::{ - pin::Pin, - str::FromStr, - sync::Arc, - task::{Context, Poll}, -}; - -/// A streaming byte stream for downloading files efficiently -pub struct ByteStream { - content_length: Option, - inner: Pin> + Send>>, -} - -impl ByteStream { - /// Create a new ByteStream from a Response - pub(crate) fn new(response: Response) -> Self { - let content_length = response.content_length(); - let stream = response.bytes_stream(); - - Self { - content_length, - inner: Box::pin(stream), - } - } - - /// Collect the entire stream into a `Vec` - /// - /// This consumes the stream and buffers all data into memory. - /// For large files, prefer using `try_next()` to process chunks incrementally. - /// - /// # Example - /// ```no_run - /// let stream = client.download_file().await?; - /// let bytes = stream.collect().await?; - /// ``` - pub async fn collect(mut self) -> Result, ApiError> { - let mut result = Vec::new(); - while let Some(chunk) = self.inner.next().await { - result.extend_from_slice(&chunk.map_err(ApiError::Network)?); - } - Ok(result) - } - - /// Get the next chunk from the stream - /// - /// Returns `Ok(Some(bytes))` if a chunk is available, - /// `Ok(None)` if the stream is finished, or an error. - /// - /// # Example - /// ```no_run - /// let mut stream = client.download_file().await?; - /// while let Some(chunk) = stream.try_next().await? { - /// process_chunk(&chunk); - /// } - /// ``` - pub async fn try_next(&mut self) -> Result, ApiError> { - match self.inner.next().await { - Some(Ok(bytes)) => Ok(Some(bytes)), - Some(Err(e)) => Err(ApiError::Network(e)), - None => Ok(None), - } - } - - /// Get the content length from response headers if available - pub fn content_length(&self) -> Option { - self.content_length - } -} - -impl Stream for ByteStream { - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.inner.as_mut().poll_next(cx) { - Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))), - Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ApiError::Network(e)))), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -/// Configuration for OAuth token fetching. -/// -/// This struct contains all the information needed to automatically fetch -/// and refresh OAuth tokens. -#[derive(Clone)] -pub struct OAuthConfig { - /// The OAuth token provider that manages token caching and refresh - pub token_provider: Arc, - /// The token endpoint path (e.g., "/token") - pub token_endpoint: String, -} - -/// Response from an OAuth token endpoint. -#[derive(Debug, Clone, serde::Deserialize)] -struct OAuthTokenResponse { - access_token: String, - #[serde(default)] - expires_in: Option, -} - -/// Internal HTTP client that handles requests with authentication and retries -#[derive(Clone)] -pub struct HttpClient { - client: Client, - config: ClientConfig, - /// Optional OAuth configuration for automatic token management - oauth_config: Option, -} - -impl HttpClient { - /// Creates a new HttpClient without OAuth support. - pub fn new(config: ClientConfig) -> Result { - Self::new_with_oauth(config, None) - } - - /// Creates a new HttpClient with optional OAuth support. - /// - /// When `oauth_config` is provided, the client will automatically fetch and refresh - /// OAuth tokens before making requests. - pub fn new_with_oauth( - config: ClientConfig, - oauth_config: Option, - ) -> Result { - let client = Client::builder() - .timeout(config.timeout) - .user_agent(&config.user_agent) - .build() - .map_err(ApiError::Network)?; - - Ok(Self { - client, - config, - oauth_config, - }) - } - - /// Returns the configured base URL. - pub fn base_url(&self) -> &str { - &self.config.base_url - } - - /// Returns a reference to the client configuration. - pub fn config(&self) -> &ClientConfig { - &self.config - } - - /// Execute a request with the given method, path, and options - pub async fn execute_request( - &self, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result - where - T: DeserializeOwned, // Generic T: DeserializeOwned means the response will be automatically deserialized into whatever type you specify: - { - let url = join_url(&self.config.base_url, path); - let mut request = self.client.request(method, &url); - - // Apply query parameters if provided - if let Some(params) = query_params { - request = request.query(¶ms); - } - - // Apply additional query parameters from options - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - // Apply body if provided - if let Some(body) = body { - request = request.json(&body); - } - - // Build the request - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - // Apply authentication and headers - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - // Execute with retries - let response = self.execute_with_retries(req, &options).await?; - self.parse_response(response).await - } - - /// Execute a request with an explicit base URL override. - /// - /// Used for multi-URL environments where different endpoints - /// resolve to different base URLs. - pub async fn execute_request_with_base_url( - &self, - base_url: &str, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result - where - T: DeserializeOwned, - { - let url = join_url(base_url, path); - let mut request = self.client.request(method, &url); - - if let Some(params) = query_params { - request = request.query(¶ms); - } - - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - if let Some(body) = body { - request = request.json(&body); - } - - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - let response = self.execute_with_retries(req, &options).await?; - self.parse_response(response).await - } - - async fn apply_auth_headers( - &self, - request: &mut Request, - options: &Option, - ) -> Result<(), ApiError> { - let headers = request.headers_mut(); - - // Apply API key (request options override config) - let api_key = options - .as_ref() - .and_then(|opts| opts.api_key.as_ref()) - .or(self.config.api_key.as_ref()); - - if let Some(key) = api_key { - let header_value = key.to_string(); - headers.insert( - "api_key", - header_value.parse().map_err(|_| ApiError::InvalidHeader)?, - ); - } - - // Apply bearer token - priority: request options > OAuth > config - let token = if let Some(opts) = options.as_ref() { - if opts.token.is_some() { - opts.token.clone() - } else { - None - } - } else { - None - }; - - let token = match token { - Some(t) => Some(t), - None => { - // Try OAuth token provider if configured - if let Some(oauth_config) = &self.oauth_config { - Some(self.get_oauth_token(oauth_config).await?) - } else { - // Fall back to static token from config - self.config.token.clone() - } - } - }; - - if let Some(token) = token { - let auth_value = format!("Bearer {}", token); - headers.insert( - "Authorization", - auth_value.parse().map_err(|_| ApiError::InvalidHeader)?, - ); - } - - Ok(()) - } - - /// Fetches an OAuth token, using the cached token if valid or fetching a new one. - async fn get_oauth_token(&self, oauth_config: &OAuthConfig) -> Result { - let token_provider = &oauth_config.token_provider; - let token_endpoint = &oauth_config.token_endpoint; - let client_id = token_provider.client_id().to_string(); - let client_secret = token_provider.client_secret().to_string(); - let base_url = self.config.base_url.clone(); - - // Use the async get_or_fetch method with a closure that fetches the token - token_provider - .get_or_fetch_async(|| async { - self.fetch_oauth_token(&base_url, token_endpoint, &client_id, &client_secret) - .await - }) - .await - } - - /// Makes an HTTP request to the OAuth token endpoint to fetch a new token. - async fn fetch_oauth_token( - &self, - base_url: &str, - token_endpoint: &str, - client_id: &str, - client_secret: &str, - ) -> Result<(String, u64), ApiError> { - let url = join_url(base_url, token_endpoint); - - // Build the token request body - let body = serde_json::json!({ - "client_id": client_id, - "client_secret": client_secret, - "grant_type": "client_credentials" - }); - - let response = self - .client - .request(Method::POST, &url) - .json(&body) - .send() - .await - .map_err(ApiError::Network)?; - - if !response.status().is_success() { - let status_code = response.status().as_u16(); - let body = response.text().await.ok(); - return Err(ApiError::from_response(status_code, body.as_deref())); - } - - // Parse the token response - let token_response: OAuthTokenResponse = - response.json().await.map_err(ApiError::Network)?; - - let expires_in = token_response.expires_in.unwrap_or(3600) as u64; - Ok((token_response.access_token, expires_in)) - } - - fn apply_custom_headers( - &self, - request: &mut Request, - options: &Option, - ) -> Result<(), ApiError> { - let headers = request.headers_mut(); - - // Apply config-level custom headers - for (key, value) in &self.config.custom_headers { - headers.insert( - HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, - HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, - ); - } - - // Apply request-level custom headers (override config) - if let Some(options) = options { - for (key, value) in &options.additional_headers { - headers.insert( - HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?, - HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?, - ); - } - } - - Ok(()) - } - - async fn execute_with_retries( - &self, - request: Request, - options: &Option, - ) -> Result { - let max_retries = options - .as_ref() - .and_then(|opts| opts.max_retries) - .unwrap_or(self.config.max_retries); - - let mut last_error = None; - - for attempt in 0..=max_retries { - let cloned_request = request.try_clone().ok_or(ApiError::RequestClone)?; - - match self.client.execute(cloned_request).await { - Ok(response) if response.status().is_success() => return Ok(response), - Ok(response) => { - let status_code = response.status().as_u16(); - let body = response.text().await.ok(); - return Err(ApiError::from_response(status_code, body.as_deref())); - } - Err(e) if attempt < max_retries => { - last_error = Some(e); - // Exponential backoff - let delay = std::time::Duration::from_millis(100 * 2_u64.pow(attempt)); - tokio::time::sleep(delay).await; - } - Err(e) => return Err(ApiError::Network(e)), - } - } - - Err(ApiError::Network(last_error.unwrap())) - } - - async fn parse_response(&self, response: Response) -> Result - where - T: DeserializeOwned, - { - let status = response.status().as_u16(); - let text = response.text().await.map_err(ApiError::Network)?; - - // Handle empty response bodies (e.g., 202 Accepted for deferred requests) - if text.is_empty() { - return Err(ApiError::Http { - status, - message: String::new(), - }); - } - - serde_json::from_str(&text).map_err(ApiError::Serialization) - } - - /// Execute a request and return a streaming response (for large file downloads) - /// - /// This method returns a `ByteStream` that can be used to download large files - /// efficiently without loading the entire content into memory. The stream can be - /// consumed chunk by chunk, written directly to disk, or collected into bytes. - /// - /// # Examples - /// - /// **Option 1: Collect all bytes into memory** - /// ```no_run - /// let stream = client.execute_stream_request( - /// Method::GET, - /// "/file", - /// None, - /// None, - /// None, - /// ).await?; - /// - /// let bytes = stream.collect().await?; - /// ``` - /// - /// **Option 2: Process chunks with try_next()** - /// ```no_run - /// let mut stream = client.execute_stream_request( - /// Method::GET, - /// "/large-file", - /// None, - /// None, - /// None, - /// ).await?; - /// - /// while let Some(chunk) = stream.try_next().await? { - /// process_chunk(&chunk); - /// } - /// ``` - /// - /// **Option 3: Stream with futures::Stream trait** - /// ```no_run - /// use futures::StreamExt; - /// - /// let stream = client.execute_stream_request( - /// Method::GET, - /// "/large-file", - /// None, - /// None, - /// None, - /// ).await?; - /// - /// let mut file = tokio::fs::File::create("output.mp4").await?; - /// let mut stream = std::pin::pin!(stream); - /// while let Some(chunk) = stream.next().await { - /// let chunk = chunk?; - /// tokio::io::AsyncWriteExt::write_all(&mut file, &chunk).await?; - /// } - /// ``` - pub async fn execute_stream_request( - &self, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result { - let url = join_url(&self.config.base_url, path); - let mut request = self.client.request(method, &url); - - // Apply query parameters if provided - if let Some(params) = query_params { - request = request.query(¶ms); - } - - // Apply additional query parameters from options - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - // Apply body if provided - if let Some(body) = body { - request = request.json(&body); - } - - // Build the request - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - // Apply authentication and headers - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - // Execute with retries - let response = self.execute_with_retries(req, &options).await?; - - // Return streaming response - Ok(ByteStream::new(response)) - } - - /// Execute a streaming request with an explicit base URL override. - pub async fn execute_stream_request_with_base_url( - &self, - base_url: &str, - method: Method, - path: &str, - body: Option, - query_params: Option>, - options: Option, - ) -> Result { - let url = join_url(base_url, path); - let mut request = self.client.request(method, &url); - - if let Some(params) = query_params { - request = request.query(¶ms); - } - - if let Some(opts) = &options { - if !opts.additional_query_params.is_empty() { - request = request.query(&opts.additional_query_params); - } - } - - if let Some(body) = body { - request = request.json(&body); - } - - let mut req = request.build().map_err(|e| ApiError::Network(e))?; - - self.apply_auth_headers(&mut req, &options).await?; - self.apply_custom_headers(&mut req, &options)?; - - let response = self.execute_with_retries(req, &options).await?; - - Ok(ByteStream::new(response)) - } -} diff --git a/seed/rust-sdk/allof/src/core/mod.rs b/seed/rust-sdk/allof/src/core/mod.rs deleted file mode 100644 index 71a75dbb141b..000000000000 --- a/seed/rust-sdk/allof/src/core/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Core client infrastructure - -pub mod flexible_datetime; -mod http_client; -mod oauth_token_provider; -mod query_parameter_builder; -mod request_options; -mod utils; - -pub use http_client::{ByteStream, HttpClient, OAuthConfig}; -pub use oauth_token_provider::OAuthTokenProvider; -pub use query_parameter_builder::{parse_structured_query, QueryBuilder, QueryBuilderError}; -pub use request_options::RequestOptions; -pub use utils::join_url; diff --git a/seed/rust-sdk/allof/src/core/oauth_token_provider.rs b/seed/rust-sdk/allof/src/core/oauth_token_provider.rs deleted file mode 100644 index 09222bedd8e0..000000000000 --- a/seed/rust-sdk/allof/src/core/oauth_token_provider.rs +++ /dev/null @@ -1,363 +0,0 @@ -use std::future::Future; -use std::sync::Mutex; -use std::time::{Duration, Instant}; -use tokio::sync::Mutex as AsyncMutex; - -/// Buffer time in seconds subtracted from token expiration to ensure -/// we refresh the token before it actually expires. -const EXPIRATION_BUFFER_SECONDS: u64 = 120; // 2 minutes - -/// Default expiry time in seconds used when the OAuth response doesn't include an expires_in value. -const DEFAULT_EXPIRY_SECONDS: u64 = 3600; // 1 hour fallback - -/// Manages OAuth access tokens, including caching and automatic refresh. -/// -/// This provider implements thread-safe token management with automatic expiration -/// handling. It uses a double-checked locking pattern to minimize lock contention -/// while ensuring only one thread fetches a new token at a time. -/// -/// # Example -/// -/// ```rust,ignore -/// use crate::OAuthTokenProvider; -/// -/// let provider = OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); -/// -/// // Get or fetch a token (sync) -/// let token = provider.get_or_fetch(|| { -/// // Your token fetching logic here -/// // Returns (access_token, expires_in_seconds) -/// Ok(("token".to_string(), Some(3600))) -/// })?; -/// -/// // Get or fetch a token (async) -/// let token = provider.get_or_fetch_async(|| async { -/// // Your async token fetching logic here -/// Ok(("token".to_string(), Some(3600))) -/// }).await?; -/// ``` -pub struct OAuthTokenProvider { - client_id: String, - client_secret: String, - inner: Mutex, - /// Separate mutex to ensure only one thread fetches a new token at a time (sync) - fetch_lock: Mutex<()>, - /// Async mutex for async token fetching - async_fetch_lock: AsyncMutex<()>, -} - -struct OAuthTokenProviderInner { - access_token: Option, - expires_at: Option, -} - -impl OAuthTokenProvider { - /// Creates a new OAuthTokenProvider with the given credentials. - pub fn new(client_id: String, client_secret: String) -> Self { - Self { - client_id, - client_secret, - inner: Mutex::new(OAuthTokenProviderInner { - access_token: None, - expires_at: None, - }), - fetch_lock: Mutex::new(()), - async_fetch_lock: AsyncMutex::new(()), - } - } - - /// Returns the client ID. - pub fn client_id(&self) -> &str { - &self.client_id - } - - /// Returns the client secret. - pub fn client_secret(&self) -> &str { - &self.client_secret - } - - /// Sets the cached access token and its expiration time. - /// - /// The `expires_in` parameter is the number of seconds until the token expires. - /// A buffer is applied to refresh before actual expiration. - pub fn set_token(&self, access_token: String, expires_in: u64) { - let mut inner = self.inner.lock().unwrap(); - inner.access_token = Some(access_token); - - if expires_in > 0 { - // Apply buffer to refresh before actual expiration - let effective_expires_in = expires_in.saturating_sub(EXPIRATION_BUFFER_SECONDS); - inner.expires_at = Some(Instant::now() + Duration::from_secs(effective_expires_in)); - } else { - // No expiration info, token won't auto-refresh based on time - inner.expires_at = None; - } - } - - /// Returns the cached access token if it's still valid. - /// - /// Returns `None` if the token is expired or not set. - pub fn get_token(&self) -> Option { - let inner = self.inner.lock().unwrap(); - - if let Some(ref token) = inner.access_token { - // Check if token is still valid - if let Some(expires_at) = inner.expires_at { - if Instant::now() < expires_at { - return Some(token.clone()); - } - } else { - // No expiration set, token is always valid - return Some(token.clone()); - } - } - - None - } - - /// Returns a valid token, fetching a new one if necessary (synchronous version). - /// - /// The `fetch_func` is called at most once even if multiple threads call `get_or_fetch` - /// concurrently when the token is expired. It should return `(access_token, expires_in_seconds)`. - /// - /// # Arguments - /// - /// * `fetch_func` - A function that fetches a new token. Returns `Result<(String, u64), E>` - /// where the tuple contains (access_token, expires_in_seconds). - /// - /// # Example - /// - /// ```rust,ignore - /// let token = provider.get_or_fetch(|| { - /// // Call your OAuth endpoint here (sync) - /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret())?; - /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) - /// })?; - /// ``` - pub fn get_or_fetch(&self, fetch_func: F) -> Result - where - F: FnOnce() -> Result<(String, u64), E>, - { - // Fast path: check if we have a valid token - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Slow path: acquire fetch lock to ensure only one thread fetches - let _fetch_guard = self.fetch_lock.lock().unwrap(); - - // Double-check after acquiring lock (another thread may have fetched) - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Fetch new token - let (access_token, expires_in) = fetch_func()?; - - // Use default expiry if not provided - let effective_expires_in = if expires_in > 0 { - expires_in - } else { - DEFAULT_EXPIRY_SECONDS - }; - - self.set_token(access_token.clone(), effective_expires_in); - Ok(access_token) - } - - /// Returns a valid token, fetching a new one if necessary (async version). - /// - /// This is the async version of `get_or_fetch` for use with async token fetching. - /// The `fetch_func` is called at most once even if multiple tasks call `get_or_fetch_async` - /// concurrently when the token is expired. - /// - /// # Arguments - /// - /// * `fetch_func` - An async function that fetches a new token. Returns `Result<(String, u64), E>` - /// where the tuple contains (access_token, expires_in_seconds). - /// - /// # Example - /// - /// ```rust,ignore - /// let token = provider.get_or_fetch_async(|| async { - /// // Call your OAuth endpoint here (async) - /// let response = auth_client.get_token(&provider.client_id(), &provider.client_secret()).await?; - /// Ok((response.access_token, response.expires_in.unwrap_or(3600))) - /// }).await?; - /// ``` - pub async fn get_or_fetch_async(&self, fetch_func: F) -> Result - where - F: FnOnce() -> Fut, - Fut: Future>, - { - // Fast path: check if we have a valid token - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Slow path: acquire async fetch lock to ensure only one task fetches - let _fetch_guard = self.async_fetch_lock.lock().await; - - // Double-check after acquiring lock (another task may have fetched) - if let Some(token) = self.get_token() { - return Ok(token); - } - - // Fetch new token - let (access_token, expires_in) = fetch_func().await?; - - // Use default expiry if not provided - let effective_expires_in = if expires_in > 0 { - expires_in - } else { - DEFAULT_EXPIRY_SECONDS - }; - - self.set_token(access_token.clone(), effective_expires_in); - Ok(access_token) - } - - /// Returns `true` if the token needs to be refreshed. - /// - /// This is useful for proactively refreshing tokens before they expire. - pub fn needs_refresh(&self) -> bool { - let inner = self.inner.lock().unwrap(); - - if inner.access_token.is_none() { - return true; - } - - if let Some(expires_at) = inner.expires_at { - if Instant::now() >= expires_at { - return true; - } - } - - false - } - - /// Clears the cached token. - /// - /// This can be used to force a token refresh on the next request. - pub fn reset(&self) { - let mut inner = self.inner.lock().unwrap(); - inner.access_token = None; - inner.expires_at = None; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::Arc; - use std::thread; - - #[test] - fn test_new_provider() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - assert_eq!(provider.client_id(), "client_id"); - assert_eq!(provider.client_secret(), "client_secret"); - assert!(provider.get_token().is_none()); - assert!(provider.needs_refresh()); - } - - #[test] - fn test_set_and_get_token() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - provider.set_token("test_token".to_string(), 3600); - - let token = provider.get_token(); - assert!(token.is_some()); - assert_eq!(token.unwrap(), "test_token"); - assert!(!provider.needs_refresh()); - } - - #[test] - fn test_expired_token() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - // Set token with 0 expiry (will be expired immediately due to buffer) - provider.set_token("test_token".to_string(), 1); - - // Token should be expired (1 second - 120 second buffer = expired) - assert!(provider.get_token().is_none()); - assert!(provider.needs_refresh()); - } - - #[test] - fn test_get_or_fetch() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - let result: Result = - provider.get_or_fetch(|| Ok(("fetched_token".to_string(), 3600))); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "fetched_token"); - - // Second call should return cached token - let result2: Result = provider.get_or_fetch(|| { - panic!("Should not be called - token is cached"); - }); - - assert!(result2.is_ok()); - assert_eq!(result2.unwrap(), "fetched_token"); - } - - #[test] - fn test_reset() { - let provider = - OAuthTokenProvider::new("client_id".to_string(), "client_secret".to_string()); - - provider.set_token("test_token".to_string(), 3600); - assert!(provider.get_token().is_some()); - - provider.reset(); - assert!(provider.get_token().is_none()); - assert!(provider.needs_refresh()); - } - - #[test] - fn test_concurrent_access() { - let provider = Arc::new(OAuthTokenProvider::new( - "client_id".to_string(), - "client_secret".to_string(), - )); - let fetch_count = Arc::new(AtomicUsize::new(0)); - - let mut handles = vec![]; - - for _ in 0..10 { - let provider_clone = Arc::clone(&provider); - let fetch_count_clone = Arc::clone(&fetch_count); - - let handle = thread::spawn(move || { - let result: Result = provider_clone.get_or_fetch(|| { - fetch_count_clone.fetch_add(1, Ordering::SeqCst); - // Simulate some work - thread::sleep(Duration::from_millis(10)); - Ok(("concurrent_token".to_string(), 3600)) - }); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "concurrent_token"); - }); - - handles.push(handle); - } - - for handle in handles { - handle.join().unwrap(); - } - - // Due to double-checked locking, fetch should only be called once - // (or at most a few times if threads race before the first fetch completes) - let count = fetch_count.load(Ordering::SeqCst); - assert!(count >= 1 && count <= 3, "Fetch was called {} times", count); - } -} diff --git a/seed/rust-sdk/allof/src/core/pagination.rs b/seed/rust-sdk/allof/src/core/pagination.rs deleted file mode 100644 index 6b41f78a4858..000000000000 --- a/seed/rust-sdk/allof/src/core/pagination.rs +++ /dev/null @@ -1,541 +0,0 @@ -use std::collections::VecDeque; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; - -use futures::Stream; -use serde_json::Value; - -use crate::{ApiError, HttpClient}; - -/// Result of a pagination request -#[derive(Debug)] -pub struct PaginationResult { - pub items: Vec, - pub next_cursor: Option, - pub has_next_page: bool, -} - -/// Async paginator that implements Stream for iterating over paginated results -pub struct AsyncPaginator { - http_client: Arc, - page_loader: Box< - dyn Fn( - Arc, - Option, - ) - -> Pin, ApiError>> + Send>> - + Send - + Sync, - >, - current_page: VecDeque, - current_cursor: Option, - has_next_page: bool, - loading_next: - Option, ApiError>> + Send>>>, -} - -impl AsyncPaginator { - pub fn new( - http_client: Arc, - page_loader: F, - initial_cursor: Option, - ) -> Result - where - F: Fn(Arc, Option) -> Fut + Send + Sync + 'static, - Fut: Future, ApiError>> + Send + 'static, - { - Ok(Self { - http_client, - page_loader: Box::new(move |client, cursor| Box::pin(page_loader(client, cursor))), - current_page: VecDeque::new(), - current_cursor: initial_cursor, - has_next_page: true, // Assume true initially, will be updated after first request - loading_next: None, - }) - } - - /// Check if there are more pages available - pub fn has_next_page(&self) -> bool { - !self.current_page.is_empty() || self.has_next_page - } - - /// Load the next page explicitly - pub async fn next_page(&mut self) -> Result, ApiError> { - if !self.has_next_page { - return Ok(Vec::new()); - } - - let result = - (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()).await?; - - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - - Ok(result.items) - } -} - -impl Stream for AsyncPaginator -where - T: Unpin, -{ - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - // If we have items in the current page, return the next one - if let Some(item) = self.current_page.pop_front() { - return Poll::Ready(Some(Ok(item))); - } - - // If we're already loading the next page, poll that future - if let Some(ref mut loading_future) = self.loading_next { - match loading_future.as_mut().poll(cx) { - Poll::Ready(Ok(result)) => { - self.current_page.extend(result.items); - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - self.loading_next = None; - - // Try to get the next item from the newly loaded page - if let Some(item) = self.current_page.pop_front() { - return Poll::Ready(Some(Ok(item))); - } else if !self.has_next_page { - return Poll::Ready(None); - } - // Fall through to start loading next page - } - Poll::Ready(Err(e)) => { - self.loading_next = None; - return Poll::Ready(Some(Err(e))); - } - Poll::Pending => return Poll::Pending, - } - } - - // If we have no more pages to load, we're done - if !self.has_next_page { - return Poll::Ready(None); - } - - // Start loading the next page - let future = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()); - self.loading_next = Some(future); - - // Poll the future immediately - if let Some(ref mut loading_future) = self.loading_next { - match loading_future.as_mut().poll(cx) { - Poll::Ready(Ok(result)) => { - self.current_page.extend(result.items); - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - self.loading_next = None; - - if let Some(item) = self.current_page.pop_front() { - Poll::Ready(Some(Ok(item))) - } else if !self.has_next_page { - Poll::Ready(None) - } else { - // This shouldn't happen, but just in case - cx.waker().wake_by_ref(); - Poll::Pending - } - } - Poll::Ready(Err(e)) => { - self.loading_next = None; - Poll::Ready(Some(Err(e))) - } - Poll::Pending => Poll::Pending, - } - } else { - Poll::Pending - } - } -} - -/// Synchronous paginator for blocking iteration -pub struct SyncPaginator { - http_client: Arc, - page_loader: Box< - dyn Fn(Arc, Option) -> Result, ApiError> - + Send - + Sync, - >, - current_page: VecDeque, - current_cursor: Option, - has_next_page: bool, -} - -impl SyncPaginator { - pub fn new( - http_client: Arc, - page_loader: F, - initial_cursor: Option, - ) -> Result - where - F: Fn(Arc, Option) -> Result, ApiError> - + Send - + Sync - + 'static, - { - Ok(Self { - http_client, - page_loader: Box::new(page_loader), - current_page: VecDeque::new(), - current_cursor: initial_cursor, - has_next_page: true, // Assume true initially - }) - } - - /// Check if there are more pages available - pub fn has_next_page(&self) -> bool { - !self.current_page.is_empty() || self.has_next_page - } - - /// Load the next page explicitly - pub fn next_page(&mut self) -> Result, ApiError> { - if !self.has_next_page { - return Ok(Vec::new()); - } - - let result = (self.page_loader)(self.http_client.clone(), self.current_cursor.clone())?; - - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - - Ok(result.items) - } - - /// Get all remaining items by loading all pages - pub fn collect_all(&mut self) -> Result, ApiError> { - let mut all_items = Vec::new(); - - // Add items from current page - while let Some(item) = self.current_page.pop_front() { - all_items.push(item); - } - - // Load all remaining pages - while self.has_next_page { - let page_items = self.next_page()?; - all_items.extend(page_items); - } - - Ok(all_items) - } -} - -impl Iterator for SyncPaginator { - type Item = Result; - - fn next(&mut self) -> Option { - // If we have items in the current page, return the next one - if let Some(item) = self.current_page.pop_front() { - return Some(Ok(item)); - } - - // If we have no more pages to load, we're done - if !self.has_next_page { - return None; - } - - // Load the next page - match (self.page_loader)(self.http_client.clone(), self.current_cursor.clone()) { - Ok(result) => { - self.current_page.extend(result.items); - self.current_cursor = result.next_cursor; - self.has_next_page = result.has_next_page; - - // Return the first item from the newly loaded page - self.current_page.pop_front().map(Ok) - } - Err(e) => Some(Err(e)), - } - } -} - -/// Trait for types that can provide pagination metadata -pub trait Paginated { - /// Extract the items from this page - fn items(&self) -> &[T]; - - /// Get the cursor for the next page, if any - fn next_cursor(&self) -> Option<&str>; - - /// Check if there's a next page available - fn has_next_page(&self) -> bool; -} - -/// Trait for types that can provide offset-based pagination metadata -pub trait OffsetPaginated { - /// Extract the items from this page - fn items(&self) -> &[T]; - - /// Check if there's a next page available - fn has_next_page(&self) -> bool; - - /// Get the current page size (for calculating next offset) - fn page_size(&self) -> usize { - self.items().len() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ClientConfig; - - fn make_http_client() -> Arc { - Arc::new(HttpClient::new(ClientConfig::default()).expect("Failed to create test HttpClient")) - } - - // =========================== - // SyncPaginator tests - // =========================== - - #[test] - fn test_sync_paginator_has_next_page_initially() { - let client = make_http_client(); - let paginator = SyncPaginator::::new(client, |_client, _cursor| { - Ok(PaginationResult { - items: vec![], - next_cursor: None, - has_next_page: false, - }) - }, None).unwrap(); - assert!(paginator.has_next_page()); - } - - #[test] - fn test_sync_paginator_single_page() { - let client = make_http_client(); - let mut paginator = SyncPaginator::new(client, |_client, _cursor| { - Ok(PaginationResult { - items: vec!["a".to_string(), "b".to_string()], - next_cursor: None, - has_next_page: false, - }) - }, None).unwrap(); - - let page = paginator.next_page().unwrap(); - assert_eq!(page, vec!["a".to_string(), "b".to_string()]); - assert!(!paginator.has_next_page()); - } - - #[test] - fn test_sync_paginator_exhausted_returns_empty() { - let client = make_http_client(); - let mut paginator = SyncPaginator::new(client, |_client, _cursor| { - Ok(PaginationResult { - items: vec!["a".to_string()], - next_cursor: None, - has_next_page: false, - }) - }, None).unwrap(); - - let _ = paginator.next_page().unwrap(); - let empty = paginator.next_page().unwrap(); - assert!(empty.is_empty()); - } - - #[test] - fn test_sync_paginator_multiple_pages() { - let client = make_http_client(); - let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let count = call_count.clone(); - - let mut paginator = SyncPaginator::new(client, move |_client, cursor| { - let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - match call { - 0 => { - assert!(cursor.is_none()); - Ok(PaginationResult { - items: vec![1, 2], - next_cursor: Some("page2".to_string()), - has_next_page: true, - }) - } - 1 => { - assert_eq!(cursor, Some("page2".to_string())); - Ok(PaginationResult { - items: vec![3, 4], - next_cursor: None, - has_next_page: false, - }) - } - _ => panic!("Unexpected call"), - } - }, None).unwrap(); - - let page1 = paginator.next_page().unwrap(); - assert_eq!(page1, vec![1, 2]); - assert!(paginator.has_next_page()); - - let page2 = paginator.next_page().unwrap(); - assert_eq!(page2, vec![3, 4]); - assert!(!paginator.has_next_page()); - } - - #[test] - fn test_sync_paginator_collect_all() { - let client = make_http_client(); - let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let count = call_count.clone(); - - let mut paginator = SyncPaginator::new(client, move |_client, _cursor| { - let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - match call { - 0 => Ok(PaginationResult { - items: vec![1, 2], - next_cursor: Some("next".to_string()), - has_next_page: true, - }), - 1 => Ok(PaginationResult { - items: vec![3], - next_cursor: None, - has_next_page: false, - }), - _ => panic!("Unexpected call"), - } - }, None).unwrap(); - - let all = paginator.collect_all().unwrap(); - assert_eq!(all, vec![1, 2, 3]); - } - - #[test] - fn test_sync_paginator_iterator() { - let client = make_http_client(); - let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let count = call_count.clone(); - - let paginator = SyncPaginator::new(client, move |_client, _cursor| { - let call = count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - match call { - 0 => Ok(PaginationResult { - items: vec![10, 20], - next_cursor: Some("p2".to_string()), - has_next_page: true, - }), - 1 => Ok(PaginationResult { - items: vec![30], - next_cursor: None, - has_next_page: false, - }), - _ => panic!("Unexpected call"), - } - }, None).unwrap(); - - let items: Vec = paginator.map(|r| r.unwrap()).collect(); - assert_eq!(items, vec![10, 20, 30]); - } - - #[test] - fn test_sync_paginator_error_propagation() { - let client = make_http_client(); - let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { - Err(ApiError::Serialization("test error".to_string())) - }, None).unwrap(); - - let result = paginator.next_page(); - assert!(result.is_err()); - } - - #[test] - fn test_sync_paginator_iterator_error() { - let client = make_http_client(); - let mut paginator = SyncPaginator::::new(client, |_client, _cursor| { - Err(ApiError::Serialization("test error".to_string())) - }, None).unwrap(); - - let item = paginator.next(); - assert!(item.is_some()); - assert!(item.unwrap().is_err()); - } - - #[test] - fn test_sync_paginator_with_initial_cursor() { - let client = make_http_client(); - let mut paginator = SyncPaginator::new(client, |_client, cursor| { - assert_eq!(cursor, Some("start_here".to_string())); - Ok(PaginationResult { - items: vec!["item".to_string()], - next_cursor: None, - has_next_page: false, - }) - }, Some("start_here".to_string())).unwrap(); - - let page = paginator.next_page().unwrap(); - assert_eq!(page, vec!["item".to_string()]); - } - - // =========================== - // PaginationResult tests - // =========================== - - #[test] - fn test_pagination_result_fields() { - let result = PaginationResult { - items: vec![1, 2, 3], - next_cursor: Some("abc".to_string()), - has_next_page: true, - }; - assert_eq!(result.items.len(), 3); - assert_eq!(result.next_cursor, Some("abc".to_string())); - assert!(result.has_next_page); - } - - // =========================== - // Trait tests - // =========================== - - struct MockPage { - data: Vec, - cursor: Option, - has_more: bool, - } - - impl Paginated for MockPage { - fn items(&self) -> &[String] { - &self.data - } - fn next_cursor(&self) -> Option<&str> { - self.cursor.as_deref() - } - fn has_next_page(&self) -> bool { - self.has_more - } - } - - impl OffsetPaginated for MockPage { - fn items(&self) -> &[String] { - &self.data - } - fn has_next_page(&self) -> bool { - self.has_more - } - } - - #[test] - fn test_paginated_trait() { - let page = MockPage { - data: vec!["a".to_string(), "b".to_string()], - cursor: Some("next".to_string()), - has_more: true, - }; - assert_eq!(Paginated::items(&page).len(), 2); - assert_eq!(page.next_cursor(), Some("next")); - assert!(Paginated::has_next_page(&page)); - } - - #[test] - fn test_offset_paginated_default_page_size() { - let page = MockPage { - data: vec!["a".to_string(), "b".to_string(), "c".to_string()], - cursor: None, - has_more: false, - }; - assert_eq!(OffsetPaginated::page_size(&page), 3); - } -} diff --git a/seed/rust-sdk/allof/src/core/query_parameter_builder.rs b/seed/rust-sdk/allof/src/core/query_parameter_builder.rs deleted file mode 100644 index 6f1a6976ec2b..000000000000 --- a/seed/rust-sdk/allof/src/core/query_parameter_builder.rs +++ /dev/null @@ -1,576 +0,0 @@ -use chrono::{DateTime, TimeZone}; -use serde::Serialize; - -/// Modern query builder with type-safe method chaining -/// Provides a clean, Swift-like API for building HTTP query parameters -#[derive(Debug, Default)] -pub struct QueryBuilder { - params: Vec<(String, String)>, -} - -impl QueryBuilder { - /// Create a new query parameter builder - pub fn new() -> Self { - Self::default() - } - - /// Add a string parameter (accept both required/optional) - pub fn string(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v)); - } - self - } - - /// Add multiple string parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn string_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v)); - } - } - self - } - - /// Add an integer parameter (accept both required/optional) - pub fn int(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - self - } - - /// Add multiple integer parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn int_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - } - self - } - - /// Add a float parameter - pub fn float(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - self - } - - /// Add multiple float parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn float_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - } - self - } - - /// Add a boolean parameter - pub fn bool(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - self - } - - /// Add multiple boolean parameters with the same key (for allow-multiple query params) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn bool_array(mut self, key: &str, values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for value in values { - if let Some(v) = value.into() { - self.params.push((key.to_string(), v.to_string())); - } - } - self - } - - /// Add a datetime parameter (any DateTime timezone) - pub fn datetime( - mut self, - key: &str, - value: impl Into>>, - ) -> Self - where - Tz::Offset: std::fmt::Display, - { - if let Some(v) = value.into() { - self.params.push(( - key.to_string(), - v.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - )); - } - self - } - - /// Add a date parameter (converts NaiveDate to DateTime) - pub fn date(mut self, key: &str, value: impl Into>) -> Self { - if let Some(v) = value.into() { - // Convert NaiveDate to DateTime at start of day - let datetime = v.and_hms_opt(0, 0, 0).unwrap().and_utc(); - self.params.push(( - key.to_string(), - datetime.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - )); - } - self - } - - /// Add any serializable parameter (for enums and complex types) - pub fn serialize(mut self, key: &str, value: Option) -> Self { - if let Some(v) = value { - // For enums that implement Display, use the Display implementation - // to avoid JSON quotes in query parameters - if let Ok(serialized) = serde_json::to_string(&v) { - // Remove JSON quotes if the value is a simple string - let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { - serialized.trim_matches('"').to_string() - } else { - serialized - }; - self.params.push((key.to_string(), cleaned)); - } - } - self - } - - /// Add multiple serializable parameters with the same key (for allow-multiple query params with enums) - /// Accepts both Vec and Vec>, adding each non-None value as a separate query parameter - pub fn serialize_array( - mut self, - key: &str, - values: impl IntoIterator, - ) -> Self { - for value in values { - if let Ok(serialized) = serde_json::to_string(&value) { - // Skip null values (from Option::None) - if serialized == "null" { - continue; - } - // Remove JSON quotes if the value is a simple string - let cleaned = if serialized.starts_with('"') && serialized.ends_with('"') { - serialized.trim_matches('"').to_string() - } else { - serialized - }; - self.params.push((key.to_string(), cleaned)); - } - } - self - } - - /// Parse and add a structured query string - /// Handles complex query patterns like: - /// - "key:value" patterns - /// - "key:value1,value2" (comma-separated values) - /// - Quoted values: "key:\"value with spaces\"" - /// - Space-separated terms (treated as AND logic) - pub fn structured_query(mut self, key: &str, value: impl Into>) -> Self { - if let Some(query_str) = value.into() { - if let Ok(parsed_params) = parse_structured_query(&query_str) { - self.params.extend(parsed_params); - } else { - // Fall back to simple query parameter if parsing fails - self.params.push((key.to_string(), query_str)); - } - } - self - } - - /// Build the final query parameters - pub fn build(self) -> Option> { - if self.params.is_empty() { - None - } else { - Some(self.params) - } - } -} - -/// Errors that can occur during structured query parsing -#[derive(Debug)] -pub enum QueryBuilderError { - InvalidQuerySyntax(String), -} - -impl std::fmt::Display for QueryBuilderError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - QueryBuilderError::InvalidQuerySyntax(msg) => { - write!(f, "Invalid query syntax: {}", msg) - } - } - } -} - -impl std::error::Error for QueryBuilderError {} - -/// Parse structured query strings like "key:value key2:value1,value2" -/// Used for complex filtering patterns in APIs like Foxglove -/// -/// Supported patterns: -/// - Simple: "status:active" -/// - Multiple values: "type:sensor,camera" -/// - Quoted values: "location:\"New York\"" -/// - Complex: "status:active type:sensor location:\"San Francisco\"" -pub fn parse_structured_query(query: &str) -> Result, QueryBuilderError> { - let mut params = Vec::new(); - let terms = tokenize_query(query); - - for term in terms { - if let Some((key, values)) = term.split_once(':') { - // Handle comma-separated values - for value in values.split(',') { - let clean_value = value.trim_matches('"'); // Remove quotes - params.push((key.to_string(), clean_value.to_string())); - } - } else { - // For terms without colons, return error to be explicit about expected format - return Err(QueryBuilderError::InvalidQuerySyntax(format!( - "Cannot parse term '{}' - expected 'key:value' format for structured queries", - term - ))); - } - } - - Ok(params) -} - -/// Tokenize a query string, properly handling quoted strings -fn tokenize_query(input: &str) -> Vec { - let mut tokens = Vec::new(); - let mut current_token = String::new(); - let mut in_quotes = false; - let mut chars = input.chars().peekable(); - - while let Some(c) = chars.next() { - match c { - '"' => { - // Toggle quote state and include the quote in the token - in_quotes = !in_quotes; - current_token.push(c); - } - ' ' if !in_quotes => { - // Space outside quotes - end current token - if !current_token.is_empty() { - tokens.push(current_token.trim().to_string()); - current_token.clear(); - } - } - _ => { - // Any other character (including spaces inside quotes) - current_token.push(c); - } - } - } - - // Add the last token if there is one - if !current_token.is_empty() { - tokens.push(current_token.trim().to_string()); - } - - tokens -} - -#[cfg(test)] -mod tests { - use super::*; - use chrono::{NaiveDate, TimeZone, Utc}; - - // =========================== - // QueryBuilder tests - // =========================== - - #[test] - fn test_empty_builder_returns_none() { - let result = QueryBuilder::new().build(); - assert!(result.is_none()); - } - - #[test] - fn test_string_param_some() { - let result = QueryBuilder::new() - .string("name", Some("alice".to_string())) - .build(); - assert_eq!( - result, - Some(vec![("name".to_string(), "alice".to_string())]) - ); - } - - #[test] - fn test_string_param_none_skipped() { - let result = QueryBuilder::new().string("name", None::).build(); - assert!(result.is_none()); - } - - #[test] - fn test_int_param() { - let result = QueryBuilder::new().int("page", Some(42i64)).build(); - assert_eq!(result, Some(vec![("page".to_string(), "42".to_string())])); - } - - #[test] - fn test_int_param_none_skipped() { - let result = QueryBuilder::new().int("page", None::).build(); - assert!(result.is_none()); - } - - #[test] - fn test_float_param() { - let result = QueryBuilder::new().float("score", Some(3.14f64)).build(); - assert_eq!( - result, - Some(vec![("score".to_string(), "3.14".to_string())]) - ); - } - - #[test] - fn test_bool_param() { - let result = QueryBuilder::new().bool("active", Some(true)).build(); - assert_eq!( - result, - Some(vec![("active".to_string(), "true".to_string())]) - ); - } - - #[test] - fn test_datetime_param_formats_rfc3339() { - let dt = Utc.with_ymd_and_hms(2024, 1, 15, 9, 30, 0).unwrap(); - let result = QueryBuilder::new().datetime("since", Some(dt)).build(); - assert_eq!( - result, - Some(vec![( - "since".to_string(), - "2024-01-15T09:30:00Z".to_string() - )]) - ); - } - - #[test] - fn test_date_param_converts_to_midnight_utc() { - let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(); - let result = QueryBuilder::new().date("on", Some(date)).build(); - assert_eq!( - result, - Some(vec![("on".to_string(), "2024-01-15T00:00:00Z".to_string())]) - ); - } - - #[test] - fn test_string_array_multiple_entries() { - let result = QueryBuilder::new() - .string_array( - "tag", - vec!["a".to_string(), "b".to_string(), "c".to_string()], - ) - .build(); - assert_eq!( - result, - Some(vec![ - ("tag".to_string(), "a".to_string()), - ("tag".to_string(), "b".to_string()), - ("tag".to_string(), "c".to_string()), - ]) - ); - } - - #[test] - fn test_int_array() { - let result = QueryBuilder::new() - .int_array("ids", vec![1i64, 2, 3]) - .build(); - assert_eq!( - result, - Some(vec![ - ("ids".to_string(), "1".to_string()), - ("ids".to_string(), "2".to_string()), - ("ids".to_string(), "3".to_string()), - ]) - ); - } - - #[test] - fn test_float_array() { - let result = QueryBuilder::new() - .float_array("scores", vec![1.1f64, 2.2]) - .build(); - assert_eq!( - result, - Some(vec![ - ("scores".to_string(), "1.1".to_string()), - ("scores".to_string(), "2.2".to_string()), - ]) - ); - } - - #[test] - fn test_bool_array() { - let result = QueryBuilder::new() - .bool_array("flags", vec![true, false]) - .build(); - assert_eq!( - result, - Some(vec![ - ("flags".to_string(), "true".to_string()), - ("flags".to_string(), "false".to_string()), - ]) - ); - } - - #[test] - fn test_serialize_strips_json_quotes() { - let result = QueryBuilder::new() - .serialize("status", Some("active")) - .build(); - assert_eq!( - result, - Some(vec![("status".to_string(), "active".to_string())]) - ); - } - - #[test] - fn test_serialize_none_skipped() { - let result = QueryBuilder::new() - .serialize::("status", None) - .build(); - assert!(result.is_none()); - } - - #[test] - fn test_serialize_numeric_no_quotes() { - let result = QueryBuilder::new().serialize("count", Some(42)).build(); - assert_eq!(result, Some(vec![("count".to_string(), "42".to_string())])); - } - - #[test] - fn test_serialize_array_skips_null() { - let values: Vec> = vec![Some("a"), None, Some("b")]; - let result = QueryBuilder::new().serialize_array("items", values).build(); - assert_eq!( - result, - Some(vec![ - ("items".to_string(), "a".to_string()), - ("items".to_string(), "b".to_string()), - ]) - ); - } - - #[test] - fn test_method_chaining() { - let result = QueryBuilder::new() - .string("name", Some("alice".to_string())) - .int("page", Some(1i64)) - .bool("active", Some(true)) - .build(); - assert_eq!( - result, - Some(vec![ - ("name".to_string(), "alice".to_string()), - ("page".to_string(), "1".to_string()), - ("active".to_string(), "true".to_string()), - ]) - ); - } - - // =========================== - // parse_structured_query tests - // =========================== - - #[test] - fn test_parse_simple_key_value() { - let result = parse_structured_query("status:active").unwrap(); - assert_eq!(result, vec![("status".to_string(), "active".to_string())]); - } - - #[test] - fn test_parse_comma_separated_values() { - let result = parse_structured_query("type:sensor,camera").unwrap(); - assert_eq!( - result, - vec![ - ("type".to_string(), "sensor".to_string()), - ("type".to_string(), "camera".to_string()), - ] - ); - } - - #[test] - fn test_parse_multiple_terms() { - let result = parse_structured_query("status:active type:sensor").unwrap(); - assert_eq!( - result, - vec![ - ("status".to_string(), "active".to_string()), - ("type".to_string(), "sensor".to_string()), - ] - ); - } - - #[test] - fn test_parse_quoted_value() { - let result = parse_structured_query("location:\"New York\"").unwrap(); - assert_eq!( - result, - vec![("location".to_string(), "New York".to_string())] - ); - } - - #[test] - fn test_parse_bare_word_returns_error() { - let result = parse_structured_query("bareword"); - assert!(result.is_err()); - } - - #[test] - fn test_structured_query_builder_fallback() { - // When parsing fails, structured_query falls back to simple param - let result = QueryBuilder::new() - .structured_query("q", Some("bareword".to_string())) - .build(); - assert_eq!( - result, - Some(vec![("q".to_string(), "bareword".to_string())]) - ); - } - - #[test] - fn test_structured_query_builder_parses() { - let result = QueryBuilder::new() - .structured_query("q", Some("status:active".to_string())) - .build(); - assert_eq!( - result, - Some(vec![("status".to_string(), "active".to_string())]) - ); - } - - #[test] - fn test_structured_query_none_skipped() { - let result = QueryBuilder::new() - .structured_query("q", None::) - .build(); - assert!(result.is_none()); - } -} diff --git a/seed/rust-sdk/allof/src/core/request_options.rs b/seed/rust-sdk/allof/src/core/request_options.rs deleted file mode 100644 index 80508c9dd6c3..000000000000 --- a/seed/rust-sdk/allof/src/core/request_options.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::collections::HashMap; -/// Options for customizing individual requests -#[derive(Debug, Clone, Default)] -pub struct RequestOptions { - /// API key for authentication (overrides client-level API key) - pub api_key: Option, - /// Bearer token for authentication (overrides client-level token) - pub token: Option, - /// Maximum number of retry attempts for failed requests - pub max_retries: Option, - /// Request timeout in seconds (overrides client-level timeout) - pub timeout_seconds: Option, - /// Additional headers to include in the request - pub additional_headers: HashMap, - /// Additional query parameters to include in the request - pub additional_query_params: HashMap, -} - -impl RequestOptions { - pub fn new() -> Self { - Self::default() - } - - pub fn api_key(mut self, key: impl Into) -> Self { - self.api_key = Some(key.into()); - self - } - - pub fn token(mut self, token: impl Into) -> Self { - self.token = Some(token.into()); - self - } - - pub fn max_retries(mut self, retries: u32) -> Self { - self.max_retries = Some(retries); - self - } - - pub fn timeout_seconds(mut self, timeout: u64) -> Self { - self.timeout_seconds = Some(timeout); - self - } - - pub fn additional_header(mut self, key: impl Into, value: impl Into) -> Self { - self.additional_headers.insert(key.into(), value.into()); - self - } - - pub fn additional_query_param( - mut self, - key: impl Into, - value: impl Into, - ) -> Self { - self.additional_query_params - .insert(key.into(), value.into()); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_default_has_no_values() { - let opts = RequestOptions::default(); - assert!(opts.api_key.is_none()); - assert!(opts.token.is_none()); - assert!(opts.max_retries.is_none()); - assert!(opts.timeout_seconds.is_none()); - assert!(opts.additional_headers.is_empty()); - assert!(opts.additional_query_params.is_empty()); - } - - #[test] - fn test_new_equals_default() { - let opts = RequestOptions::new(); - assert!(opts.api_key.is_none()); - assert!(opts.token.is_none()); - assert!(opts.max_retries.is_none()); - assert!(opts.timeout_seconds.is_none()); - assert!(opts.additional_headers.is_empty()); - assert!(opts.additional_query_params.is_empty()); - } - - #[test] - fn test_api_key() { - let opts = RequestOptions::new().api_key("my-key"); - assert_eq!(opts.api_key, Some("my-key".to_string())); - } - - #[test] - fn test_token() { - let opts = RequestOptions::new().token("my-token"); - assert_eq!(opts.token, Some("my-token".to_string())); - } - - #[test] - fn test_max_retries() { - let opts = RequestOptions::new().max_retries(3); - assert_eq!(opts.max_retries, Some(3)); - } - - #[test] - fn test_timeout_seconds() { - let opts = RequestOptions::new().timeout_seconds(30); - assert_eq!(opts.timeout_seconds, Some(30)); - } - - #[test] - fn test_additional_header() { - let opts = RequestOptions::new().additional_header("X-Custom", "value"); - assert_eq!( - opts.additional_headers.get("X-Custom"), - Some(&"value".to_string()) - ); - } - - #[test] - fn test_additional_headers_accumulate() { - let opts = RequestOptions::new() - .additional_header("X-First", "1") - .additional_header("X-Second", "2"); - assert_eq!(opts.additional_headers.len(), 2); - assert_eq!( - opts.additional_headers.get("X-First"), - Some(&"1".to_string()) - ); - assert_eq!( - opts.additional_headers.get("X-Second"), - Some(&"2".to_string()) - ); - } - - #[test] - fn test_additional_query_param() { - let opts = RequestOptions::new().additional_query_param("page", "1"); - assert_eq!( - opts.additional_query_params.get("page"), - Some(&"1".to_string()) - ); - } - - #[test] - fn test_additional_query_params_accumulate() { - let opts = RequestOptions::new() - .additional_query_param("page", "1") - .additional_query_param("limit", "10"); - assert_eq!(opts.additional_query_params.len(), 2); - assert_eq!( - opts.additional_query_params.get("page"), - Some(&"1".to_string()) - ); - assert_eq!( - opts.additional_query_params.get("limit"), - Some(&"10".to_string()) - ); - } - - #[test] - fn test_full_method_chaining() { - let opts = RequestOptions::new() - .api_key("key") - .token("tok") - .max_retries(5) - .timeout_seconds(60) - .additional_header("X-Foo", "bar") - .additional_query_param("q", "search"); - assert_eq!(opts.api_key, Some("key".to_string())); - assert_eq!(opts.token, Some("tok".to_string())); - assert_eq!(opts.max_retries, Some(5)); - assert_eq!(opts.timeout_seconds, Some(60)); - assert_eq!(opts.additional_headers.len(), 1); - assert_eq!(opts.additional_query_params.len(), 1); - } -} diff --git a/seed/rust-sdk/allof/src/core/utils.rs b/seed/rust-sdk/allof/src/core/utils.rs deleted file mode 100644 index 323676f39ea2..000000000000 --- a/seed/rust-sdk/allof/src/core/utils.rs +++ /dev/null @@ -1,77 +0,0 @@ -/// URL building utilities -/// Safely join a base URL with a path, handling slashes properly -/// -/// # Examples -/// ``` -/// use example_api::utils::url::join_url; -/// -/// assert_eq!(join_url("https://api.example.com", "users"), "https://api.example.com/users"); -/// assert_eq!(join_url("https://api.example.com/", "users"), "https://api.example.com/users"); -/// assert_eq!(join_url("https://api.example.com", "/users"), "https://api.example.com/users"); -/// assert_eq!(join_url("https://api.example.com/", "/users"), "https://api.example.com/users"); -/// ``` -pub fn join_url(base_url: &str, path: &str) -> String { - format!( - "{}/{}", - base_url.trim_end_matches('/'), - path.trim_start_matches('/') - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_join_url_no_slashes() { - assert_eq!( - join_url("https://api.example.com", "users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_trailing_slash_on_base() { - assert_eq!( - join_url("https://api.example.com/", "users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_leading_slash_on_path() { - assert_eq!( - join_url("https://api.example.com", "/users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_both_slashes() { - assert_eq!( - join_url("https://api.example.com/", "/users"), - "https://api.example.com/users" - ); - } - - #[test] - fn test_join_url_multi_segment_path() { - assert_eq!( - join_url("https://api.example.com", "v1/users/123"), - "https://api.example.com/v1/users/123" - ); - } - - #[test] - fn test_join_url_empty_path() { - assert_eq!( - join_url("https://api.example.com", ""), - "https://api.example.com/" - ); - } - - #[test] - fn test_join_url_empty_base() { - assert_eq!(join_url("", "users"), "/users"); - } -} diff --git a/seed/rust-sdk/allof/src/environment.rs b/seed/rust-sdk/allof/src/environment.rs deleted file mode 100644 index 9034ff803954..000000000000 --- a/seed/rust-sdk/allof/src/environment.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum Environment { - #[serde(rename = "default")] - Default, -} -impl Environment { - pub fn url(&self) -> &'static str { - match self { - Self::Default => "https://api.example.com", - } - } -} -impl Default for Environment { - fn default() -> Self { - Self::Default - } -} diff --git a/seed/rust-sdk/allof/src/error.rs b/seed/rust-sdk/allof/src/error.rs deleted file mode 100644 index 655d945d71f1..000000000000 --- a/seed/rust-sdk/allof/src/error.rs +++ /dev/null @@ -1,54 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ApiError { - #[error("HTTP error {status}: {message}")] - Http { status: u16, message: String }, - #[error("Network error: {0}")] - Network(reqwest::Error), - #[error("Serialization error: {0}")] - Serialization(serde_json::Error), - #[error("Configuration error: {0}")] - Configuration(String), - #[error("Invalid header value")] - InvalidHeader, - #[error("Could not clone request for retry")] - RequestClone, - #[error("SSE stream terminated")] - StreamTerminated, - #[error("SSE stream timed out waiting for next event")] - StreamTimeout, - #[error("SSE parse error: {0}")] - SseParseError(String), -} - -impl ApiError { - pub fn from_response(status_code: u16, body: Option<&str>) -> Self { - match status_code { - _ => Self::Http { - status: status_code, - message: body.unwrap_or("Unknown error").to_string(), - }, - } - } -} - -/// Error returned when a required field was not set on a builder. -#[derive(Debug)] -pub struct BuildError { - field: &'static str, -} - -impl BuildError { - pub fn missing_field(field: &'static str) -> Self { - Self { field } - } -} - -impl std::fmt::Display for BuildError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "`{}` was not set but is required", self.field) - } -} - -impl std::error::Error for BuildError {} diff --git a/seed/rust-sdk/allof/src/lib.rs b/seed/rust-sdk/allof/src/lib.rs deleted file mode 100644 index 626894135f95..000000000000 --- a/seed/rust-sdk/allof/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! # allOf Composition SDK -//! -//! The official Rust SDK for the allOf Composition. -//! -//! ## Getting Started -//! -//! ```rust -//! use seed_api::prelude::*; -//! -//! #[tokio::main] -//! async fn main() { -//! let config = ClientConfig { -//! ..Default::default() -//! }; -//! let client = ApiClient::new(config).expect("Failed to build client"); -//! client -//! .search_rule_types( -//! &SearchRuleTypesQueryRequest { -//! ..Default::default() -//! }, -//! None, -//! ) -//! .await; -//! } -//! ``` -//! -//! ## Modules -//! -//! - [`api`] - Core API types and models -//! - [`client`] - Client implementations -//! - [`config`] - Configuration options -//! - [`core`] - Core utilities and infrastructure -//! - [`error`] - Error types and handling -//! - [`prelude`] - Common imports for convenience - -pub mod api; -pub mod client; -pub mod config; -pub mod core; -pub mod environment; -pub mod error; -pub mod prelude; - -pub use api::*; -pub use client::*; -pub use config::*; -pub use core::*; -pub use environment::*; -pub use error::{ApiError, BuildError}; diff --git a/seed/rust-sdk/allof/src/prelude.rs b/seed/rust-sdk/allof/src/prelude.rs deleted file mode 100644 index 5656ffc7e1b7..000000000000 --- a/seed/rust-sdk/allof/src/prelude.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Prelude module for convenient imports -//! -//! This module re-exports the most commonly used types and traits. -//! Import it with: `use seed_api::prelude::*;` - -// Client and configuration -pub use crate::config::ClientConfig; -pub use crate::core::{HttpClient, RequestOptions}; -pub use crate::error::{ApiError, BuildError}; - -// Main client and resource clients -pub use crate::api::*; - -// Re-export commonly used external types -pub use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, Utc}; -pub use serde::{Deserialize, Serialize}; -pub use serde_json::{json, Value}; -pub use std::collections::{HashMap, HashSet}; -pub use std::fmt; diff --git a/seed/swift-sdk/allof-inline/.fern/metadata.json b/seed/swift-sdk/allof-inline/.fern/metadata.json deleted file mode 100644 index 56c0dd230a46..000000000000 --- a/seed/swift-sdk/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-swift-sdk", - "generatorVersion": "local", - "generatorConfig": {}, - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Package.swift b/seed/swift-sdk/allof-inline/Package.swift deleted file mode 100644 index a66695fa5685..000000000000 --- a/seed/swift-sdk/allof-inline/Package.swift +++ /dev/null @@ -1,31 +0,0 @@ -// swift-tools-version: 5.7 - -import PackageDescription - -let package = Package( - name: "Api", - platforms: [ - .iOS(.v15), - .macOS(.v12), - .tvOS(.v15), - .watchOS(.v8) - ], - products: [ - .library( - name: "Api", - targets: ["Api"] - ) - ], - dependencies: [], - targets: [ - .target( - name: "Api", - path: "Sources" - ), - .testTarget( - name: "ApiTests", - dependencies: ["Api"], - path: "Tests" - ) - ] -) diff --git a/seed/swift-sdk/allof-inline/README.md b/seed/swift-sdk/allof-inline/README.md deleted file mode 100644 index 65c624269c6a..000000000000 --- a/seed/swift-sdk/allof-inline/README.md +++ /dev/null @@ -1,180 +0,0 @@ -# Seed Swift Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FSwift) -![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-orange.svg) - -The Seed Swift library provides convenient access to the Seed APIs from Swift. - -## Table of Contents - -- [Requirements](#requirements) -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Request Types](#request-types) -- [Advanced](#advanced) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) - - [Timeouts](#timeouts) - - [Custom Networking Client](#custom-networking-client) -- [Contributing](#contributing) - -## Requirements - -This SDK requires: -- Swift 5.7+ -- iOS 15+ -- macOS 12+ -- tvOS 15+ -- watchOS 8+ - -## Installation - -With Swift Package Manager (SPM), add the following to the top-level `dependencies` array within your `Package.swift` file: - -```swift -dependencies: [ - .package(url: "https://github.com/allof-inline/fern", from: "0.0.1"), -] -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```swift -import Api - -let client = ApiClient( - ..., - environment: .default -) -``` - -## Errors - -The SDK throws a single error enum for all failures. Client-side issues encoding/decoding failures and network errors use dedicated cases, while non-success HTTP responses are wrapped in an `HTTPError` that exposes the status code, a simple classification and an optional decoded message. - -```swift -import Api - -let client = ApiClient(...) - -do { - let response = try await client.createRule(...) - // Handle successful response -} catch let error as ApiError { - switch error { - case .httpError(let httpError): - print("Status code:", httpError.statusCode) - print("Kind:", httpError.kind) - print("Message:", httpError.body?.message ?? httpError.localizedDescription) - case .encodingError(let underlying): - print("Encoding error:", underlying) - case .networkError(let underlying): - print("Network error:", underlying) - default: - print("Other client error:", error) - } -} catch { - print("Unexpected error:", error) -} -``` - -## Request Types - -The SDK exports all request types as Swift structs. Simply import the SDK module to access them: - -```swift -import Api - -let request = Requests.RuleCreateRequest( - ... -) -``` - -## Advanced - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `additionalHeaders` request option. - -```swift -try await client.createRule(..., requestOptions: .init( - additionalHeaders: [ - "X-Custom-Header": "custom value" - ] -)) -``` - -### Additional Query String Parameters - -If you would like to send additional query string parameters as part of the request, use the `additionalQueryParameters` request option. - -```swift -try await client.createRule(..., requestOptions: .init( - additionalQueryParameters: [ - "custom_query_param_key": "custom_query_param_value" - ] -)) -``` - -### Timeouts - -The SDK defaults to a 60-second timeout. Use the `timeout` option to configure this behavior. - -```swift -try await client.createRule(..., requestOptions: .init( - timeout: 30 -)) -``` - -### Custom Networking Client - -The SDK allows you to customize the underlying `URLSession` used for HTTP requests. Use the `urlSession` option to provide your own configured `URLSession` instance. - -```swift -import Foundation -import Api - -let client = ApiClient( - ..., - urlSession: // Provide your implementation here -) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/swift-sdk/allof-inline/Snippets/Example0.swift b/seed/swift-sdk/allof-inline/Snippets/Example0.swift deleted file mode 100644 index 6a8a354329f0..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example0.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.searchRuleTypes() -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example1.swift b/seed/swift-sdk/allof-inline/Snippets/Example1.swift deleted file mode 100644 index 165f0fa4c377..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example1.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.searchRuleTypes(query: "query") -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example2.swift b/seed/swift-sdk/allof-inline/Snippets/Example2.swift deleted file mode 100644 index 77e7ea37ecaf..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example2.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example3.swift b/seed/swift-sdk/allof-inline/Snippets/Example3.swift deleted file mode 100644 index 77e7ea37ecaf..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example3.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example4.swift b/seed/swift-sdk/allof-inline/Snippets/Example4.swift deleted file mode 100644 index 25d9aafe2c0e..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example4.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.listUsers() -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example5.swift b/seed/swift-sdk/allof-inline/Snippets/Example5.swift deleted file mode 100644 index 25d9aafe2c0e..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example5.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.listUsers() -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example6.swift b/seed/swift-sdk/allof-inline/Snippets/Example6.swift deleted file mode 100644 index cd12bd038474..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example6.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getEntity() -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example7.swift b/seed/swift-sdk/allof-inline/Snippets/Example7.swift deleted file mode 100644 index cd12bd038474..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example7.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getEntity() -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example8.swift b/seed/swift-sdk/allof-inline/Snippets/Example8.swift deleted file mode 100644 index 935ba9bb5c06..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example8.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getOrganization() -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Snippets/Example9.swift b/seed/swift-sdk/allof-inline/Snippets/Example9.swift deleted file mode 100644 index 935ba9bb5c06..000000000000 --- a/seed/swift-sdk/allof-inline/Snippets/Example9.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getOrganization() -} - -try await main() diff --git a/seed/swift-sdk/allof-inline/Sources/ApiClient.swift b/seed/swift-sdk/allof-inline/Sources/ApiClient.swift deleted file mode 100644 index 3bb0c30a67c5..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/ApiClient.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Foundation - -/// Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. -public final class ApiClient: Sendable { - private let httpClient: HTTPClient - - /// Initialize the client with the specified configuration. - /// - /// - Parameter baseURL: The base URL to use for requests from the client. If not provided, the default base URL will be used. - /// - Parameter headers: Additional headers to send with each request. - /// - Parameter timeout: Request timeout in seconds. Defaults to 60 seconds. Ignored if a custom `urlSession` is provided. - /// - Parameter maxRetries: Maximum number of retries for failed requests. Defaults to 2. - /// - Parameter urlSession: Custom `URLSession` to use for requests. If not provided, a default session will be created with the specified timeout. - public convenience init( - baseURL: String = ApiEnvironment.default.rawValue, - headers: [String: String]? = nil, - timeout: Int? = nil, - maxRetries: Int? = nil, - urlSession: Networking.URLSession? = nil - ) { - self.init( - baseURL: baseURL, - headerAuth: nil, - bearerAuth: nil, - basicAuth: nil, - headers: headers, - timeout: timeout, - maxRetries: maxRetries, - urlSession: urlSession - ) - } - - init( - baseURL: String, - headerAuth: ClientConfig.HeaderAuth? = nil, - bearerAuth: ClientConfig.BearerAuth? = nil, - basicAuth: ClientConfig.BasicAuth? = nil, - headers: [String: String]? = nil, - timeout: Int? = nil, - maxRetries: Int? = nil, - urlSession: Networking.URLSession? = nil - ) { - let config = ClientConfig( - baseURL: baseURL, - headerAuth: headerAuth, - bearerAuth: bearerAuth, - basicAuth: basicAuth, - headers: headers, - timeout: timeout, - maxRetries: maxRetries, - urlSession: urlSession - ) - self.httpClient = HTTPClient(config: config) - } - - public func searchRuleTypes(query: String? = nil, requestOptions: RequestOptions? = nil) async throws -> RuleTypeSearchResponse { - return try await httpClient.performRequest( - method: .get, - path: "/rule-types", - queryParams: [ - "query": query.map { .string($0) } - ], - requestOptions: requestOptions, - responseType: RuleTypeSearchResponse.self - ) - } - - public func createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions? = nil) async throws -> RuleResponse { - return try await httpClient.performRequest( - method: .post, - path: "/rules", - body: request, - requestOptions: requestOptions, - responseType: RuleResponse.self - ) - } - - public func listUsers(requestOptions: RequestOptions? = nil) async throws -> UserSearchResponse { - return try await httpClient.performRequest( - method: .get, - path: "/users", - requestOptions: requestOptions, - responseType: UserSearchResponse.self - ) - } - - public func getEntity(requestOptions: RequestOptions? = nil) async throws -> CombinedEntity { - return try await httpClient.performRequest( - method: .get, - path: "/entities", - requestOptions: requestOptions, - responseType: CombinedEntity.self - ) - } - - public func getOrganization(requestOptions: RequestOptions? = nil) async throws -> Organization { - return try await httpClient.performRequest( - method: .get, - path: "/organizations", - requestOptions: requestOptions, - responseType: Organization.self - ) - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift b/seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift deleted file mode 100644 index 817df36e4a46..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/ApiEnvironment.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public enum ApiEnvironment: String, CaseIterable { - case `default` = "https://api.example.com" -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/ApiError.swift b/seed/swift-sdk/allof-inline/Sources/ApiError.swift deleted file mode 100644 index 37bf4a118e61..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/ApiError.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -/// High-level error type thrown by generated Swift SDKs. -/// -/// Request / client-side failures are represented as dedicated cases and -/// HTTP response failures are wrapped in an `HTTPError` that classifies the status code. -public enum ApiError: Swift.Error { - // MARK: - Client / transport errors - - /// The request URL could not be constructed. - case invalidURL(Swift.String) - - /// The request body could not be encoded. - case encodingError(Swift.Error) - - /// The response body could not be decoded. - case decodingError(Swift.Error) - - /// The SDK received a response it could not interpret as a valid HTTP response. - case invalidResponse - - /// An underlying networking error occurred (e.g., connection reset). - case networkError(Swift.Error) - - /// The request timed out. - case timeout(Swift.Error?) - - // MARK: - HTTP response errors - - /// An error HTTP response was returned by the server. - case httpError(HTTPError) - - // MARK: - Description - - public var errorDescription: Swift.String? { - switch self { - case .invalidURL(let url): - return "Invalid URL '\(url)'" - case .encodingError(let error): - return "Failed to encode request: \(error.localizedDescription)" - case .decodingError(let error): - return "Failed to decode response: \(error.localizedDescription)" - case .invalidResponse: - return "Invalid response received" - case .networkError(let error): - return "Network error: \(error.localizedDescription)" - case .timeout(let underlying): - if let underlying { - return "Request timed out: \(underlying.localizedDescription)" - } else { - return "Request timed out" - } - case .httpError(let httpError): - return httpError.localizedDescription - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift b/seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift deleted file mode 100644 index ddf326ffbfe6..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Data+String.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -// MARK: - Data + String Extensions -extension Foundation.Data { - /// Safely appends a UTF-8 encoded string to the data - /// - /// - Parameter string: The string to append - mutating func appendUTF8String(_ string: Swift.String) { - guard let data = string.data(using: .utf8) else { - assertionFailure("Failed to encode string to UTF-8: \(string)") - return - } - self.append(data) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift deleted file mode 100644 index 445e45cbdb96..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTP.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation - -enum HTTP { - enum Method: Swift.String, Swift.CaseIterable { - case get = "GET" - case post = "POST" - case put = "PUT" - case delete = "DELETE" - case patch = "PATCH" - case head = "HEAD" - } - - enum ContentType: Swift.String, Swift.CaseIterable { - case applicationJson = "application/json" - case applicationOctetStream = "application/octet-stream" - case multipartFormData = "multipart/form-data" - } - - enum RequestBody { - case jsonEncodable(any Swift.Encodable) - case data(Foundation.Data) - case multipartFormData(MultipartFormData) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift deleted file mode 100644 index 18e8927122ef..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Networking/HTTPClient.swift +++ /dev/null @@ -1,397 +0,0 @@ -import Foundation - -final class HTTPClient: Swift.Sendable { - private let clientConfig: ClientConfig - private let jsonEncoder = Serde.jsonEncoder - private let jsonDecoder = Serde.jsonDecoder - - private static let initialRetryDelay: Foundation.TimeInterval = 1.0 // 1 second - private static let maxRetryDelay: Foundation.TimeInterval = 60.0 // 60 seconds - private static let jitterFactor: Swift.Double = 0.2 // 20% jitter - - init(config: ClientConfig) { - self.clientConfig = config - } - - /// Performs a request with no response. - func performRequest( - method: HTTP.Method, - path: Swift.String, - contentType requestContentType: HTTP.ContentType = .applicationJson, - headers requestHeaders: [Swift.String: Swift.String?] = [:], - queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], - body requestBody: Any? = nil, - requestOptions: RequestOptions? = nil - ) async throws { - _ = try await performRequest( - method: method, - path: path, - contentType: requestContentType, - headers: requestHeaders, - queryParams: requestQueryParams, - body: requestBody, - requestOptions: requestOptions, - responseType: Foundation.Data.self - ) - } - - /// Performs a request with the specified response type. - func performRequest( - method: HTTP.Method, - path: Swift.String, - contentType requestContentType: HTTP.ContentType = .applicationJson, - headers requestHeaders: [Swift.String: Swift.String?] = [:], - queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], - body requestBody: Any? = nil, - requestOptions: RequestOptions? = nil, - responseType: T.Type - ) async throws -> T { - let requestBody: HTTP.RequestBody? = requestBody.map { body in - if let multipartData = body as? MultipartFormData { - return .multipartFormData(multipartData) - } else if let data = body as? Foundation.Data { - return .data(data) - } else if let encodable = body as? any Swift.Encodable { - return .jsonEncodable(encodable) - } else { - preconditionFailure("Unsupported body type: \(type(of: body))") - } - } - - let request = try await buildRequest( - method: method, - path: path, - requestContentType: requestContentType, - requestHeaders: requestHeaders, - requestQueryParams: requestQueryParams, - requestBody: requestBody, - requestOptions: requestOptions - ) - - let (data, _) = try await executeRequestWithURLSession( - request, - requestOptions: requestOptions - ) - - if responseType == Foundation.Data.self { - if let data = data as? T { - return data - } else { - throw ApiError.invalidResponse - } - } - - if responseType == Swift.String.self { - if let string = Swift.String(data: data, encoding: .utf8) as? T { - return string - } else { - throw ApiError.invalidResponse - } - } - - do { - return try jsonDecoder.decode(responseType, from: data) - } catch { - throw ApiError.decodingError(error) - } - } - - private func buildRequest( - method: HTTP.Method, - path: Swift.String, - requestContentType: HTTP.ContentType, - requestHeaders: [Swift.String: Swift.String?], - requestQueryParams: [Swift.String: QueryParameter?], - requestBody: HTTP.RequestBody? = nil, - requestOptions: RequestOptions? = nil - ) async throws -> Networking.URLRequest { - // Init with URL - let url = buildRequestURL( - path: path, requestQueryParams: requestQueryParams, requestOptions: requestOptions - ) - var request = Networking.URLRequest(url: url) - - // Set timeout - if let timeout = requestOptions?.timeout { - request.timeoutInterval = Foundation.TimeInterval(timeout) - } - - // Set method - request.httpMethod = method.rawValue - - // Set headers - let headers = try await buildRequestHeaders( - requestBody: requestBody, - requestContentType: requestContentType, - requestHeaders: requestHeaders, - requestOptions: requestOptions - ) - for (key, value) in headers { - request.setValue(value, forHTTPHeaderField: key) - } - - // Set body - if let requestBody = requestBody { - request.httpBody = buildRequestBody( - requestBody: requestBody, - requestOptions: requestOptions - ) - } - - return request - } - - private func buildRequestURL( - path: Swift.String, - requestQueryParams: [Swift.String: QueryParameter?], - requestOptions: RequestOptions? = nil - ) -> URL { - let endpointURL = "\(clientConfig.baseURL)\(path)" - guard var components = Foundation.URLComponents(string: endpointURL) else { - preconditionFailure( - "Invalid URL '\(endpointURL)' - this indicates an unexpected error in the SDK." - ) - } - if !requestQueryParams.isEmpty { - let baseItems: [Foundation.URLQueryItem] = requestQueryParams.compactMap { key, value in - guard let unwrapped = value else { return nil } - let stringValue = unwrapped.toString() - guard !stringValue.isEmpty else { return nil } - return Foundation.URLQueryItem(name: key, value: stringValue) - } - if !baseItems.isEmpty { - components.queryItems = baseItems - } - } - if let additionalQueryParams = requestOptions?.additionalQueryParameters { - let extraItems = additionalQueryParams.compactMap { key, value in - value.isEmpty ? nil : Foundation.URLQueryItem(name: key, value: value) - } - if components.queryItems == nil { - components.queryItems = extraItems - } else { - components.queryItems?.append(contentsOf: extraItems) - } - } - guard let url = components.url else { - preconditionFailure( - "Failed to construct URL from components - this indicates an unexpected error in the SDK." - ) - } - return url - } - - private func buildRequestHeaders( - requestBody: HTTP.RequestBody?, - requestContentType: HTTP.ContentType, - requestHeaders: [Swift.String: Swift.String?], - requestOptions: RequestOptions? = nil - ) async throws -> [Swift.String: Swift.String] { - var headers = clientConfig.headers ?? [:] - - headers["Content-Type"] = buildContentTypeHeader( - requestBody: requestBody, - requestContentType: requestContentType - ) - - if let headerAuth = clientConfig.headerAuth { - headers[headerAuth.header] = requestOptions?.apiKey ?? headerAuth.key - } - if let basicAuthToken = clientConfig.basicAuth?.token { - headers["Authorization"] = "Basic \(basicAuthToken)" - } - if let bearerAuthToken = try await getBearerAuthToken(requestOptions) { - headers["Authorization"] = "Bearer \(bearerAuthToken)" - } - for (key, value) in requestHeaders { - if let value = value { - headers[key] = value - } - } - for (key, value) in requestOptions?.additionalHeaders ?? [:] { - headers[key] = value - } - - return headers - } - - private func buildContentTypeHeader( - requestBody: HTTP.RequestBody?, - requestContentType: HTTP.ContentType, - ) -> Swift.String { - var contentType = requestContentType.rawValue - if let requestBody, case .multipartFormData(let multipartData) = requestBody { - if contentType != HTTP.ContentType.multipartFormData.rawValue { - preconditionFailure( - "The content type for multipart form data requests must be multipart/form-data - this indicates an unexpected error in the SDK." - ) - } - // Multipart form data content type must include the boundary - contentType = "\(contentType); boundary=\(multipartData.boundary)" - } - return contentType - } - - private func getBearerAuthToken(_ requestOptions: RequestOptions?) async throws -> Swift.String? - { - if let tokenString = requestOptions?.token { - return tokenString - } - if let bearerAuth = clientConfig.bearerAuth { - return try await bearerAuth.token.retrieve() - } - return nil - } - - private func buildRequestBody( - requestBody: HTTP.RequestBody, - requestOptions: RequestOptions? = nil - ) -> Data { - switch requestBody { - case .jsonEncodable(let encodableBody): - do { - return try jsonEncoder.encode(encodableBody) - } catch { - preconditionFailure( - "Failed to encode request body: \(error) - this indicates an unexpected error in the SDK." - ) - } - case .data(let dataBody): - return dataBody - case .multipartFormData(let multipartData): - return multipartData.data() - } - } - - private func executeRequestWithURLSession( - _ request: Networking.URLRequest, - requestOptions: RequestOptions? = nil - ) async throws -> (Foundation.Data, Swift.String?) { - let maxRetries = requestOptions?.maxRetries ?? clientConfig.maxRetries - var lastResponse: (Foundation.Data, Networking.HTTPURLResponse)? - - for attempt in 0...maxRetries { - do { - let (data, response) = try await clientConfig.urlSession.data(for: request) - - guard let httpResponse = response as? Networking.HTTPURLResponse else { - throw ApiError.invalidResponse - } - - // Handle successful responses - if 200...299 ~= httpResponse.statusCode { - let contentType = httpResponse.value(forHTTPHeaderField: "Content-Type") - return (data, contentType) - } - - lastResponse = (data, httpResponse) - - if attempt < maxRetries && shouldRetry(statusCode: httpResponse.statusCode) { - let delay = getRetryDelay(response: httpResponse, retryAttempt: attempt) - try await _Concurrency.Task.sleep( - nanoseconds: Swift.UInt64(delay * 1_000_000_000)) - continue - } - - throw makeErrorFromResponse( - statusCode: httpResponse.statusCode, - data: data - ) - } catch { - let clientError: ApiError? - - // Treat timeouts as a first-class, non-retryable error - if let urlError = error as? Foundation.URLError, urlError.code == .timedOut { - clientError = .timeout(error) - } else if let existingClientError = error as? ApiError { - clientError = existingClientError - } else { - clientError = nil - } - - if attempt >= maxRetries || clientError != nil { - if let clientError { - throw clientError - } else { - throw ApiError.networkError(error) - } - } - let delay = Self.initialRetryDelay * pow(2.0, Swift.Double(attempt)) - let cappedDelay = min(delay, Self.maxRetryDelay) - let jitteredDelay = addSymmetricJitter(to: cappedDelay) - try await _Concurrency.Task.sleep( - nanoseconds: Swift.UInt64(jitteredDelay * 1_000_000_000)) - } - } - - if let (data, httpResponse) = lastResponse { - throw makeErrorFromResponse(statusCode: httpResponse.statusCode, data: data) - } - throw ApiError.invalidResponse - } - - private func shouldRetry(statusCode: Swift.Int) -> Swift.Bool { - return statusCode == 408 || statusCode == 429 || statusCode >= 500 - } - - private func getRetryDelay(response: Networking.HTTPURLResponse, retryAttempt: Swift.Int) - -> Foundation.TimeInterval - { - if let retryAfter = response.value(forHTTPHeaderField: "Retry-After") { - if let seconds = Swift.Double(retryAfter), seconds > 0 { - return min(seconds, Self.maxRetryDelay) - } - - if let date = parseHTTPDate(retryAfter) { - let delay = date.timeIntervalSinceNow - if delay > 0 { - return min(delay, Self.maxRetryDelay) - } - } - } - - if let rateLimitReset = response.value(forHTTPHeaderField: "X-RateLimit-Reset") { - if let resetTimeSeconds = Swift.Double(rateLimitReset) { - let resetDate = Foundation.Date(timeIntervalSince1970: resetTimeSeconds) - let delay = resetDate.timeIntervalSinceNow - if delay > 0 { - let cappedDelay = min(delay, Self.maxRetryDelay) - return addPositiveJitter(to: cappedDelay) - } - } - } - - let baseDelay = Self.initialRetryDelay * pow(2.0, Swift.Double(retryAttempt)) - let cappedDelay = min(baseDelay, Self.maxRetryDelay) - return addSymmetricJitter(to: cappedDelay) - } - - private func parseHTTPDate(_ dateString: Swift.String) -> Foundation.Date? { - let formatter = Foundation.DateFormatter() - formatter.locale = Foundation.Locale(identifier: "en_US_POSIX") - formatter.timeZone = Foundation.TimeZone(abbreviation: "GMT") - formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" - return formatter.date(from: dateString) - } - - private func addPositiveJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { - let jitterMultiplier = 1.0 + Swift.Double.random(in: 0...Self.jitterFactor) - return delay * jitterMultiplier - } - - private func addSymmetricJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { - let jitterMultiplier = - 1.0 + Swift.Double.random(in: -Self.jitterFactor / 2...Self.jitterFactor / 2) - return delay * jitterMultiplier - } - - private func makeErrorFromResponse(statusCode: Swift.Int, data: Foundation.Data) -> ApiError - { - let httpError = HTTPError.from( - statusCode: statusCode, - data: data, - jsonDecoder: jsonDecoder - ) - return ApiError.httpError(httpError) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift deleted file mode 100644 index 06d0a4474d13..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormData.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation - -/// Helper class for building multipart form data requests -class MultipartFormData { - let boundary: Swift.String - private var bodyData: Foundation.Data - - init() { - self.boundary = "Boundary-\(Foundation.UUID().uuidString)" - self.bodyData = Foundation.Data() - } - - /// Append a file field to the form data - func appendFile( - _ data: Foundation.Data, withName name: Swift.String, fileName: Swift.String? = nil - ) { - bodyData.appendUTF8String("--\(boundary)\r\n") - var contentDisposition = "Content-Disposition: form-data; name=\"\(name)\"" - if let fileName { - contentDisposition += "; filename=\"\(fileName)\"" - } - contentDisposition += "\r\n" - bodyData.appendUTF8String(contentDisposition) - bodyData.appendUTF8String( - "Content-Type: \(HTTP.ContentType.applicationOctetStream.rawValue)\r\n\r\n") - bodyData.append(data) - bodyData.appendUTF8String("\r\n") - } - - /// Append a text field to the form data - func appendField(_ value: Swift.String, withName name: Swift.String) { - bodyData.appendUTF8String("--\(boundary)\r\n") - bodyData.appendUTF8String( - "Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") - bodyData.appendUTF8String(value) - bodyData.appendUTF8String("\r\n") - } - - /// Returns the complete multipart form data with closing boundary - func data() -> Foundation.Data { - var finalData = bodyData - finalData.appendUTF8String("--\(boundary)--\r\n") - return finalData - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift deleted file mode 100644 index 6184b103cf1a..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormDataConvertible.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation - -/// Protocol for types that can be converted to multipart form data -protocol MultipartFormDataConvertible { - /// The multipart fields that represent this request - var multipartFormFields: [MultipartFormField] { get } -} - -extension MultipartFormDataConvertible { - /// Converts this request to multipart form data - func asMultipartFormData() -> MultipartFormData { - let multipartData = MultipartFormData() - let jsonEncoder = Serde.jsonEncoder - - for field in multipartFormFields { - switch field { - case .file(let file, let fieldName): - multipartData.appendFile(file.data, withName: fieldName, fileName: file.filename) - case .fileArray(let files, let fieldName): - for file in files { - multipartData.appendFile( - file.data, - withName: fieldName, - fileName: file.filename - ) - } - case .field(let encodableValue, let fieldName): - do { - let encodedData = try jsonEncoder.encode(value: encodableValue) - if let encodedString = Swift.String(data: encodedData, encoding: .utf8) { - multipartData.appendField(encodedString, withName: fieldName) - } - } catch { - // Fallback - this should rarely happen with well-formed Encodable types - multipartData.appendField("", withName: fieldName) - } - } - } - - return multipartData - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift deleted file mode 100644 index 3b57f57d8904..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Networking/MultipartFormField.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -/// Represents a field in multipart form data -enum MultipartFormField { - /// A single file field - case file(_ file: FormFile, fieldName: Swift.String) - /// An array of files with the same field name - case fileArray(_ files: [FormFile], fieldName: Swift.String) - /// A text field with JSON-encoded value (for strings, numbers, booleans, dates, etc.) - case field(_ value: EncodableValue, fieldName: Swift.String) - - /// Create a text field from any Encodable value - static func field(_ value: T, fieldName: Swift.String) -> MultipartFormField - { - return .field(.init(value), fieldName: fieldName) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift b/seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift deleted file mode 100644 index 504f439749b5..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Networking/QueryParameter.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation - -enum QueryParameter { - case string(Swift.String) - case bool(Swift.Bool) - case int(Swift.Int) - case uint(Swift.UInt) - case uint64(Swift.UInt64) - case int64(Swift.Int64) - case float(Swift.Float) - case double(Swift.Double) - case date(Foundation.Date) - case calendarDate(CalendarDate) - case stringArray([Swift.String]) - case uuid(Foundation.UUID) - case unknown(Any) - - func toString() -> Swift.String { - switch self { - case .string(let value): - return value - case .bool(let value): - return value ? "true" : "false" - case .int(let value): - return Swift.String(value) - case .uint(let value): - return Swift.String(value) - case .uint64(let value): - return Swift.String(value) - case .int64(let value): - return Swift.String(value) - case .float(let value): - return Swift.String(value) - case .double(let value): - return Swift.String(value) - case .date(let value): - return value.ISO8601Format() - case .calendarDate(let value): - return value.description - case .stringArray(let values): - return values.joined(separator: ",") - case .uuid(let value): - return value.uuidString - case .unknown: - return "" - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift deleted file mode 100644 index 4148f56b643d..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Decoder+AdditionalProperties.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -extension Swift.Decoder { - func decodeAdditionalProperties< - T: Swift.Decodable, C: Swift.CaseIterable & Swift.RawRepresentable - >( - using codingKeysType: C.Type - ) throws - -> [Swift.String: T] where C.RawValue == Swift.String - { - return try decodeAdditionalProperties( - knownKeys: Swift.Set(codingKeysType.allCases.map(\.rawValue))) - } - - func decodeAdditionalProperties(knownKeys: Swift.Set) throws - -> [Swift.String: T] - { - let container = try container(keyedBy: StringKey.self) - let unknownKeys = Swift.Set(container.allKeys).subtracting( - knownKeys.map(StringKey.init(_:))) - guard !unknownKeys.isEmpty else { return .init() } - let keyValuePairs: [(Swift.String, T)] = try unknownKeys.compactMap { key in - (key.stringValue, try container.decode(T.self, forKey: key)) - } - return .init(uniqueKeysWithValues: keyValuePairs) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift deleted file mode 100644 index 69a075f244f5..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/EncodableValue.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -/// Type-erased wrapper for encodable values -struct EncodableValue { - let value: any Swift.Encodable - - init(_ value: T) { - self.value = value - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift deleted file mode 100644 index e1e7c48f9fc0..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Encoder+AdditionalProperties.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Swift.Encoder { - func encodeAdditionalProperties(_ additionalProperties: [Swift.String: T]) - throws - { - guard !additionalProperties.isEmpty else { return } - var container = self.container(keyedBy: StringKey.self) - for (key, value) in additionalProperties { - try container.encode(value, forKey: .init(key)) - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift deleted file mode 100644 index 3f0ea998c148..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/JSONEncoder+EncodableValue.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -extension Foundation.JSONEncoder { - /// Helper for type-erasing Encodable values - private struct AnyEncodable: Swift.Encodable { - private let value: any Swift.Encodable - - init(_ value: any Swift.Encodable) { - self.value = value - } - - func encode(to encoder: Swift.Encoder) throws { - try value.encode(to: encoder) - } - } - - func encode(value: EncodableValue) throws -> Foundation.Data { - return try self.encode(AnyEncodable(value.value)) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift deleted file mode 100644 index a5dfedba2c4b..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -extension Swift.KeyedDecodingContainer { - /// Decodes a Nullable? value, properly handling missing vs null vs value - /// Use this for `Optional>` fields - public func decodeNullableIfPresent( - _ type: T.Type, forKey key: Swift.KeyedDecodingContainer.Key - ) throws -> Nullable? where T: Swift.Decodable { - if contains(key) { - if try decodeNil(forKey: key) { - return .null - } else { - let value = try decode(type, forKey: key) - return .value(value) - } - } else { - return nil - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift deleted file mode 100644 index 89ac59ace39b..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -extension Swift.KeyedEncodingContainer { - /// Encodes a Nullable? value, properly handling missing vs null vs value - /// Use this for `Optional>` fields - public mutating func encodeNullableIfPresent( - _ value: Nullable?, forKey key: Swift.KeyedEncodingContainer.Key - ) throws where T: Swift.Encodable { - switch value { - case nil: - // Don't encode the key at all - field is missing - break - case .some(.null): - try encodeNil(forKey: key) - case .some(.value(let wrapped)): - try encode(wrapped, forKey: key) - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift deleted file mode 100644 index b64e6cb3d410..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/Serde.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation - -final class Serde { - static var jsonEncoder: Foundation.JSONEncoder { - let encoder = Foundation.JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - return encoder - } - - static var jsonDecoder: Foundation.JSONDecoder { - let decoder = Foundation.JSONDecoder() - // Use custom strategy for robust ISO 8601 date parsing with fractional seconds - decoder.dateDecodingStrategy = .custom { decoder in - let container = try decoder.singleValueContainer() - let dateString = try container.decode(String.self) - - let formatter = Foundation.ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - - if let date = formatter.date(from: dateString) { - return date - } - - // Fallback for dates without fractional seconds - formatter.formatOptions = [.withInternetDateTime] - if let date = formatter.date(from: dateString) { - return date - } - - throw Swift.DecodingError.dataCorruptedError( - in: container, debugDescription: "Invalid date format: \(dateString)") - } - return decoder - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift b/seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift deleted file mode 100644 index a4f0c42b3c30..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/Serde/StringKey.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -struct StringKey: Swift.CodingKey, Swift.Hashable { - var stringValue: Swift.String - var intValue: Swift.Int? { Swift.Int(stringValue) } - - init(_ string: Swift.String) { - self.stringValue = string - } - - init?(stringValue: Swift.String) { - self.stringValue = stringValue - } - - init?(intValue: Swift.Int) { - self.stringValue = Swift.String(intValue) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift b/seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift deleted file mode 100644 index f852dbd94cb1..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Core/String+URLEncoding.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -extension Swift.String { - func urlPathEncoded() -> Swift.String { - return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self - } - - func urlQueryEncoded() -> Swift.String { - return self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift b/seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift deleted file mode 100644 index 3159252bcd09..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/CalendarDate.swift +++ /dev/null @@ -1,105 +0,0 @@ -import Foundation - -/// Represents a calendar date without time information, following RFC 3339 section 5.6 (`YYYY-MM-DD` format) -public struct CalendarDate: Swift.Codable, Swift.Hashable, Swift.Sendable, Swift - .CustomStringConvertible, Swift.Comparable -{ - /// The year component (expected range: 1-9999) - public let year: Swift.Int - - /// The month component (valid range: 1-12) - public let month: Swift.Int - - /// The day component (valid range: 1-31, depending on month) - public let day: Swift.Int - - /// Failable initializer for creating a CalendarDate with validation - public init?(year: Swift.Int, month: Swift.Int, day: Swift.Int) { - guard Self.isValidDate(year: year, month: month, day: day) else { - return nil - } - self.year = year - self.month = month - self.day = day - } - - /// Failable initializer for creating a CalendarDate from a `YYYY-MM-DD` string - public init?(_ dateString: Swift.String) { - let components = dateString.split(separator: "-") - guard components.count == 3, - let year = Swift.Int(components[0]), - let month = Swift.Int(components[1]), - let day = Swift.Int(components[2]) - else { - return nil - } - self.init(year: year, month: month, day: day) - } - - // MARK: - Codable - - public init(from decoder: Swift.Decoder) throws { - let container = try decoder.singleValueContainer() - let dateString = try container.decode(String.self) - guard let calendarDate = CalendarDate(dateString) else { - throw Error.invalidFormat(dateString) - } - self = calendarDate - } - - public func encode(to encoder: Swift.Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(description) - } - - // MARK: - CustomStringConvertible - - public var description: Swift.String { - // Format as YYYY-MM-DD with zero-padding - // %04d = 4-digit year with leading zeros (e.g., 2025) - // %02d = 2-digit month/day with leading zeros (e.g., 01, 05) - Swift.String(format: "%04d-%02d-%02d", year, month, day) - } - - // MARK: - Comparable - - public static func < (lhs: CalendarDate, rhs: CalendarDate) -> Swift.Bool { - if lhs.year != rhs.year { return lhs.year < rhs.year } - if lhs.month != rhs.month { return lhs.month < rhs.month } - return lhs.day < rhs.day - } - - // MARK: - Private Helpers - - /// Validates that the given year, month, and day form a valid calendar date using Foundation's Calendar APIs. - private static func isValidDate(year: Swift.Int, month: Swift.Int, day: Swift.Int) -> Swift.Bool - { - let calendar = Foundation.Calendar(identifier: .gregorian) - let components = Foundation.DateComponents(year: year, month: month, day: day) - - guard let date = calendar.date(from: components) else { - return false - } - - // Ensure the date components match what we created (handles invalid dates like Feb 30) - let reconstructedComponents = calendar.dateComponents([.year, .month, .day], from: date) - return - (reconstructedComponents.year == year - && reconstructedComponents.month == month - && reconstructedComponents.day == day) - } - - // MARK: - Error Types - - /// Errors that can occur when working with CalendarDate - public enum Error: Swift.Error, Foundation.LocalizedError { - case invalidFormat(Swift.String) - - public var errorDescription: Swift.String? { - switch self { - case .invalidFormat(let string): - return "Invalid date format: '\(string)'. Expected YYYY-MM-DD" - } - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift b/seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift deleted file mode 100644 index 01ab9ae13b67..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/ClientConfig.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Foundation - -public final class ClientConfig: Swift.Sendable { - public typealias CredentialProvider = @Sendable () async throws -> Swift.String - - struct Defaults { - static let timeout: Swift.Int = 60 - static let maxRetries: Swift.Int = 2 - } - - struct HeaderAuth { - let key: Swift.String - let header: Swift.String - } - - struct BearerAuth { - let token: Token - - enum Token: Swift.Sendable { - case staticToken(Swift.String) - case provider(CredentialProvider) - - func retrieve() async throws -> Swift.String { - switch self { - case .staticToken(let token): - return token - case .provider(let provider): - return try await provider() - } - } - } - } - - struct BasicAuth { - let username: Swift.String? - let password: Swift.String? - - var token: Swift.String? { - if let username, let password { - let credentials: Swift.String = "\(username):\(password)" - let data = credentials.data(using: .utf8) ?? Foundation.Data() - return data.base64EncodedString() - } - return nil - } - } - - let baseURL: Swift.String - let headerAuth: HeaderAuth? - let bearerAuth: BearerAuth? - let basicAuth: BasicAuth? - let headers: [Swift.String: Swift.String]? - let timeout: Swift.Int - let maxRetries: Swift.Int - let urlSession: Networking.URLSession - - init( - baseURL: Swift.String, - headerAuth: HeaderAuth? = nil, - bearerAuth: BearerAuth? = nil, - basicAuth: BasicAuth? = nil, - headers: [Swift.String: Swift.String]? = nil, - timeout: Swift.Int? = nil, - maxRetries: Swift.Int? = nil, - urlSession: Networking.URLSession? = nil - ) { - self.baseURL = baseURL - self.headerAuth = headerAuth - self.bearerAuth = bearerAuth - self.basicAuth = basicAuth - self.headers = headers - self.timeout = timeout ?? Defaults.timeout - self.maxRetries = maxRetries ?? Defaults.maxRetries - self.urlSession = urlSession ?? buildURLSession(timeoutSeconds: self.timeout) - } -} - -private func buildURLSession(timeoutSeconds: Swift.Int) -> Networking.URLSession { - let configuration = Networking.URLSessionConfiguration.default - configuration.timeoutIntervalForRequest = .init(timeoutSeconds) - return .init(configuration: configuration) -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift b/seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift deleted file mode 100644 index 4960a15bc000..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/FormFile.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -/// Represents a file with its data and optional metadata for multipart uploads -public struct FormFile { - /// The file data - public let data: Foundation.Data - /// Optional filename - public let filename: Swift.String? - - public init(data: Foundation.Data, filename: Swift.String? = nil) { - self.data = data - self.filename = filename - } -} - -// MARK: - Convenience Initializers - -extension FormFile { - /// Create a FormFile from raw Data with no metadata - /// - Parameter data: The file data - public static func data(_ data: Foundation.Data) -> FormFile { - return FormFile(data: data) - } - - /// Create a FormFile from Data with a filename - /// - Parameters: - /// - data: The file data - /// - filename: The filename - public static func named(_ data: Foundation.Data, filename: Swift.String) -> FormFile { - return FormFile(data: data, filename: filename) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift b/seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift deleted file mode 100644 index 69a1e2962c60..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/HTTPError.swift +++ /dev/null @@ -1,190 +0,0 @@ -import Foundation - -/// Represents an HTTP error response from the server. -/// -/// This type provides a structured view of non-success HTTP responses, including -/// the status code, an optional parsed error payload and a semantic classification. -public struct HTTPError: Swift.Error, Swift.CustomStringConvertible { - /// The HTTP status code returned by the server. - public let statusCode: Swift.Int - - /// Parsed error payload returned by the server, if any. - public let body: ResponseBody? - - /// Semantic classification of the error based on the status code. - public let kind: Kind - - public init( - statusCode: Swift.Int, - body: ResponseBody?, - kind: Kind - ) { - self.statusCode = statusCode - self.body = body - self.kind = kind - } - - // MARK: - Description - - public var description: Swift.String { - localizedDescription - } - - public var localizedDescription: Swift.String { - let defaultPrefix: Swift.String - switch kind { - case .redirect: - defaultPrefix = "Redirect error" - case .unauthorized: - defaultPrefix = "Unauthorized" - case .forbidden: - defaultPrefix = "Forbidden" - case .notFound: - defaultPrefix = "Not found" - case .client: - defaultPrefix = "Client error" - case .validation: - defaultPrefix = "Validation error" - case .serviceUnavailable: - defaultPrefix = "Service unavailable" - case .server: - defaultPrefix = "Server error" - case .other: - defaultPrefix = "HTTP error" - } - - guard let body = body else { - return "\(defaultPrefix) (Code: \(statusCode))" - } - - var message = defaultPrefix - - // Use server message if available and meaningful. - if let serverMessage = body.message, !serverMessage.isEmpty { - message = serverMessage - } - - var details = "Code: \(body.code)" - if let type = body.type, !type.isEmpty { - details += ", Type: \(type)" - } - - return "\(message) (\(details))" - } - - // MARK: - Factories - - /// Builds an ``HTTPError`` from an HTTP status code and raw response data. - /// - /// The mapping from status code to ``Kind`` is: - /// - `300...399` → `.redirect` - /// - `401` → `.unauthorized` - /// - `403` → `.forbidden` - /// - `404` → `.notFound` - /// - `422` → `.validation` - /// - `400...499` → `.client` - /// - `503` → `.serviceUnavailable` - /// - `500...599` → `.server` - /// - anything else → `.other` - public static func from( - statusCode: Swift.Int, - data: Foundation.Data, - jsonDecoder: Foundation.JSONDecoder - ) -> HTTPError { - let parsedBody = ResponseBody.decode( - statusCode: statusCode, - data: data, - using: jsonDecoder - ) - - let kind: Kind - switch statusCode { - case 300..<400: - kind = .redirect - case 401: - kind = .unauthorized - case 403: - kind = .forbidden - case 404: - kind = .notFound - case 422: - kind = .validation - case 503: - kind = .serviceUnavailable - case 400..<500: - kind = .client - case 500..<600: - kind = .server - default: - kind = .other - } - - return HTTPError(statusCode: statusCode, body: parsedBody, kind: kind) - } - - public enum Kind { - /// 3xx responses. - case redirect - /// 401 responses. - case unauthorized - /// 403 responses. - case forbidden - /// 404 responses. - case notFound - /// Other 4xx responses. - case client - /// 422 responses. - case validation - /// 503 responses. - case serviceUnavailable - /// Other 5xx responses. - case server - /// Any other non-success status code. - case other - } - - /// A best-effort representation of an error payload returned by an API. - /// - /// This type is intentionally minimal and is populated from a variety of possible - /// response body formats (typed JSON, loose JSON with a `"message"` field, or plain text). - public struct ResponseBody: Swift.Codable, Swift.Sendable { - public let code: Swift.Int - public let type: Swift.String? - public let message: Swift.String? - - /// Attempts to decode an `HTTPError.ResponseBody` from the given HTTP response payload. - public static func decode( - statusCode: Swift.Int, - data: Foundation.Data, - using jsonDecoder: Foundation.JSONDecoder - ) -> ResponseBody? { - // Try to parse as a strongly-typed error response first - if let errorResponse = try? jsonDecoder.decode(ResponseBody.self, from: data) { - return errorResponse - } - - // Try to parse as a simple JSON object with a "message" field - if let json = try? Foundation.JSONSerialization.jsonObject(with: data) - as? [Swift.String: Any], - let message = json["message"] as? Swift.String - { - return ResponseBody(code: statusCode, message: message) - } - - // Try to parse as plain text - if let errorMessage: String = Swift.String(data: data, encoding: .utf8), - !errorMessage.isEmpty - { - return ResponseBody(code: statusCode, message: errorMessage) - } - - return nil - } - - public init(code: Swift.Int, type: Swift.String? = nil, message: Swift.String? = nil) { - self.code = code - self.type = type - self.message = message - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift b/seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift deleted file mode 100644 index ec6aa924fc8c..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/Indirect.swift +++ /dev/null @@ -1,27 +0,0 @@ -public final class Indirect: Codable, @unchecked Sendable { - public let value: T - - public init(_ value: T) { - self.value = value - } - - public convenience init(from decoder: Decoder) throws { - self.init(try T(from: decoder)) - } - - public func encode(to encoder: Encoder) throws { - try value.encode(to: encoder) - } -} - -extension Indirect: Equatable where T: Equatable { - public static func == (lhs: Indirect, rhs: Indirect) -> Bool { - lhs.value == rhs.value - } -} - -extension Indirect: Hashable where T: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(value) - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift b/seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift deleted file mode 100644 index f8c56db4debf..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/JSONValue.swift +++ /dev/null @@ -1,136 +0,0 @@ -import Foundation - -/// A type that can represent any JSON value. -public enum JSONValue: Swift.Codable, Swift.Hashable, Swift.Sendable { - case string(Swift.String) - case number(Swift.Double) - case bool(Swift.Bool) - case null - case array([JSONValue]) - case object([Swift.String: JSONValue]) - - public init(from decoder: Swift.Decoder) throws { - let container = try decoder.singleValueContainer() - - if container.decodeNil() { - self = .null - } else if let bool = try? container.decode(Swift.Bool.self) { - self = .bool(bool) - } else if let int = try? container.decode(Swift.Int.self) { - self = .number(Swift.Double(int)) - } else if let double = try? container.decode(Swift.Double.self) { - self = .number(double) - } else if let string = try? container.decode(Swift.String.self) { - self = .string(string) - } else if let array = try? container.decode([JSONValue].self) { - self = .array(array) - } else if let object = try? container.decode([Swift.String: JSONValue].self) { - self = .object(object) - } else { - throw Swift.DecodingError.dataCorrupted( - Swift.DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unable to decode JSONValue" - ) - ) - } - } - - public func encode(to encoder: Swift.Encoder) throws { - var container = encoder.singleValueContainer() - - switch self { - case .string(let string): - try container.encode(string) - case .number(let number): - try container.encode(number) - case .bool(let bool): - try container.encode(bool) - case .null: - try container.encodeNil() - case .array(let array): - try container.encode(array) - case .object(let object): - try container.encode(object) - } - } -} - -// MARK: - Convenience initializers -extension JSONValue { - public init(_ value: String) { - self = .string(value) - } - - public init(_ value: Int) { - self = .number(Double(value)) - } - - public init(_ value: Double) { - self = .number(value) - } - - public init(_ value: Bool) { - self = .bool(value) - } - - public init(_ value: [JSONValue]) { - self = .array(value) - } - - public init(_ value: [String: JSONValue]) { - self = .object(value) - } -} - -// MARK: - Value extraction -extension JSONValue { - public var stringValue: String? { - if case .string(let value) = self { - return value - } - return nil - } - - public var numberValue: Double? { - if case .number(let value) = self { - return value - } - return nil - } - - public var intValue: Int? { - if case .number(let value) = self { - return Int(value) - } - return nil - } - - public var boolValue: Bool? { - if case .bool(let value) = self { - return value - } - return nil - } - - public var arrayValue: [JSONValue]? { - if case .array(let value) = self { - return value - } - return nil - } - - public var objectValue: [String: JSONValue]? { - if case .object(let value) = self { - return value - } - return nil - } - - public var isNull: Bool { - if case .null = self { - return true - } - return false - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/Networking.swift b/seed/swift-sdk/allof-inline/Sources/Public/Networking.swift deleted file mode 100644 index ad54acdcf5fb..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/Networking.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -public enum Networking { - #if canImport(FoundationNetworking) - public typealias URLSession = FoundationNetworking.URLSession - public typealias URLSessionConfiguration = FoundationNetworking.URLSessionConfiguration - public typealias URLProtocol = FoundationNetworking.URLProtocol - public typealias URLRequest = FoundationNetworking.URLRequest - public typealias HTTPURLResponse = FoundationNetworking.HTTPURLResponse - #else - public typealias URLSession = Foundation.URLSession - public typealias URLSessionConfiguration = Foundation.URLSessionConfiguration - public typealias URLProtocol = Foundation.URLProtocol - public typealias URLRequest = Foundation.URLRequest - public typealias HTTPURLResponse = Foundation.HTTPURLResponse - #endif -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift b/seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift deleted file mode 100644 index 81c92d7b82c6..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/Nullable.swift +++ /dev/null @@ -1,59 +0,0 @@ -import Foundation - -/// Represents a value that can be either a concrete value or explicit `null`, distinguishing between null and missing fields in JSON. -public enum Nullable: Swift.Codable, Swift.Hashable, Swift.Sendable -where Wrapped: Swift.Codable & Swift.Hashable & Swift.Sendable { - case value(Wrapped) - case null - - public init(from decoder: Swift.Decoder) throws { - let container = try decoder.singleValueContainer() - - if container.decodeNil() { - self = .null - } else { - let wrappedValue = try container.decode(Wrapped.self) - self = .value(wrappedValue) - } - } - - public func encode(to encoder: Swift.Encoder) throws { - var container = encoder.singleValueContainer() - - switch self { - case .value(let wrapped): - try container.encode(wrapped) - case .null: - try container.encodeNil() - } - } - - /// Returns the wrapped value if present, otherwise nil - public var wrappedValue: Wrapped? { - switch self { - case .value(let wrapped): - return wrapped - case .null: - return nil - } - } - - /// Returns true if this contains an explicit null value - public var isNull: Swift.Bool { - switch self { - case .value(_): - return false - case .null: - return true - } - } - - /// Convenience initializer from optional value - public init(_ value: Wrapped?) { - if let value = value { - self = .value(value) - } else { - self = .null - } - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift b/seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift deleted file mode 100644 index 3f210d8cf887..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Public/RequestOptions.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation - -/// Options for customizing an individual API request. -/// -/// Use this struct to override or supplement client-wide configuration for a single request. -public struct RequestOptions { - /// The API key to use for this request, overriding the client-wide API key if provided. - let apiKey: Swift.String? - - /// The token to use for this request, overriding the client-wide token if provided. - let token: Swift.String? - - /// The number of seconds to await an API call before timing out. If `nil`, uses the client or system default. - let timeout: Swift.Int? - - /// The number of times to retry a failed API call. If `nil`, uses the client or system default. - let maxRetries: Swift.Int? - - /// Additional HTTP headers to include with this request. These can override or supplement client-wide headers. - let additionalHeaders: [Swift.String: Swift.String]? - - /// Additional query parameters to include in the request URL. These are merged with any parameters generated from the request model. - let additionalQueryParameters: [Swift.String: Swift.String]? - - /// Additional body parameters to include in the request payload. These are merged with any parameters generated from the request model. - let additionalBodyParameters: [Swift.String: Swift.String]? - - public init( - apiKey: Swift.String? = nil, - token: Swift.String? = nil, - timeout: Swift.Int? = nil, - maxRetries: Swift.Int? = nil, - additionalHeaders: [Swift.String: Swift.String]? = nil, - additionalQueryParameters: [Swift.String: Swift.String]? = nil, - additionalBodyParameters: [Swift.String: Swift.String]? = nil - ) { - self.apiKey = apiKey - self.token = token - self.timeout = timeout - self.maxRetries = maxRetries - self.additionalHeaders = additionalHeaders - self.additionalQueryParameters = additionalQueryParameters - self.additionalBodyParameters = additionalBodyParameters - } -} diff --git a/seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift b/seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift deleted file mode 100644 index 652324b903b1..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Requests/Requests+RuleCreateRequest.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -extension Requests { - public struct RuleCreateRequest: Codable, Hashable, Sendable { - public let name: String - public let executionContext: RuleExecutionContext - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - name: String, - executionContext: RuleExecutionContext, - additionalProperties: [String: JSONValue] = .init() - ) { - self.name = name - self.executionContext = executionContext - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.executionContext = try container.decode(RuleExecutionContext.self, forKey: .executionContext) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.name, forKey: .name) - try container.encode(self.executionContext, forKey: .executionContext) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case name - case executionContext - } - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift b/seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift deleted file mode 100644 index 2b06437b8213..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Requests/Requests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -/// Container for all inline request types used throughout the SDK. -/// -/// This enum serves as a namespace to organize request types that are defined inline within endpoint specifications. -public enum Requests {} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift deleted file mode 100644 index e2d2dca164b6..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/AuditInfo.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation - -/// Common audit metadata. -public struct AuditInfo: Codable, Hashable, Sendable { - /// The user who created this resource. - public let createdBy: String? - /// When this resource was created. - public let createdDateTime: Date? - /// The user who last modified this resource. - public let modifiedBy: String? - /// When this resource was last modified. - public let modifiedDateTime: Date? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - createdBy: String? = nil, - createdDateTime: Date? = nil, - modifiedBy: String? = nil, - modifiedDateTime: Date? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.createdBy = createdBy - self.createdDateTime = createdDateTime - self.modifiedBy = modifiedBy - self.modifiedDateTime = modifiedDateTime - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) - self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) - self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) - self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.createdBy, forKey: .createdBy) - try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) - try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) - try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case createdBy - case createdDateTime - case modifiedBy - case modifiedDateTime - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift deleted file mode 100644 index 826006803d13..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrg.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -public struct BaseOrg: Codable, Hashable, Sendable { - public let id: String - public let metadata: BaseOrgMetadata? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - metadata: BaseOrgMetadata? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.metadata = metadata - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.metadata = try container.decodeIfPresent(BaseOrgMetadata.self, forKey: .metadata) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.metadata, forKey: .metadata) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case metadata - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift deleted file mode 100644 index 8b76b582c6ff..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/BaseOrgMetadata.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct BaseOrgMetadata: Codable, Hashable, Sendable { - /// Deployment region from BaseOrg. - public let region: String - /// Subscription tier. - public let tier: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - region: String, - tier: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.region = region - self.tier = tier - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.region = try container.decode(String.self, forKey: .region) - self.tier = try container.decodeIfPresent(String.self, forKey: .tier) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.region, forKey: .region) - try container.encodeIfPresent(self.tier, forKey: .tier) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case region - case tier - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift deleted file mode 100644 index 23e7e2d4b104..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntity.swift +++ /dev/null @@ -1,53 +0,0 @@ -import Foundation - -public struct CombinedEntity: Codable, Hashable, Sendable { - /// Unique identifier. - public let id: String - /// Display name from Describable. - public let name: String? - /// A short summary. - public let summary: String? - public let status: CombinedEntityStatus - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - name: String? = nil, - summary: String? = nil, - status: CombinedEntityStatus, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.name = name - self.summary = summary - self.status = status - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decodeIfPresent(String.self, forKey: .name) - self.summary = try container.decodeIfPresent(String.self, forKey: .summary) - self.status = try container.decode(CombinedEntityStatus.self, forKey: .status) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.name, forKey: .name) - try container.encodeIfPresent(self.summary, forKey: .summary) - try container.encode(self.status, forKey: .status) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case name - case summary - case status - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift deleted file mode 100644 index ad507eb4b537..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/CombinedEntityStatus.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -public enum CombinedEntityStatus: String, Codable, Hashable, CaseIterable, Sendable { - case active - case archived -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift deleted file mode 100644 index 5f59f3f5a66b..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/Describable.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct Describable: Codable, Hashable, Sendable { - /// Display name from Describable. - public let name: String? - /// A short summary. - public let summary: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - name: String? = nil, - summary: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.name = name - self.summary = summary - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) - self.summary = try container.decodeIfPresent(String.self, forKey: .summary) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.name, forKey: .name) - try container.encodeIfPresent(self.summary, forKey: .summary) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case name - case summary - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift deleted file mode 100644 index 412a6626a9c5..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrg.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -public struct DetailedOrg: Codable, Hashable, Sendable { - public let metadata: DetailedOrgMetadata? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - metadata: DetailedOrgMetadata? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.metadata = metadata - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.metadata = try container.decodeIfPresent(DetailedOrgMetadata.self, forKey: .metadata) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.metadata, forKey: .metadata) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case metadata - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift deleted file mode 100644 index bf4903b3d0ea..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/DetailedOrgMetadata.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct DetailedOrgMetadata: Codable, Hashable, Sendable { - /// Deployment region from DetailedOrg. - public let region: String - /// Custom domain name. - public let domain: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - region: String, - domain: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.region = region - self.domain = domain - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.region = try container.decode(String.self, forKey: .region) - self.domain = try container.decodeIfPresent(String.self, forKey: .domain) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.region, forKey: .region) - try container.encodeIfPresent(self.domain, forKey: .domain) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case region - case domain - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift deleted file mode 100644 index c0c54d0a6b65..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/Identifiable.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct Identifiable: Codable, Hashable, Sendable { - /// Unique identifier. - public let id: String - /// Display name from Identifiable. - public let name: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - name: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.name = name - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decodeIfPresent(String.self, forKey: .name) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.name, forKey: .name) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case name - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift deleted file mode 100644 index 4b1c75c5b4fa..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/Organization.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation - -public struct Organization: Codable, Hashable, Sendable { - public let id: String - public let metadata: OrganizationMetadata? - public let name: String - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - metadata: OrganizationMetadata? = nil, - name: String, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.metadata = metadata - self.name = name - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.metadata = try container.decodeIfPresent(OrganizationMetadata.self, forKey: .metadata) - self.name = try container.decode(String.self, forKey: .name) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.metadata, forKey: .metadata) - try container.encode(self.name, forKey: .name) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case metadata - case name - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift deleted file mode 100644 index 0799ed018c50..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/OrganizationMetadata.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct OrganizationMetadata: Codable, Hashable, Sendable { - /// Deployment region from DetailedOrg. - public let region: String - /// Custom domain name. - public let domain: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - region: String, - domain: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.region = region - self.domain = domain - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.region = try container.decode(String.self, forKey: .region) - self.domain = try container.decodeIfPresent(String.self, forKey: .domain) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.region, forKey: .region) - try container.encodeIfPresent(self.domain, forKey: .domain) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case region - case domain - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift deleted file mode 100644 index 60920a570d88..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/PaginatedResult.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation - -public struct PaginatedResult: Codable, Hashable, Sendable { - public let paging: PagingCursors - /// Current page of results from the requested resource. - public let results: [JSONValue] - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - paging: PagingCursors, - results: [JSONValue], - additionalProperties: [String: JSONValue] = .init() - ) { - self.paging = paging - self.results = results - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.paging = try container.decode(PagingCursors.self, forKey: .paging) - self.results = try container.decode([JSONValue].self, forKey: .results) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.paging, forKey: .paging) - try container.encode(self.results, forKey: .results) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case paging - case results - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift deleted file mode 100644 index a6c6369f7a4e..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/PagingCursors.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct PagingCursors: Codable, Hashable, Sendable { - /// Cursor for the next page of results. - public let next: String - /// Cursor for the previous page of results. - public let previous: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - next: String, - previous: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.next = next - self.previous = previous - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.next = try container.decode(String.self, forKey: .next) - self.previous = try container.decodeIfPresent(String.self, forKey: .previous) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.next, forKey: .next) - try container.encodeIfPresent(self.previous, forKey: .previous) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case next - case previous - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift deleted file mode 100644 index b3825cc062d2..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleExecutionContext.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -/// Execution environment for a rule. -public enum RuleExecutionContext: String, Codable, Hashable, CaseIterable, Sendable { - case prod - case staging - case dev -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift deleted file mode 100644 index 23834241d4e1..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponse.swift +++ /dev/null @@ -1,78 +0,0 @@ -import Foundation - -public struct RuleResponse: Codable, Hashable, Sendable { - /// The user who created this resource. - public let createdBy: String? - /// When this resource was created. - public let createdDateTime: Date? - /// The user who last modified this resource. - public let modifiedBy: String? - /// When this resource was last modified. - public let modifiedDateTime: Date? - public let id: String - public let name: String - public let status: RuleResponseStatus - public let executionContext: RuleExecutionContext? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - createdBy: String? = nil, - createdDateTime: Date? = nil, - modifiedBy: String? = nil, - modifiedDateTime: Date? = nil, - id: String, - name: String, - status: RuleResponseStatus, - executionContext: RuleExecutionContext? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.createdBy = createdBy - self.createdDateTime = createdDateTime - self.modifiedBy = modifiedBy - self.modifiedDateTime = modifiedDateTime - self.id = id - self.name = name - self.status = status - self.executionContext = executionContext - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) - self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) - self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) - self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decode(String.self, forKey: .name) - self.status = try container.decode(RuleResponseStatus.self, forKey: .status) - self.executionContext = try container.decodeIfPresent(RuleExecutionContext.self, forKey: .executionContext) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.createdBy, forKey: .createdBy) - try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) - try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) - try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) - try container.encode(self.id, forKey: .id) - try container.encode(self.name, forKey: .name) - try container.encode(self.status, forKey: .status) - try container.encodeIfPresent(self.executionContext, forKey: .executionContext) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case createdBy - case createdDateTime - case modifiedBy - case modifiedDateTime - case id - case name - case status - case executionContext - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift deleted file mode 100644 index 1f8cbe93a34c..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleResponseStatus.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -public enum RuleResponseStatus: String, Codable, Hashable, CaseIterable, Sendable { - case active - case inactive - case draft -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift deleted file mode 100644 index 89987c5def90..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleType.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation - -public struct RuleType: Codable, Hashable, Sendable { - public let id: String - public let name: String - public let description: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - name: String, - description: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.name = name - self.description = description - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decode(String.self, forKey: .name) - self.description = try container.decodeIfPresent(String.self, forKey: .description) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encode(self.name, forKey: .name) - try container.encodeIfPresent(self.description, forKey: .description) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case name - case description - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift deleted file mode 100644 index 4d5765dfec4d..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/RuleTypeSearchResponse.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation - -public struct RuleTypeSearchResponse: Codable, Hashable, Sendable { - public let paging: PagingCursors - /// Current page of results from the requested resource. - public let results: [RuleType]? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - paging: PagingCursors, - results: [RuleType]? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.paging = paging - self.results = results - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.paging = try container.decode(PagingCursors.self, forKey: .paging) - self.results = try container.decodeIfPresent([RuleType].self, forKey: .results) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.paging, forKey: .paging) - try container.encodeIfPresent(self.results, forKey: .results) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case paging - case results - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/User.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/User.swift deleted file mode 100644 index 3e9a5ec45166..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/User.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -public struct User: Codable, Hashable, Sendable { - public let id: String - public let email: String - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - email: String, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.email = email - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.email = try container.decode(String.self, forKey: .email) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encode(self.email, forKey: .email) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case email - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift b/seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift deleted file mode 100644 index 50a9d6d4067a..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Schemas/UserSearchResponse.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation - -public struct UserSearchResponse: Codable, Hashable, Sendable { - public let paging: PagingCursors - /// Current page of results from the requested resource. - public let results: [User]? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - paging: PagingCursors, - results: [User]? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.paging = paging - self.results = results - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.paging = try container.decode(PagingCursors.self, forKey: .paging) - self.results = try container.decodeIfPresent([User].self, forKey: .results) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.paging, forKey: .paging) - try container.encodeIfPresent(self.results, forKey: .results) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case paging - case results - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof-inline/Sources/Version.swift b/seed/swift-sdk/allof-inline/Sources/Version.swift deleted file mode 100644 index b17ac8c77973..000000000000 --- a/seed/swift-sdk/allof-inline/Sources/Version.swift +++ /dev/null @@ -1 +0,0 @@ -public let sdkVersion = "0.0.1" diff --git a/seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift b/seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift deleted file mode 100644 index 63de33596c7b..000000000000 --- a/seed/swift-sdk/allof-inline/Tests/Core/ClientErrorTests.swift +++ /dev/null @@ -1,222 +0,0 @@ -import Api -import Foundation -import Testing - -@Suite("Client Error & HTTP Error Tests") struct ClientErrorTests { - // MARK: - 4xx client errors - - @Test func testClientErrorFor400BadRequest() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 400, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Bad request"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 400) - try #require(httpError.kind == .client) - try #require(httpError.body?.message == "Bad request") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorFor404NotFound() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 404, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Not found"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 404) - try #require(httpError.kind == .notFound) - try #require(httpError.body?.message == "Not found") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorFor422ValidationError() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 422, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Validation failed"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 422) - try #require(httpError.kind == .validation) - try #require(httpError.body?.message == "Validation failed") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - // MARK: - 5xx server errors - - @Test func testClientErrorFor500ServerError() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 500, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Internal error"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 500) - try #require(httpError.kind == .server) - try #require(httpError.body?.message == "Internal error") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorFor503ServiceUnavailable() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 503, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Unavailable"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 503) - try #require(httpError.kind == .serviceUnavailable) - try #require(httpError.body?.message == "Unavailable") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - // MARK: - 3xx redirect & plain-text bodies - - @Test func testClientErrorFor302RedirectNoBody() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 302, - headers: ["Location": "https://example.com"], - body: Data() - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 302) - try #require(httpError.kind == .redirect) - try #require(httpError.body == nil) - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorPlainTextBodyIsDecoded() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 500, - headers: ["Content-Type": "text/plain"], - body: Data("Plain text error".utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 500) - try #require(httpError.kind == .server) - try #require(httpError.body?.message == "Plain text error") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } -} - diff --git a/seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift b/seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift deleted file mode 100644 index 8ebbf4c73e22..000000000000 --- a/seed/swift-sdk/allof-inline/Tests/Core/ClientRetryTests.swift +++ /dev/null @@ -1,355 +0,0 @@ -import Api -import Foundation -import Testing - -@Suite("Client Retry Tests") struct ClientRetryTests { - @Test func testRetryOn408RequestTimeout() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 3) - } - - @Test func testRetryOn429TooManyRequests() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 3) - } - - @Test func testRetryOn500InternalServerError() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 3) - } - - @Test func testRetryOn503ServiceUnavailable() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 503, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 2) - } - - @Test func testNoRetryOn400BadRequest() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 400, headers: ["Content-Type": "application/json"], - body: Data("{\"errorName\":\"BadRequest\"}".utf8) - ) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 1) - } - } - - @Test func testNoRetryOn404NotFound() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 404, headers: ["Content-Type": "application/json"], - body: Data("{\"errorName\":\"NotFound\"}".utf8) - ) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 1) - } - } - - @Test func testMaxRetriesExhausted() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 3) - } - } - - @Test func testRetryAfterHeaderWithSeconds() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 429, headers: ["Content-Type": "application/json", "Retry-After": "1"], - body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - let startTime = Date() - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - let elapsed = Date().timeIntervalSince(startTime) - - try #require(stub.getRequestCount() == 2) - try #require(elapsed >= 1.0) - } - - @Test func testRetryAfterHeaderWithHTTPDate() async throws { - let stub = HTTPStub() - let futureEpoch = ceil(Date().timeIntervalSince1970) + 1.0 - let futureDate = Date(timeIntervalSince1970: futureEpoch) - let formatter = DateFormatter() - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(abbreviation: "GMT") - formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" - let httpDate = formatter.string(from: futureDate) - - stub.setResponseSequence([ - ( - statusCode: 429, - headers: ["Content-Type": "application/json", "Retry-After": httpDate], body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - let startTime = Date() - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - let elapsed = Date().timeIntervalSince(startTime) - - try #require(stub.getRequestCount() == 2) - try #require(elapsed >= 0.9) - } - - @Test func testXRateLimitResetHeader() async throws { - let stub = HTTPStub() - let futureTimestamp = Int(ceil(Date().timeIntervalSince1970)) + 1 - - stub.setResponseSequence([ - ( - statusCode: 429, - headers: [ - "Content-Type": "application/json", "X-RateLimit-Reset": "\(futureTimestamp)", - ], body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - let startTime = Date() - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - let elapsed = Date().timeIntervalSince(startTime) - - try #require(stub.getRequestCount() == 2) - try #require(elapsed >= 0.9) - } - - @Test func testEndpointLevelMaxRetriesOverride() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 5, additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 6) - } - - @Test func testEndpointLevelMaxRetriesZero() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 0, additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 1) - } - } - - @Test func testSuccessOnFirstAttempt() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 1) - } -} diff --git a/seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift b/seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift deleted file mode 100644 index 82a3aa80ad22..000000000000 --- a/seed/swift-sdk/allof-inline/Tests/Utilities/HTTPStub.swift +++ /dev/null @@ -1,317 +0,0 @@ -import Api -import Foundation - -final class HTTPStub { - private static func buildURLSession(stubId: String, operationQueue: OperationQueue) - -> Networking.URLSession - { - let config = buildURLSessionConfiguration(stubId: stubId) - if let uuid = UUID(uuidString: stubId) { - StubURLProtocol.register(queue: operationQueue, id: uuid) - } - return Networking.URLSession(configuration: config, delegate: nil, delegateQueue: operationQueue) - } - - private static func buildURLSessionConfiguration(stubId: String) -> Networking.URLSessionConfiguration { - let config = Networking.URLSessionConfiguration.ephemeral - config.protocolClasses = [StubURLProtocol.self] - config.requestCachePolicy = .reloadIgnoringLocalCacheData - config.urlCache = nil - config.httpAdditionalHeaders = ["Stub-ID": stubId] - return config - } - - private static func buildOperationQueue() -> OperationQueue { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - queue.qualityOfService = .userInitiated - return queue - } - - private let session: Networking.URLSession - private let delegateQueue: OperationQueue - private let identifier: UUID - - init() { - self.identifier = UUID() - self.delegateQueue = Self.buildOperationQueue() - self.session = Self.buildURLSession( - stubId: identifier.uuidString, operationQueue: delegateQueue) - #if !canImport(Darwin) - // On Linux, URLProtocol doesn't get the additional headers at canInit time. - // Track the active stub id so the protocol can resolve responses without headers. - StubURLProtocol.setActiveStubId(identifier) - #endif - } - - var urlSession: Networking.URLSession { - session - } - - var headers: [String: String] { - ["Stub-ID": identifier.uuidString] - } - - func setResponse( - statusCode: Int = 200, - headers: [String: String] = ["Content-Type": "application/json"], - body: Data - ) { - StubURLProtocol.configure( - id: identifier, - statusCode: statusCode, - headers: headers, - body: body - ) - } - - func setResponseSequence( - _ responses: [(statusCode: Int, headers: [String: String], body: Data)] - ) { - StubURLProtocol.configureSequence(id: identifier, responses: responses) - } - - func takeLastRequest() -> Networking.URLRequest? { - StubURLProtocol.takeLastRequest(for: identifier) - } - - func getRequestCount() -> Int { - StubURLProtocol.getRequestCount(for: identifier) - } - - deinit { - StubURLProtocol.reset(id: identifier) - StubURLProtocol.deregister(queue: delegateQueue) - #if !canImport(Darwin) - StubURLProtocol.clearActiveStubId(identifier) - #endif - } -} - -private final class StubURLProtocol: Networking.URLProtocol { - struct Response { - let statusCode: Int - let headers: [String: String] - let body: Data - var lastRequest: Networking.URLRequest? - } - - struct ResponseSequence { - var responses: [Response] - var currentIndex: Int = 0 - var lastRequest: Networking.URLRequest? - - mutating func nextResponse() -> Response? { - guard currentIndex < responses.count else { return nil } - let response = responses[currentIndex] - currentIndex += 1 - return response - } - } - - private static var responses: [UUID: Response] = [:] - private static var responseSequences: [UUID: ResponseSequence] = [:] - private static let lock = NSLock() - private static var queueIdMap: [ObjectIdentifier: UUID] = [:] - #if !canImport(Darwin) - // Fallback for Linux where request headers may not be visible in canInit. - private static var activeStubIds: [UUID] = [] - #endif - - static func configure( - id: UUID, - statusCode: Int, - headers: [String: String], - body: Data - ) { - lock.lock() - responses[id] = Response( - statusCode: statusCode, headers: headers, body: body, lastRequest: nil) - responseSequences[id] = nil - lock.unlock() - } - - static func configureSequence( - id: UUID, - responses: [(statusCode: Int, headers: [String: String], body: Data)] - ) { - lock.lock() - let responseList = responses.map { - Response( - statusCode: $0.statusCode, headers: $0.headers, body: $0.body, lastRequest: nil) - } - responseSequences[id] = ResponseSequence(responses: responseList) - StubURLProtocol.responses[id] = nil - lock.unlock() - } - - static func takeLastRequest(for id: UUID) -> Networking.URLRequest? { - lock.lock() - defer { lock.unlock() } - - if var sequence = responseSequences[id], let request = sequence.lastRequest { - sequence.lastRequest = nil - responseSequences[id] = sequence - return request - } - - guard var response = responses[id], let request = response.lastRequest else { - return nil - } - response.lastRequest = nil - responses[id] = response - return request - } - - static func getRequestCount(for id: UUID) -> Int { - lock.lock() - defer { lock.unlock() } - - if let sequence = responseSequences[id] { - return sequence.currentIndex - } - - return responses[id]?.lastRequest != nil ? 1 : 0 - } - - static func reset(id: UUID) { - lock.lock() - responses[id] = nil - responseSequences[id] = nil - lock.unlock() - } - - static func register(queue: OperationQueue, id: UUID) { - lock.lock() - queueIdMap[ObjectIdentifier(queue)] = id - lock.unlock() - } - - static func deregister(queue: OperationQueue) { - lock.lock() - queueIdMap[ObjectIdentifier(queue)] = nil - lock.unlock() - } - - #if !canImport(Darwin) - static func setActiveStubId(_ id: UUID) { - lock.lock() - activeStubIds.append(id) - lock.unlock() - } - - static func clearActiveStubId(_ id: UUID) { - lock.lock() - if let idx = activeStubIds.lastIndex(of: id) { - activeStubIds.remove(at: idx) - } - lock.unlock() - } - #endif - - override class func canInit(with request: Networking.URLRequest) -> Bool { - #if canImport(Darwin) - return request.value(forHTTPHeaderField: "Stub-ID") != nil - #else - // On Linux, intercept all requests created by the session that installed this protocol. - // We'll resolve the correct stub id during startLoading using the active id stack. - return true - #endif - } - - override class func canonicalRequest(for request: Networking.URLRequest) -> Networking.URLRequest { - request - } - - override func startLoading() { - guard let client else { return } - #if canImport(Darwin) - guard let idValue = request.value(forHTTPHeaderField: "Stub-ID"), - let id = UUID(uuidString: idValue) - else { - client.urlProtocol(self, didFailWithError: URLError(.cannotFindHost)) - return - } - #else - // Prefer the Stub-ID header if available on Linux; fall back to the active id stack. - var resolvedId: UUID? - if let idValue = request.value(forHTTPHeaderField: "Stub-ID"), - let headerId = UUID(uuidString: idValue) - { - resolvedId = headerId - } else { - if let currentQueue = OperationQueue.current { - StubURLProtocol.lock.lock() - if let mapped = StubURLProtocol.queueIdMap[ObjectIdentifier(currentQueue)] { - resolvedId = mapped - } - StubURLProtocol.lock.unlock() - } - StubURLProtocol.lock.lock() - resolvedId = StubURLProtocol.activeStubIds.last - StubURLProtocol.lock.unlock() - } - guard let id = resolvedId else { - client.urlProtocol(self, didFailWithError: URLError(.unknown)) - return - } - #endif - - StubURLProtocol.lock.lock() - - if var sequence = StubURLProtocol.responseSequences[id] { - guard let response = sequence.nextResponse() else { - StubURLProtocol.lock.unlock() - client.urlProtocol(self, didFailWithError: URLError(.unknown)) - return - } - sequence.lastRequest = request - StubURLProtocol.responseSequences[id] = sequence - StubURLProtocol.lock.unlock() - - guard let url = request.url else { - client.urlProtocol(self, didFailWithError: URLError(.badURL)) - return - } - - let httpResponse = Networking.HTTPURLResponse( - url: url, - statusCode: response.statusCode, - httpVersion: "HTTP/1.1", - headerFields: response.headers - )! - - client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) - client.urlProtocol(self, didLoad: response.body) - client.urlProtocolDidFinishLoading(self) - return - } - - guard var response = StubURLProtocol.responses[id] else { - StubURLProtocol.lock.unlock() - client.urlProtocol(self, didFailWithError: URLError(.unknown)) - return - } - response.lastRequest = request - StubURLProtocol.responses[id] = response - StubURLProtocol.lock.unlock() - - guard let url = request.url else { - client.urlProtocol(self, didFailWithError: URLError(.badURL)) - return - } - - let httpResponse = Networking.HTTPURLResponse( - url: url, - statusCode: response.statusCode, - httpVersion: "HTTP/1.1", - headerFields: response.headers - )! - - client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) - client.urlProtocol(self, didLoad: response.body) - client.urlProtocolDidFinishLoading(self) - } - - override func stopLoading() {} -} diff --git a/seed/swift-sdk/allof-inline/reference.md b/seed/swift-sdk/allof-inline/reference.md deleted file mode 100644 index 75ea9a025cca..000000000000 --- a/seed/swift-sdk/allof-inline/reference.md +++ /dev/null @@ -1,265 +0,0 @@ -# Reference -
client.searchRuleTypes(query: String?, requestOptions: RequestOptions?) -> RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.searchRuleTypes() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `String?` - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions?) -> RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `Requests.RuleCreateRequest` - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.listUsers(requestOptions: RequestOptions?) -> UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.listUsers() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.getEntity(requestOptions: RequestOptions?) -> CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.getEntity() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.getOrganization(requestOptions: RequestOptions?) -> Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.getOrganization() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- diff --git a/seed/swift-sdk/allof-inline/snippet.json b/seed/swift-sdk/allof-inline/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/swift-sdk/allof/.fern/metadata.json b/seed/swift-sdk/allof/.fern/metadata.json deleted file mode 100644 index 56c0dd230a46..000000000000 --- a/seed/swift-sdk/allof/.fern/metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-swift-sdk", - "generatorVersion": "local", - "generatorConfig": {}, - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Package.swift b/seed/swift-sdk/allof/Package.swift deleted file mode 100644 index a66695fa5685..000000000000 --- a/seed/swift-sdk/allof/Package.swift +++ /dev/null @@ -1,31 +0,0 @@ -// swift-tools-version: 5.7 - -import PackageDescription - -let package = Package( - name: "Api", - platforms: [ - .iOS(.v15), - .macOS(.v12), - .tvOS(.v15), - .watchOS(.v8) - ], - products: [ - .library( - name: "Api", - targets: ["Api"] - ) - ], - dependencies: [], - targets: [ - .target( - name: "Api", - path: "Sources" - ), - .testTarget( - name: "ApiTests", - dependencies: ["Api"], - path: "Tests" - ) - ] -) diff --git a/seed/swift-sdk/allof/README.md b/seed/swift-sdk/allof/README.md deleted file mode 100644 index f17a8fa15733..000000000000 --- a/seed/swift-sdk/allof/README.md +++ /dev/null @@ -1,180 +0,0 @@ -# Seed Swift Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FSwift) -![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-orange.svg) - -The Seed Swift library provides convenient access to the Seed APIs from Swift. - -## Table of Contents - -- [Requirements](#requirements) -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Errors](#errors) -- [Request Types](#request-types) -- [Advanced](#advanced) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) - - [Timeouts](#timeouts) - - [Custom Networking Client](#custom-networking-client) -- [Contributing](#contributing) - -## Requirements - -This SDK requires: -- Swift 5.7+ -- iOS 15+ -- macOS 12+ -- tvOS 15+ -- watchOS 8+ - -## Installation - -With Swift Package Manager (SPM), add the following to the top-level `dependencies` array within your `Package.swift` file: - -```swift -dependencies: [ - .package(url: "https://github.com/allof/fern", from: "0.0.1"), -] -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```swift -import Api - -let client = ApiClient( - ..., - environment: .default -) -``` - -## Errors - -The SDK throws a single error enum for all failures. Client-side issues encoding/decoding failures and network errors use dedicated cases, while non-success HTTP responses are wrapped in an `HTTPError` that exposes the status code, a simple classification and an optional decoded message. - -```swift -import Api - -let client = ApiClient(...) - -do { - let response = try await client.createRule(...) - // Handle successful response -} catch let error as ApiError { - switch error { - case .httpError(let httpError): - print("Status code:", httpError.statusCode) - print("Kind:", httpError.kind) - print("Message:", httpError.body?.message ?? httpError.localizedDescription) - case .encodingError(let underlying): - print("Encoding error:", underlying) - case .networkError(let underlying): - print("Network error:", underlying) - default: - print("Other client error:", error) - } -} catch { - print("Unexpected error:", error) -} -``` - -## Request Types - -The SDK exports all request types as Swift structs. Simply import the SDK module to access them: - -```swift -import Api - -let request = Requests.RuleCreateRequest( - ... -) -``` - -## Advanced - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `additionalHeaders` request option. - -```swift -try await client.createRule(..., requestOptions: .init( - additionalHeaders: [ - "X-Custom-Header": "custom value" - ] -)) -``` - -### Additional Query String Parameters - -If you would like to send additional query string parameters as part of the request, use the `additionalQueryParameters` request option. - -```swift -try await client.createRule(..., requestOptions: .init( - additionalQueryParameters: [ - "custom_query_param_key": "custom_query_param_value" - ] -)) -``` - -### Timeouts - -The SDK defaults to a 60-second timeout. Use the `timeout` option to configure this behavior. - -```swift -try await client.createRule(..., requestOptions: .init( - timeout: 30 -)) -``` - -### Custom Networking Client - -The SDK allows you to customize the underlying `URLSession` used for HTTP requests. Use the `urlSession` option to provide your own configured `URLSession` instance. - -```swift -import Foundation -import Api - -let client = ApiClient( - ..., - urlSession: // Provide your implementation here -) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/swift-sdk/allof/Snippets/Example0.swift b/seed/swift-sdk/allof/Snippets/Example0.swift deleted file mode 100644 index 6a8a354329f0..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example0.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.searchRuleTypes() -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example1.swift b/seed/swift-sdk/allof/Snippets/Example1.swift deleted file mode 100644 index 165f0fa4c377..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example1.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.searchRuleTypes(query: "query") -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example2.swift b/seed/swift-sdk/allof/Snippets/Example2.swift deleted file mode 100644 index 77e7ea37ecaf..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example2.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example3.swift b/seed/swift-sdk/allof/Snippets/Example3.swift deleted file mode 100644 index 77e7ea37ecaf..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example3.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example4.swift b/seed/swift-sdk/allof/Snippets/Example4.swift deleted file mode 100644 index 25d9aafe2c0e..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example4.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.listUsers() -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example5.swift b/seed/swift-sdk/allof/Snippets/Example5.swift deleted file mode 100644 index 25d9aafe2c0e..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example5.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.listUsers() -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example6.swift b/seed/swift-sdk/allof/Snippets/Example6.swift deleted file mode 100644 index cd12bd038474..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example6.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getEntity() -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example7.swift b/seed/swift-sdk/allof/Snippets/Example7.swift deleted file mode 100644 index cd12bd038474..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example7.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getEntity() -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example8.swift b/seed/swift-sdk/allof/Snippets/Example8.swift deleted file mode 100644 index 935ba9bb5c06..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example8.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getOrganization() -} - -try await main() diff --git a/seed/swift-sdk/allof/Snippets/Example9.swift b/seed/swift-sdk/allof/Snippets/Example9.swift deleted file mode 100644 index 935ba9bb5c06..000000000000 --- a/seed/swift-sdk/allof/Snippets/Example9.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import Api - -private func main() async throws { - let client = ApiClient(baseURL: "https://api.fern.com") - - _ = try await client.getOrganization() -} - -try await main() diff --git a/seed/swift-sdk/allof/Sources/ApiClient.swift b/seed/swift-sdk/allof/Sources/ApiClient.swift deleted file mode 100644 index 3bb0c30a67c5..000000000000 --- a/seed/swift-sdk/allof/Sources/ApiClient.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Foundation - -/// Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. -public final class ApiClient: Sendable { - private let httpClient: HTTPClient - - /// Initialize the client with the specified configuration. - /// - /// - Parameter baseURL: The base URL to use for requests from the client. If not provided, the default base URL will be used. - /// - Parameter headers: Additional headers to send with each request. - /// - Parameter timeout: Request timeout in seconds. Defaults to 60 seconds. Ignored if a custom `urlSession` is provided. - /// - Parameter maxRetries: Maximum number of retries for failed requests. Defaults to 2. - /// - Parameter urlSession: Custom `URLSession` to use for requests. If not provided, a default session will be created with the specified timeout. - public convenience init( - baseURL: String = ApiEnvironment.default.rawValue, - headers: [String: String]? = nil, - timeout: Int? = nil, - maxRetries: Int? = nil, - urlSession: Networking.URLSession? = nil - ) { - self.init( - baseURL: baseURL, - headerAuth: nil, - bearerAuth: nil, - basicAuth: nil, - headers: headers, - timeout: timeout, - maxRetries: maxRetries, - urlSession: urlSession - ) - } - - init( - baseURL: String, - headerAuth: ClientConfig.HeaderAuth? = nil, - bearerAuth: ClientConfig.BearerAuth? = nil, - basicAuth: ClientConfig.BasicAuth? = nil, - headers: [String: String]? = nil, - timeout: Int? = nil, - maxRetries: Int? = nil, - urlSession: Networking.URLSession? = nil - ) { - let config = ClientConfig( - baseURL: baseURL, - headerAuth: headerAuth, - bearerAuth: bearerAuth, - basicAuth: basicAuth, - headers: headers, - timeout: timeout, - maxRetries: maxRetries, - urlSession: urlSession - ) - self.httpClient = HTTPClient(config: config) - } - - public func searchRuleTypes(query: String? = nil, requestOptions: RequestOptions? = nil) async throws -> RuleTypeSearchResponse { - return try await httpClient.performRequest( - method: .get, - path: "/rule-types", - queryParams: [ - "query": query.map { .string($0) } - ], - requestOptions: requestOptions, - responseType: RuleTypeSearchResponse.self - ) - } - - public func createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions? = nil) async throws -> RuleResponse { - return try await httpClient.performRequest( - method: .post, - path: "/rules", - body: request, - requestOptions: requestOptions, - responseType: RuleResponse.self - ) - } - - public func listUsers(requestOptions: RequestOptions? = nil) async throws -> UserSearchResponse { - return try await httpClient.performRequest( - method: .get, - path: "/users", - requestOptions: requestOptions, - responseType: UserSearchResponse.self - ) - } - - public func getEntity(requestOptions: RequestOptions? = nil) async throws -> CombinedEntity { - return try await httpClient.performRequest( - method: .get, - path: "/entities", - requestOptions: requestOptions, - responseType: CombinedEntity.self - ) - } - - public func getOrganization(requestOptions: RequestOptions? = nil) async throws -> Organization { - return try await httpClient.performRequest( - method: .get, - path: "/organizations", - requestOptions: requestOptions, - responseType: Organization.self - ) - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/ApiEnvironment.swift b/seed/swift-sdk/allof/Sources/ApiEnvironment.swift deleted file mode 100644 index 817df36e4a46..000000000000 --- a/seed/swift-sdk/allof/Sources/ApiEnvironment.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public enum ApiEnvironment: String, CaseIterable { - case `default` = "https://api.example.com" -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/ApiError.swift b/seed/swift-sdk/allof/Sources/ApiError.swift deleted file mode 100644 index 37bf4a118e61..000000000000 --- a/seed/swift-sdk/allof/Sources/ApiError.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -/// High-level error type thrown by generated Swift SDKs. -/// -/// Request / client-side failures are represented as dedicated cases and -/// HTTP response failures are wrapped in an `HTTPError` that classifies the status code. -public enum ApiError: Swift.Error { - // MARK: - Client / transport errors - - /// The request URL could not be constructed. - case invalidURL(Swift.String) - - /// The request body could not be encoded. - case encodingError(Swift.Error) - - /// The response body could not be decoded. - case decodingError(Swift.Error) - - /// The SDK received a response it could not interpret as a valid HTTP response. - case invalidResponse - - /// An underlying networking error occurred (e.g., connection reset). - case networkError(Swift.Error) - - /// The request timed out. - case timeout(Swift.Error?) - - // MARK: - HTTP response errors - - /// An error HTTP response was returned by the server. - case httpError(HTTPError) - - // MARK: - Description - - public var errorDescription: Swift.String? { - switch self { - case .invalidURL(let url): - return "Invalid URL '\(url)'" - case .encodingError(let error): - return "Failed to encode request: \(error.localizedDescription)" - case .decodingError(let error): - return "Failed to decode response: \(error.localizedDescription)" - case .invalidResponse: - return "Invalid response received" - case .networkError(let error): - return "Network error: \(error.localizedDescription)" - case .timeout(let underlying): - if let underlying { - return "Request timed out: \(underlying.localizedDescription)" - } else { - return "Request timed out" - } - case .httpError(let httpError): - return httpError.localizedDescription - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Data+String.swift b/seed/swift-sdk/allof/Sources/Core/Data+String.swift deleted file mode 100644 index ddf326ffbfe6..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Data+String.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -// MARK: - Data + String Extensions -extension Foundation.Data { - /// Safely appends a UTF-8 encoded string to the data - /// - /// - Parameter string: The string to append - mutating func appendUTF8String(_ string: Swift.String) { - guard let data = string.data(using: .utf8) else { - assertionFailure("Failed to encode string to UTF-8: \(string)") - return - } - self.append(data) - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift b/seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift deleted file mode 100644 index 445e45cbdb96..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Networking/HTTP.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation - -enum HTTP { - enum Method: Swift.String, Swift.CaseIterable { - case get = "GET" - case post = "POST" - case put = "PUT" - case delete = "DELETE" - case patch = "PATCH" - case head = "HEAD" - } - - enum ContentType: Swift.String, Swift.CaseIterable { - case applicationJson = "application/json" - case applicationOctetStream = "application/octet-stream" - case multipartFormData = "multipart/form-data" - } - - enum RequestBody { - case jsonEncodable(any Swift.Encodable) - case data(Foundation.Data) - case multipartFormData(MultipartFormData) - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift b/seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift deleted file mode 100644 index 18e8927122ef..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Networking/HTTPClient.swift +++ /dev/null @@ -1,397 +0,0 @@ -import Foundation - -final class HTTPClient: Swift.Sendable { - private let clientConfig: ClientConfig - private let jsonEncoder = Serde.jsonEncoder - private let jsonDecoder = Serde.jsonDecoder - - private static let initialRetryDelay: Foundation.TimeInterval = 1.0 // 1 second - private static let maxRetryDelay: Foundation.TimeInterval = 60.0 // 60 seconds - private static let jitterFactor: Swift.Double = 0.2 // 20% jitter - - init(config: ClientConfig) { - self.clientConfig = config - } - - /// Performs a request with no response. - func performRequest( - method: HTTP.Method, - path: Swift.String, - contentType requestContentType: HTTP.ContentType = .applicationJson, - headers requestHeaders: [Swift.String: Swift.String?] = [:], - queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], - body requestBody: Any? = nil, - requestOptions: RequestOptions? = nil - ) async throws { - _ = try await performRequest( - method: method, - path: path, - contentType: requestContentType, - headers: requestHeaders, - queryParams: requestQueryParams, - body: requestBody, - requestOptions: requestOptions, - responseType: Foundation.Data.self - ) - } - - /// Performs a request with the specified response type. - func performRequest( - method: HTTP.Method, - path: Swift.String, - contentType requestContentType: HTTP.ContentType = .applicationJson, - headers requestHeaders: [Swift.String: Swift.String?] = [:], - queryParams requestQueryParams: [Swift.String: QueryParameter?] = [:], - body requestBody: Any? = nil, - requestOptions: RequestOptions? = nil, - responseType: T.Type - ) async throws -> T { - let requestBody: HTTP.RequestBody? = requestBody.map { body in - if let multipartData = body as? MultipartFormData { - return .multipartFormData(multipartData) - } else if let data = body as? Foundation.Data { - return .data(data) - } else if let encodable = body as? any Swift.Encodable { - return .jsonEncodable(encodable) - } else { - preconditionFailure("Unsupported body type: \(type(of: body))") - } - } - - let request = try await buildRequest( - method: method, - path: path, - requestContentType: requestContentType, - requestHeaders: requestHeaders, - requestQueryParams: requestQueryParams, - requestBody: requestBody, - requestOptions: requestOptions - ) - - let (data, _) = try await executeRequestWithURLSession( - request, - requestOptions: requestOptions - ) - - if responseType == Foundation.Data.self { - if let data = data as? T { - return data - } else { - throw ApiError.invalidResponse - } - } - - if responseType == Swift.String.self { - if let string = Swift.String(data: data, encoding: .utf8) as? T { - return string - } else { - throw ApiError.invalidResponse - } - } - - do { - return try jsonDecoder.decode(responseType, from: data) - } catch { - throw ApiError.decodingError(error) - } - } - - private func buildRequest( - method: HTTP.Method, - path: Swift.String, - requestContentType: HTTP.ContentType, - requestHeaders: [Swift.String: Swift.String?], - requestQueryParams: [Swift.String: QueryParameter?], - requestBody: HTTP.RequestBody? = nil, - requestOptions: RequestOptions? = nil - ) async throws -> Networking.URLRequest { - // Init with URL - let url = buildRequestURL( - path: path, requestQueryParams: requestQueryParams, requestOptions: requestOptions - ) - var request = Networking.URLRequest(url: url) - - // Set timeout - if let timeout = requestOptions?.timeout { - request.timeoutInterval = Foundation.TimeInterval(timeout) - } - - // Set method - request.httpMethod = method.rawValue - - // Set headers - let headers = try await buildRequestHeaders( - requestBody: requestBody, - requestContentType: requestContentType, - requestHeaders: requestHeaders, - requestOptions: requestOptions - ) - for (key, value) in headers { - request.setValue(value, forHTTPHeaderField: key) - } - - // Set body - if let requestBody = requestBody { - request.httpBody = buildRequestBody( - requestBody: requestBody, - requestOptions: requestOptions - ) - } - - return request - } - - private func buildRequestURL( - path: Swift.String, - requestQueryParams: [Swift.String: QueryParameter?], - requestOptions: RequestOptions? = nil - ) -> URL { - let endpointURL = "\(clientConfig.baseURL)\(path)" - guard var components = Foundation.URLComponents(string: endpointURL) else { - preconditionFailure( - "Invalid URL '\(endpointURL)' - this indicates an unexpected error in the SDK." - ) - } - if !requestQueryParams.isEmpty { - let baseItems: [Foundation.URLQueryItem] = requestQueryParams.compactMap { key, value in - guard let unwrapped = value else { return nil } - let stringValue = unwrapped.toString() - guard !stringValue.isEmpty else { return nil } - return Foundation.URLQueryItem(name: key, value: stringValue) - } - if !baseItems.isEmpty { - components.queryItems = baseItems - } - } - if let additionalQueryParams = requestOptions?.additionalQueryParameters { - let extraItems = additionalQueryParams.compactMap { key, value in - value.isEmpty ? nil : Foundation.URLQueryItem(name: key, value: value) - } - if components.queryItems == nil { - components.queryItems = extraItems - } else { - components.queryItems?.append(contentsOf: extraItems) - } - } - guard let url = components.url else { - preconditionFailure( - "Failed to construct URL from components - this indicates an unexpected error in the SDK." - ) - } - return url - } - - private func buildRequestHeaders( - requestBody: HTTP.RequestBody?, - requestContentType: HTTP.ContentType, - requestHeaders: [Swift.String: Swift.String?], - requestOptions: RequestOptions? = nil - ) async throws -> [Swift.String: Swift.String] { - var headers = clientConfig.headers ?? [:] - - headers["Content-Type"] = buildContentTypeHeader( - requestBody: requestBody, - requestContentType: requestContentType - ) - - if let headerAuth = clientConfig.headerAuth { - headers[headerAuth.header] = requestOptions?.apiKey ?? headerAuth.key - } - if let basicAuthToken = clientConfig.basicAuth?.token { - headers["Authorization"] = "Basic \(basicAuthToken)" - } - if let bearerAuthToken = try await getBearerAuthToken(requestOptions) { - headers["Authorization"] = "Bearer \(bearerAuthToken)" - } - for (key, value) in requestHeaders { - if let value = value { - headers[key] = value - } - } - for (key, value) in requestOptions?.additionalHeaders ?? [:] { - headers[key] = value - } - - return headers - } - - private func buildContentTypeHeader( - requestBody: HTTP.RequestBody?, - requestContentType: HTTP.ContentType, - ) -> Swift.String { - var contentType = requestContentType.rawValue - if let requestBody, case .multipartFormData(let multipartData) = requestBody { - if contentType != HTTP.ContentType.multipartFormData.rawValue { - preconditionFailure( - "The content type for multipart form data requests must be multipart/form-data - this indicates an unexpected error in the SDK." - ) - } - // Multipart form data content type must include the boundary - contentType = "\(contentType); boundary=\(multipartData.boundary)" - } - return contentType - } - - private func getBearerAuthToken(_ requestOptions: RequestOptions?) async throws -> Swift.String? - { - if let tokenString = requestOptions?.token { - return tokenString - } - if let bearerAuth = clientConfig.bearerAuth { - return try await bearerAuth.token.retrieve() - } - return nil - } - - private func buildRequestBody( - requestBody: HTTP.RequestBody, - requestOptions: RequestOptions? = nil - ) -> Data { - switch requestBody { - case .jsonEncodable(let encodableBody): - do { - return try jsonEncoder.encode(encodableBody) - } catch { - preconditionFailure( - "Failed to encode request body: \(error) - this indicates an unexpected error in the SDK." - ) - } - case .data(let dataBody): - return dataBody - case .multipartFormData(let multipartData): - return multipartData.data() - } - } - - private func executeRequestWithURLSession( - _ request: Networking.URLRequest, - requestOptions: RequestOptions? = nil - ) async throws -> (Foundation.Data, Swift.String?) { - let maxRetries = requestOptions?.maxRetries ?? clientConfig.maxRetries - var lastResponse: (Foundation.Data, Networking.HTTPURLResponse)? - - for attempt in 0...maxRetries { - do { - let (data, response) = try await clientConfig.urlSession.data(for: request) - - guard let httpResponse = response as? Networking.HTTPURLResponse else { - throw ApiError.invalidResponse - } - - // Handle successful responses - if 200...299 ~= httpResponse.statusCode { - let contentType = httpResponse.value(forHTTPHeaderField: "Content-Type") - return (data, contentType) - } - - lastResponse = (data, httpResponse) - - if attempt < maxRetries && shouldRetry(statusCode: httpResponse.statusCode) { - let delay = getRetryDelay(response: httpResponse, retryAttempt: attempt) - try await _Concurrency.Task.sleep( - nanoseconds: Swift.UInt64(delay * 1_000_000_000)) - continue - } - - throw makeErrorFromResponse( - statusCode: httpResponse.statusCode, - data: data - ) - } catch { - let clientError: ApiError? - - // Treat timeouts as a first-class, non-retryable error - if let urlError = error as? Foundation.URLError, urlError.code == .timedOut { - clientError = .timeout(error) - } else if let existingClientError = error as? ApiError { - clientError = existingClientError - } else { - clientError = nil - } - - if attempt >= maxRetries || clientError != nil { - if let clientError { - throw clientError - } else { - throw ApiError.networkError(error) - } - } - let delay = Self.initialRetryDelay * pow(2.0, Swift.Double(attempt)) - let cappedDelay = min(delay, Self.maxRetryDelay) - let jitteredDelay = addSymmetricJitter(to: cappedDelay) - try await _Concurrency.Task.sleep( - nanoseconds: Swift.UInt64(jitteredDelay * 1_000_000_000)) - } - } - - if let (data, httpResponse) = lastResponse { - throw makeErrorFromResponse(statusCode: httpResponse.statusCode, data: data) - } - throw ApiError.invalidResponse - } - - private func shouldRetry(statusCode: Swift.Int) -> Swift.Bool { - return statusCode == 408 || statusCode == 429 || statusCode >= 500 - } - - private func getRetryDelay(response: Networking.HTTPURLResponse, retryAttempt: Swift.Int) - -> Foundation.TimeInterval - { - if let retryAfter = response.value(forHTTPHeaderField: "Retry-After") { - if let seconds = Swift.Double(retryAfter), seconds > 0 { - return min(seconds, Self.maxRetryDelay) - } - - if let date = parseHTTPDate(retryAfter) { - let delay = date.timeIntervalSinceNow - if delay > 0 { - return min(delay, Self.maxRetryDelay) - } - } - } - - if let rateLimitReset = response.value(forHTTPHeaderField: "X-RateLimit-Reset") { - if let resetTimeSeconds = Swift.Double(rateLimitReset) { - let resetDate = Foundation.Date(timeIntervalSince1970: resetTimeSeconds) - let delay = resetDate.timeIntervalSinceNow - if delay > 0 { - let cappedDelay = min(delay, Self.maxRetryDelay) - return addPositiveJitter(to: cappedDelay) - } - } - } - - let baseDelay = Self.initialRetryDelay * pow(2.0, Swift.Double(retryAttempt)) - let cappedDelay = min(baseDelay, Self.maxRetryDelay) - return addSymmetricJitter(to: cappedDelay) - } - - private func parseHTTPDate(_ dateString: Swift.String) -> Foundation.Date? { - let formatter = Foundation.DateFormatter() - formatter.locale = Foundation.Locale(identifier: "en_US_POSIX") - formatter.timeZone = Foundation.TimeZone(abbreviation: "GMT") - formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" - return formatter.date(from: dateString) - } - - private func addPositiveJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { - let jitterMultiplier = 1.0 + Swift.Double.random(in: 0...Self.jitterFactor) - return delay * jitterMultiplier - } - - private func addSymmetricJitter(to delay: Foundation.TimeInterval) -> Foundation.TimeInterval { - let jitterMultiplier = - 1.0 + Swift.Double.random(in: -Self.jitterFactor / 2...Self.jitterFactor / 2) - return delay * jitterMultiplier - } - - private func makeErrorFromResponse(statusCode: Swift.Int, data: Foundation.Data) -> ApiError - { - let httpError = HTTPError.from( - statusCode: statusCode, - data: data, - jsonDecoder: jsonDecoder - ) - return ApiError.httpError(httpError) - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift deleted file mode 100644 index 06d0a4474d13..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormData.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation - -/// Helper class for building multipart form data requests -class MultipartFormData { - let boundary: Swift.String - private var bodyData: Foundation.Data - - init() { - self.boundary = "Boundary-\(Foundation.UUID().uuidString)" - self.bodyData = Foundation.Data() - } - - /// Append a file field to the form data - func appendFile( - _ data: Foundation.Data, withName name: Swift.String, fileName: Swift.String? = nil - ) { - bodyData.appendUTF8String("--\(boundary)\r\n") - var contentDisposition = "Content-Disposition: form-data; name=\"\(name)\"" - if let fileName { - contentDisposition += "; filename=\"\(fileName)\"" - } - contentDisposition += "\r\n" - bodyData.appendUTF8String(contentDisposition) - bodyData.appendUTF8String( - "Content-Type: \(HTTP.ContentType.applicationOctetStream.rawValue)\r\n\r\n") - bodyData.append(data) - bodyData.appendUTF8String("\r\n") - } - - /// Append a text field to the form data - func appendField(_ value: Swift.String, withName name: Swift.String) { - bodyData.appendUTF8String("--\(boundary)\r\n") - bodyData.appendUTF8String( - "Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") - bodyData.appendUTF8String(value) - bodyData.appendUTF8String("\r\n") - } - - /// Returns the complete multipart form data with closing boundary - func data() -> Foundation.Data { - var finalData = bodyData - finalData.appendUTF8String("--\(boundary)--\r\n") - return finalData - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift deleted file mode 100644 index 6184b103cf1a..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormDataConvertible.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation - -/// Protocol for types that can be converted to multipart form data -protocol MultipartFormDataConvertible { - /// The multipart fields that represent this request - var multipartFormFields: [MultipartFormField] { get } -} - -extension MultipartFormDataConvertible { - /// Converts this request to multipart form data - func asMultipartFormData() -> MultipartFormData { - let multipartData = MultipartFormData() - let jsonEncoder = Serde.jsonEncoder - - for field in multipartFormFields { - switch field { - case .file(let file, let fieldName): - multipartData.appendFile(file.data, withName: fieldName, fileName: file.filename) - case .fileArray(let files, let fieldName): - for file in files { - multipartData.appendFile( - file.data, - withName: fieldName, - fileName: file.filename - ) - } - case .field(let encodableValue, let fieldName): - do { - let encodedData = try jsonEncoder.encode(value: encodableValue) - if let encodedString = Swift.String(data: encodedData, encoding: .utf8) { - multipartData.appendField(encodedString, withName: fieldName) - } - } catch { - // Fallback - this should rarely happen with well-formed Encodable types - multipartData.appendField("", withName: fieldName) - } - } - } - - return multipartData - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift b/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift deleted file mode 100644 index 3b57f57d8904..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Networking/MultipartFormField.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -/// Represents a field in multipart form data -enum MultipartFormField { - /// A single file field - case file(_ file: FormFile, fieldName: Swift.String) - /// An array of files with the same field name - case fileArray(_ files: [FormFile], fieldName: Swift.String) - /// A text field with JSON-encoded value (for strings, numbers, booleans, dates, etc.) - case field(_ value: EncodableValue, fieldName: Swift.String) - - /// Create a text field from any Encodable value - static func field(_ value: T, fieldName: Swift.String) -> MultipartFormField - { - return .field(.init(value), fieldName: fieldName) - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift b/seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift deleted file mode 100644 index 504f439749b5..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Networking/QueryParameter.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation - -enum QueryParameter { - case string(Swift.String) - case bool(Swift.Bool) - case int(Swift.Int) - case uint(Swift.UInt) - case uint64(Swift.UInt64) - case int64(Swift.Int64) - case float(Swift.Float) - case double(Swift.Double) - case date(Foundation.Date) - case calendarDate(CalendarDate) - case stringArray([Swift.String]) - case uuid(Foundation.UUID) - case unknown(Any) - - func toString() -> Swift.String { - switch self { - case .string(let value): - return value - case .bool(let value): - return value ? "true" : "false" - case .int(let value): - return Swift.String(value) - case .uint(let value): - return Swift.String(value) - case .uint64(let value): - return Swift.String(value) - case .int64(let value): - return Swift.String(value) - case .float(let value): - return Swift.String(value) - case .double(let value): - return Swift.String(value) - case .date(let value): - return value.ISO8601Format() - case .calendarDate(let value): - return value.description - case .stringArray(let values): - return values.joined(separator: ",") - case .uuid(let value): - return value.uuidString - case .unknown: - return "" - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift b/seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift deleted file mode 100644 index 4148f56b643d..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/Decoder+AdditionalProperties.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -extension Swift.Decoder { - func decodeAdditionalProperties< - T: Swift.Decodable, C: Swift.CaseIterable & Swift.RawRepresentable - >( - using codingKeysType: C.Type - ) throws - -> [Swift.String: T] where C.RawValue == Swift.String - { - return try decodeAdditionalProperties( - knownKeys: Swift.Set(codingKeysType.allCases.map(\.rawValue))) - } - - func decodeAdditionalProperties(knownKeys: Swift.Set) throws - -> [Swift.String: T] - { - let container = try container(keyedBy: StringKey.self) - let unknownKeys = Swift.Set(container.allKeys).subtracting( - knownKeys.map(StringKey.init(_:))) - guard !unknownKeys.isEmpty else { return .init() } - let keyValuePairs: [(Swift.String, T)] = try unknownKeys.compactMap { key in - (key.stringValue, try container.decode(T.self, forKey: key)) - } - return .init(uniqueKeysWithValues: keyValuePairs) - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift b/seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift deleted file mode 100644 index 69a075f244f5..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/EncodableValue.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -/// Type-erased wrapper for encodable values -struct EncodableValue { - let value: any Swift.Encodable - - init(_ value: T) { - self.value = value - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift b/seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift deleted file mode 100644 index e1e7c48f9fc0..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/Encoder+AdditionalProperties.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Swift.Encoder { - func encodeAdditionalProperties(_ additionalProperties: [Swift.String: T]) - throws - { - guard !additionalProperties.isEmpty else { return } - var container = self.container(keyedBy: StringKey.self) - for (key, value) in additionalProperties { - try container.encode(value, forKey: .init(key)) - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift b/seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift deleted file mode 100644 index 3f0ea998c148..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/JSONEncoder+EncodableValue.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -extension Foundation.JSONEncoder { - /// Helper for type-erasing Encodable values - private struct AnyEncodable: Swift.Encodable { - private let value: any Swift.Encodable - - init(_ value: any Swift.Encodable) { - self.value = value - } - - func encode(to encoder: Swift.Encoder) throws { - try value.encode(to: encoder) - } - } - - func encode(value: EncodableValue) throws -> Foundation.Data { - return try self.encode(AnyEncodable(value.value)) - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift b/seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift deleted file mode 100644 index a5dfedba2c4b..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/KeyedDecodingContainer+Nullable.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -extension Swift.KeyedDecodingContainer { - /// Decodes a Nullable? value, properly handling missing vs null vs value - /// Use this for `Optional>` fields - public func decodeNullableIfPresent( - _ type: T.Type, forKey key: Swift.KeyedDecodingContainer.Key - ) throws -> Nullable? where T: Swift.Decodable { - if contains(key) { - if try decodeNil(forKey: key) { - return .null - } else { - let value = try decode(type, forKey: key) - return .value(value) - } - } else { - return nil - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift b/seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift deleted file mode 100644 index 89ac59ace39b..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/KeyedEncodingContainer+Nullable.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -extension Swift.KeyedEncodingContainer { - /// Encodes a Nullable? value, properly handling missing vs null vs value - /// Use this for `Optional>` fields - public mutating func encodeNullableIfPresent( - _ value: Nullable?, forKey key: Swift.KeyedEncodingContainer.Key - ) throws where T: Swift.Encodable { - switch value { - case nil: - // Don't encode the key at all - field is missing - break - case .some(.null): - try encodeNil(forKey: key) - case .some(.value(let wrapped)): - try encode(wrapped, forKey: key) - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift b/seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift deleted file mode 100644 index b64e6cb3d410..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/Serde.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation - -final class Serde { - static var jsonEncoder: Foundation.JSONEncoder { - let encoder = Foundation.JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - return encoder - } - - static var jsonDecoder: Foundation.JSONDecoder { - let decoder = Foundation.JSONDecoder() - // Use custom strategy for robust ISO 8601 date parsing with fractional seconds - decoder.dateDecodingStrategy = .custom { decoder in - let container = try decoder.singleValueContainer() - let dateString = try container.decode(String.self) - - let formatter = Foundation.ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - - if let date = formatter.date(from: dateString) { - return date - } - - // Fallback for dates without fractional seconds - formatter.formatOptions = [.withInternetDateTime] - if let date = formatter.date(from: dateString) { - return date - } - - throw Swift.DecodingError.dataCorruptedError( - in: container, debugDescription: "Invalid date format: \(dateString)") - } - return decoder - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift b/seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift deleted file mode 100644 index a4f0c42b3c30..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/Serde/StringKey.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -struct StringKey: Swift.CodingKey, Swift.Hashable { - var stringValue: Swift.String - var intValue: Swift.Int? { Swift.Int(stringValue) } - - init(_ string: Swift.String) { - self.stringValue = string - } - - init?(stringValue: Swift.String) { - self.stringValue = stringValue - } - - init?(intValue: Swift.Int) { - self.stringValue = Swift.String(intValue) - } -} diff --git a/seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift b/seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift deleted file mode 100644 index f852dbd94cb1..000000000000 --- a/seed/swift-sdk/allof/Sources/Core/String+URLEncoding.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -extension Swift.String { - func urlPathEncoded() -> Swift.String { - return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self - } - - func urlQueryEncoded() -> Swift.String { - return self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self - } -} diff --git a/seed/swift-sdk/allof/Sources/Public/CalendarDate.swift b/seed/swift-sdk/allof/Sources/Public/CalendarDate.swift deleted file mode 100644 index 3159252bcd09..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/CalendarDate.swift +++ /dev/null @@ -1,105 +0,0 @@ -import Foundation - -/// Represents a calendar date without time information, following RFC 3339 section 5.6 (`YYYY-MM-DD` format) -public struct CalendarDate: Swift.Codable, Swift.Hashable, Swift.Sendable, Swift - .CustomStringConvertible, Swift.Comparable -{ - /// The year component (expected range: 1-9999) - public let year: Swift.Int - - /// The month component (valid range: 1-12) - public let month: Swift.Int - - /// The day component (valid range: 1-31, depending on month) - public let day: Swift.Int - - /// Failable initializer for creating a CalendarDate with validation - public init?(year: Swift.Int, month: Swift.Int, day: Swift.Int) { - guard Self.isValidDate(year: year, month: month, day: day) else { - return nil - } - self.year = year - self.month = month - self.day = day - } - - /// Failable initializer for creating a CalendarDate from a `YYYY-MM-DD` string - public init?(_ dateString: Swift.String) { - let components = dateString.split(separator: "-") - guard components.count == 3, - let year = Swift.Int(components[0]), - let month = Swift.Int(components[1]), - let day = Swift.Int(components[2]) - else { - return nil - } - self.init(year: year, month: month, day: day) - } - - // MARK: - Codable - - public init(from decoder: Swift.Decoder) throws { - let container = try decoder.singleValueContainer() - let dateString = try container.decode(String.self) - guard let calendarDate = CalendarDate(dateString) else { - throw Error.invalidFormat(dateString) - } - self = calendarDate - } - - public func encode(to encoder: Swift.Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(description) - } - - // MARK: - CustomStringConvertible - - public var description: Swift.String { - // Format as YYYY-MM-DD with zero-padding - // %04d = 4-digit year with leading zeros (e.g., 2025) - // %02d = 2-digit month/day with leading zeros (e.g., 01, 05) - Swift.String(format: "%04d-%02d-%02d", year, month, day) - } - - // MARK: - Comparable - - public static func < (lhs: CalendarDate, rhs: CalendarDate) -> Swift.Bool { - if lhs.year != rhs.year { return lhs.year < rhs.year } - if lhs.month != rhs.month { return lhs.month < rhs.month } - return lhs.day < rhs.day - } - - // MARK: - Private Helpers - - /// Validates that the given year, month, and day form a valid calendar date using Foundation's Calendar APIs. - private static func isValidDate(year: Swift.Int, month: Swift.Int, day: Swift.Int) -> Swift.Bool - { - let calendar = Foundation.Calendar(identifier: .gregorian) - let components = Foundation.DateComponents(year: year, month: month, day: day) - - guard let date = calendar.date(from: components) else { - return false - } - - // Ensure the date components match what we created (handles invalid dates like Feb 30) - let reconstructedComponents = calendar.dateComponents([.year, .month, .day], from: date) - return - (reconstructedComponents.year == year - && reconstructedComponents.month == month - && reconstructedComponents.day == day) - } - - // MARK: - Error Types - - /// Errors that can occur when working with CalendarDate - public enum Error: Swift.Error, Foundation.LocalizedError { - case invalidFormat(Swift.String) - - public var errorDescription: Swift.String? { - switch self { - case .invalidFormat(let string): - return "Invalid date format: '\(string)'. Expected YYYY-MM-DD" - } - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Public/ClientConfig.swift b/seed/swift-sdk/allof/Sources/Public/ClientConfig.swift deleted file mode 100644 index 01ab9ae13b67..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/ClientConfig.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Foundation - -public final class ClientConfig: Swift.Sendable { - public typealias CredentialProvider = @Sendable () async throws -> Swift.String - - struct Defaults { - static let timeout: Swift.Int = 60 - static let maxRetries: Swift.Int = 2 - } - - struct HeaderAuth { - let key: Swift.String - let header: Swift.String - } - - struct BearerAuth { - let token: Token - - enum Token: Swift.Sendable { - case staticToken(Swift.String) - case provider(CredentialProvider) - - func retrieve() async throws -> Swift.String { - switch self { - case .staticToken(let token): - return token - case .provider(let provider): - return try await provider() - } - } - } - } - - struct BasicAuth { - let username: Swift.String? - let password: Swift.String? - - var token: Swift.String? { - if let username, let password { - let credentials: Swift.String = "\(username):\(password)" - let data = credentials.data(using: .utf8) ?? Foundation.Data() - return data.base64EncodedString() - } - return nil - } - } - - let baseURL: Swift.String - let headerAuth: HeaderAuth? - let bearerAuth: BearerAuth? - let basicAuth: BasicAuth? - let headers: [Swift.String: Swift.String]? - let timeout: Swift.Int - let maxRetries: Swift.Int - let urlSession: Networking.URLSession - - init( - baseURL: Swift.String, - headerAuth: HeaderAuth? = nil, - bearerAuth: BearerAuth? = nil, - basicAuth: BasicAuth? = nil, - headers: [Swift.String: Swift.String]? = nil, - timeout: Swift.Int? = nil, - maxRetries: Swift.Int? = nil, - urlSession: Networking.URLSession? = nil - ) { - self.baseURL = baseURL - self.headerAuth = headerAuth - self.bearerAuth = bearerAuth - self.basicAuth = basicAuth - self.headers = headers - self.timeout = timeout ?? Defaults.timeout - self.maxRetries = maxRetries ?? Defaults.maxRetries - self.urlSession = urlSession ?? buildURLSession(timeoutSeconds: self.timeout) - } -} - -private func buildURLSession(timeoutSeconds: Swift.Int) -> Networking.URLSession { - let configuration = Networking.URLSessionConfiguration.default - configuration.timeoutIntervalForRequest = .init(timeoutSeconds) - return .init(configuration: configuration) -} diff --git a/seed/swift-sdk/allof/Sources/Public/FormFile.swift b/seed/swift-sdk/allof/Sources/Public/FormFile.swift deleted file mode 100644 index 4960a15bc000..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/FormFile.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -/// Represents a file with its data and optional metadata for multipart uploads -public struct FormFile { - /// The file data - public let data: Foundation.Data - /// Optional filename - public let filename: Swift.String? - - public init(data: Foundation.Data, filename: Swift.String? = nil) { - self.data = data - self.filename = filename - } -} - -// MARK: - Convenience Initializers - -extension FormFile { - /// Create a FormFile from raw Data with no metadata - /// - Parameter data: The file data - public static func data(_ data: Foundation.Data) -> FormFile { - return FormFile(data: data) - } - - /// Create a FormFile from Data with a filename - /// - Parameters: - /// - data: The file data - /// - filename: The filename - public static func named(_ data: Foundation.Data, filename: Swift.String) -> FormFile { - return FormFile(data: data, filename: filename) - } -} diff --git a/seed/swift-sdk/allof/Sources/Public/HTTPError.swift b/seed/swift-sdk/allof/Sources/Public/HTTPError.swift deleted file mode 100644 index 69a1e2962c60..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/HTTPError.swift +++ /dev/null @@ -1,190 +0,0 @@ -import Foundation - -/// Represents an HTTP error response from the server. -/// -/// This type provides a structured view of non-success HTTP responses, including -/// the status code, an optional parsed error payload and a semantic classification. -public struct HTTPError: Swift.Error, Swift.CustomStringConvertible { - /// The HTTP status code returned by the server. - public let statusCode: Swift.Int - - /// Parsed error payload returned by the server, if any. - public let body: ResponseBody? - - /// Semantic classification of the error based on the status code. - public let kind: Kind - - public init( - statusCode: Swift.Int, - body: ResponseBody?, - kind: Kind - ) { - self.statusCode = statusCode - self.body = body - self.kind = kind - } - - // MARK: - Description - - public var description: Swift.String { - localizedDescription - } - - public var localizedDescription: Swift.String { - let defaultPrefix: Swift.String - switch kind { - case .redirect: - defaultPrefix = "Redirect error" - case .unauthorized: - defaultPrefix = "Unauthorized" - case .forbidden: - defaultPrefix = "Forbidden" - case .notFound: - defaultPrefix = "Not found" - case .client: - defaultPrefix = "Client error" - case .validation: - defaultPrefix = "Validation error" - case .serviceUnavailable: - defaultPrefix = "Service unavailable" - case .server: - defaultPrefix = "Server error" - case .other: - defaultPrefix = "HTTP error" - } - - guard let body = body else { - return "\(defaultPrefix) (Code: \(statusCode))" - } - - var message = defaultPrefix - - // Use server message if available and meaningful. - if let serverMessage = body.message, !serverMessage.isEmpty { - message = serverMessage - } - - var details = "Code: \(body.code)" - if let type = body.type, !type.isEmpty { - details += ", Type: \(type)" - } - - return "\(message) (\(details))" - } - - // MARK: - Factories - - /// Builds an ``HTTPError`` from an HTTP status code and raw response data. - /// - /// The mapping from status code to ``Kind`` is: - /// - `300...399` → `.redirect` - /// - `401` → `.unauthorized` - /// - `403` → `.forbidden` - /// - `404` → `.notFound` - /// - `422` → `.validation` - /// - `400...499` → `.client` - /// - `503` → `.serviceUnavailable` - /// - `500...599` → `.server` - /// - anything else → `.other` - public static func from( - statusCode: Swift.Int, - data: Foundation.Data, - jsonDecoder: Foundation.JSONDecoder - ) -> HTTPError { - let parsedBody = ResponseBody.decode( - statusCode: statusCode, - data: data, - using: jsonDecoder - ) - - let kind: Kind - switch statusCode { - case 300..<400: - kind = .redirect - case 401: - kind = .unauthorized - case 403: - kind = .forbidden - case 404: - kind = .notFound - case 422: - kind = .validation - case 503: - kind = .serviceUnavailable - case 400..<500: - kind = .client - case 500..<600: - kind = .server - default: - kind = .other - } - - return HTTPError(statusCode: statusCode, body: parsedBody, kind: kind) - } - - public enum Kind { - /// 3xx responses. - case redirect - /// 401 responses. - case unauthorized - /// 403 responses. - case forbidden - /// 404 responses. - case notFound - /// Other 4xx responses. - case client - /// 422 responses. - case validation - /// 503 responses. - case serviceUnavailable - /// Other 5xx responses. - case server - /// Any other non-success status code. - case other - } - - /// A best-effort representation of an error payload returned by an API. - /// - /// This type is intentionally minimal and is populated from a variety of possible - /// response body formats (typed JSON, loose JSON with a `"message"` field, or plain text). - public struct ResponseBody: Swift.Codable, Swift.Sendable { - public let code: Swift.Int - public let type: Swift.String? - public let message: Swift.String? - - /// Attempts to decode an `HTTPError.ResponseBody` from the given HTTP response payload. - public static func decode( - statusCode: Swift.Int, - data: Foundation.Data, - using jsonDecoder: Foundation.JSONDecoder - ) -> ResponseBody? { - // Try to parse as a strongly-typed error response first - if let errorResponse = try? jsonDecoder.decode(ResponseBody.self, from: data) { - return errorResponse - } - - // Try to parse as a simple JSON object with a "message" field - if let json = try? Foundation.JSONSerialization.jsonObject(with: data) - as? [Swift.String: Any], - let message = json["message"] as? Swift.String - { - return ResponseBody(code: statusCode, message: message) - } - - // Try to parse as plain text - if let errorMessage: String = Swift.String(data: data, encoding: .utf8), - !errorMessage.isEmpty - { - return ResponseBody(code: statusCode, message: errorMessage) - } - - return nil - } - - public init(code: Swift.Int, type: Swift.String? = nil, message: Swift.String? = nil) { - self.code = code - self.type = type - self.message = message - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Public/Indirect.swift b/seed/swift-sdk/allof/Sources/Public/Indirect.swift deleted file mode 100644 index ec6aa924fc8c..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/Indirect.swift +++ /dev/null @@ -1,27 +0,0 @@ -public final class Indirect: Codable, @unchecked Sendable { - public let value: T - - public init(_ value: T) { - self.value = value - } - - public convenience init(from decoder: Decoder) throws { - self.init(try T(from: decoder)) - } - - public func encode(to encoder: Encoder) throws { - try value.encode(to: encoder) - } -} - -extension Indirect: Equatable where T: Equatable { - public static func == (lhs: Indirect, rhs: Indirect) -> Bool { - lhs.value == rhs.value - } -} - -extension Indirect: Hashable where T: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(value) - } -} diff --git a/seed/swift-sdk/allof/Sources/Public/JSONValue.swift b/seed/swift-sdk/allof/Sources/Public/JSONValue.swift deleted file mode 100644 index f8c56db4debf..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/JSONValue.swift +++ /dev/null @@ -1,136 +0,0 @@ -import Foundation - -/// A type that can represent any JSON value. -public enum JSONValue: Swift.Codable, Swift.Hashable, Swift.Sendable { - case string(Swift.String) - case number(Swift.Double) - case bool(Swift.Bool) - case null - case array([JSONValue]) - case object([Swift.String: JSONValue]) - - public init(from decoder: Swift.Decoder) throws { - let container = try decoder.singleValueContainer() - - if container.decodeNil() { - self = .null - } else if let bool = try? container.decode(Swift.Bool.self) { - self = .bool(bool) - } else if let int = try? container.decode(Swift.Int.self) { - self = .number(Swift.Double(int)) - } else if let double = try? container.decode(Swift.Double.self) { - self = .number(double) - } else if let string = try? container.decode(Swift.String.self) { - self = .string(string) - } else if let array = try? container.decode([JSONValue].self) { - self = .array(array) - } else if let object = try? container.decode([Swift.String: JSONValue].self) { - self = .object(object) - } else { - throw Swift.DecodingError.dataCorrupted( - Swift.DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unable to decode JSONValue" - ) - ) - } - } - - public func encode(to encoder: Swift.Encoder) throws { - var container = encoder.singleValueContainer() - - switch self { - case .string(let string): - try container.encode(string) - case .number(let number): - try container.encode(number) - case .bool(let bool): - try container.encode(bool) - case .null: - try container.encodeNil() - case .array(let array): - try container.encode(array) - case .object(let object): - try container.encode(object) - } - } -} - -// MARK: - Convenience initializers -extension JSONValue { - public init(_ value: String) { - self = .string(value) - } - - public init(_ value: Int) { - self = .number(Double(value)) - } - - public init(_ value: Double) { - self = .number(value) - } - - public init(_ value: Bool) { - self = .bool(value) - } - - public init(_ value: [JSONValue]) { - self = .array(value) - } - - public init(_ value: [String: JSONValue]) { - self = .object(value) - } -} - -// MARK: - Value extraction -extension JSONValue { - public var stringValue: String? { - if case .string(let value) = self { - return value - } - return nil - } - - public var numberValue: Double? { - if case .number(let value) = self { - return value - } - return nil - } - - public var intValue: Int? { - if case .number(let value) = self { - return Int(value) - } - return nil - } - - public var boolValue: Bool? { - if case .bool(let value) = self { - return value - } - return nil - } - - public var arrayValue: [JSONValue]? { - if case .array(let value) = self { - return value - } - return nil - } - - public var objectValue: [String: JSONValue]? { - if case .object(let value) = self { - return value - } - return nil - } - - public var isNull: Bool { - if case .null = self { - return true - } - return false - } -} diff --git a/seed/swift-sdk/allof/Sources/Public/Networking.swift b/seed/swift-sdk/allof/Sources/Public/Networking.swift deleted file mode 100644 index ad54acdcf5fb..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/Networking.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -public enum Networking { - #if canImport(FoundationNetworking) - public typealias URLSession = FoundationNetworking.URLSession - public typealias URLSessionConfiguration = FoundationNetworking.URLSessionConfiguration - public typealias URLProtocol = FoundationNetworking.URLProtocol - public typealias URLRequest = FoundationNetworking.URLRequest - public typealias HTTPURLResponse = FoundationNetworking.HTTPURLResponse - #else - public typealias URLSession = Foundation.URLSession - public typealias URLSessionConfiguration = Foundation.URLSessionConfiguration - public typealias URLProtocol = Foundation.URLProtocol - public typealias URLRequest = Foundation.URLRequest - public typealias HTTPURLResponse = Foundation.HTTPURLResponse - #endif -} diff --git a/seed/swift-sdk/allof/Sources/Public/Nullable.swift b/seed/swift-sdk/allof/Sources/Public/Nullable.swift deleted file mode 100644 index 81c92d7b82c6..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/Nullable.swift +++ /dev/null @@ -1,59 +0,0 @@ -import Foundation - -/// Represents a value that can be either a concrete value or explicit `null`, distinguishing between null and missing fields in JSON. -public enum Nullable: Swift.Codable, Swift.Hashable, Swift.Sendable -where Wrapped: Swift.Codable & Swift.Hashable & Swift.Sendable { - case value(Wrapped) - case null - - public init(from decoder: Swift.Decoder) throws { - let container = try decoder.singleValueContainer() - - if container.decodeNil() { - self = .null - } else { - let wrappedValue = try container.decode(Wrapped.self) - self = .value(wrappedValue) - } - } - - public func encode(to encoder: Swift.Encoder) throws { - var container = encoder.singleValueContainer() - - switch self { - case .value(let wrapped): - try container.encode(wrapped) - case .null: - try container.encodeNil() - } - } - - /// Returns the wrapped value if present, otherwise nil - public var wrappedValue: Wrapped? { - switch self { - case .value(let wrapped): - return wrapped - case .null: - return nil - } - } - - /// Returns true if this contains an explicit null value - public var isNull: Swift.Bool { - switch self { - case .value(_): - return false - case .null: - return true - } - } - - /// Convenience initializer from optional value - public init(_ value: Wrapped?) { - if let value = value { - self = .value(value) - } else { - self = .null - } - } -} diff --git a/seed/swift-sdk/allof/Sources/Public/RequestOptions.swift b/seed/swift-sdk/allof/Sources/Public/RequestOptions.swift deleted file mode 100644 index 3f210d8cf887..000000000000 --- a/seed/swift-sdk/allof/Sources/Public/RequestOptions.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation - -/// Options for customizing an individual API request. -/// -/// Use this struct to override or supplement client-wide configuration for a single request. -public struct RequestOptions { - /// The API key to use for this request, overriding the client-wide API key if provided. - let apiKey: Swift.String? - - /// The token to use for this request, overriding the client-wide token if provided. - let token: Swift.String? - - /// The number of seconds to await an API call before timing out. If `nil`, uses the client or system default. - let timeout: Swift.Int? - - /// The number of times to retry a failed API call. If `nil`, uses the client or system default. - let maxRetries: Swift.Int? - - /// Additional HTTP headers to include with this request. These can override or supplement client-wide headers. - let additionalHeaders: [Swift.String: Swift.String]? - - /// Additional query parameters to include in the request URL. These are merged with any parameters generated from the request model. - let additionalQueryParameters: [Swift.String: Swift.String]? - - /// Additional body parameters to include in the request payload. These are merged with any parameters generated from the request model. - let additionalBodyParameters: [Swift.String: Swift.String]? - - public init( - apiKey: Swift.String? = nil, - token: Swift.String? = nil, - timeout: Swift.Int? = nil, - maxRetries: Swift.Int? = nil, - additionalHeaders: [Swift.String: Swift.String]? = nil, - additionalQueryParameters: [Swift.String: Swift.String]? = nil, - additionalBodyParameters: [Swift.String: Swift.String]? = nil - ) { - self.apiKey = apiKey - self.token = token - self.timeout = timeout - self.maxRetries = maxRetries - self.additionalHeaders = additionalHeaders - self.additionalQueryParameters = additionalQueryParameters - self.additionalBodyParameters = additionalBodyParameters - } -} diff --git a/seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift b/seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift deleted file mode 100644 index 652324b903b1..000000000000 --- a/seed/swift-sdk/allof/Sources/Requests/Requests+RuleCreateRequest.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -extension Requests { - public struct RuleCreateRequest: Codable, Hashable, Sendable { - public let name: String - public let executionContext: RuleExecutionContext - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - name: String, - executionContext: RuleExecutionContext, - additionalProperties: [String: JSONValue] = .init() - ) { - self.name = name - self.executionContext = executionContext - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.executionContext = try container.decode(RuleExecutionContext.self, forKey: .executionContext) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.name, forKey: .name) - try container.encode(self.executionContext, forKey: .executionContext) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case name - case executionContext - } - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Requests/Requests.swift b/seed/swift-sdk/allof/Sources/Requests/Requests.swift deleted file mode 100644 index 2b06437b8213..000000000000 --- a/seed/swift-sdk/allof/Sources/Requests/Requests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -/// Container for all inline request types used throughout the SDK. -/// -/// This enum serves as a namespace to organize request types that are defined inline within endpoint specifications. -public enum Requests {} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift b/seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift deleted file mode 100644 index e2d2dca164b6..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/AuditInfo.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation - -/// Common audit metadata. -public struct AuditInfo: Codable, Hashable, Sendable { - /// The user who created this resource. - public let createdBy: String? - /// When this resource was created. - public let createdDateTime: Date? - /// The user who last modified this resource. - public let modifiedBy: String? - /// When this resource was last modified. - public let modifiedDateTime: Date? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - createdBy: String? = nil, - createdDateTime: Date? = nil, - modifiedBy: String? = nil, - modifiedDateTime: Date? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.createdBy = createdBy - self.createdDateTime = createdDateTime - self.modifiedBy = modifiedBy - self.modifiedDateTime = modifiedDateTime - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) - self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) - self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) - self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.createdBy, forKey: .createdBy) - try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) - try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) - try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case createdBy - case createdDateTime - case modifiedBy - case modifiedDateTime - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift b/seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift deleted file mode 100644 index 826006803d13..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/BaseOrg.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -public struct BaseOrg: Codable, Hashable, Sendable { - public let id: String - public let metadata: BaseOrgMetadata? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - metadata: BaseOrgMetadata? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.metadata = metadata - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.metadata = try container.decodeIfPresent(BaseOrgMetadata.self, forKey: .metadata) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.metadata, forKey: .metadata) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case metadata - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift b/seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift deleted file mode 100644 index 8b76b582c6ff..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/BaseOrgMetadata.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct BaseOrgMetadata: Codable, Hashable, Sendable { - /// Deployment region from BaseOrg. - public let region: String - /// Subscription tier. - public let tier: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - region: String, - tier: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.region = region - self.tier = tier - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.region = try container.decode(String.self, forKey: .region) - self.tier = try container.decodeIfPresent(String.self, forKey: .tier) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.region, forKey: .region) - try container.encodeIfPresent(self.tier, forKey: .tier) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case region - case tier - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift b/seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift deleted file mode 100644 index fe35e084034d..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/CombinedEntity.swift +++ /dev/null @@ -1,53 +0,0 @@ -import Foundation - -public struct CombinedEntity: Codable, Hashable, Sendable { - public let status: CombinedEntityStatus - /// Unique identifier. - public let id: String - /// Display name from Identifiable. - public let name: String? - /// A short summary. - public let summary: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - status: CombinedEntityStatus, - id: String, - name: String? = nil, - summary: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.status = status - self.id = id - self.name = name - self.summary = summary - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.status = try container.decode(CombinedEntityStatus.self, forKey: .status) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decodeIfPresent(String.self, forKey: .name) - self.summary = try container.decodeIfPresent(String.self, forKey: .summary) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.status, forKey: .status) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.name, forKey: .name) - try container.encodeIfPresent(self.summary, forKey: .summary) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case status - case id - case name - case summary - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift b/seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift deleted file mode 100644 index ad507eb4b537..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/CombinedEntityStatus.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -public enum CombinedEntityStatus: String, Codable, Hashable, CaseIterable, Sendable { - case active - case archived -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/Describable.swift b/seed/swift-sdk/allof/Sources/Schemas/Describable.swift deleted file mode 100644 index 5f59f3f5a66b..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/Describable.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct Describable: Codable, Hashable, Sendable { - /// Display name from Describable. - public let name: String? - /// A short summary. - public let summary: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - name: String? = nil, - summary: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.name = name - self.summary = summary - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) - self.summary = try container.decodeIfPresent(String.self, forKey: .summary) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.name, forKey: .name) - try container.encodeIfPresent(self.summary, forKey: .summary) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case name - case summary - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift b/seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift deleted file mode 100644 index 412a6626a9c5..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/DetailedOrg.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -public struct DetailedOrg: Codable, Hashable, Sendable { - public let metadata: DetailedOrgMetadata? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - metadata: DetailedOrgMetadata? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.metadata = metadata - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.metadata = try container.decodeIfPresent(DetailedOrgMetadata.self, forKey: .metadata) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.metadata, forKey: .metadata) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case metadata - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift b/seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift deleted file mode 100644 index bf4903b3d0ea..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/DetailedOrgMetadata.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct DetailedOrgMetadata: Codable, Hashable, Sendable { - /// Deployment region from DetailedOrg. - public let region: String - /// Custom domain name. - public let domain: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - region: String, - domain: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.region = region - self.domain = domain - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.region = try container.decode(String.self, forKey: .region) - self.domain = try container.decodeIfPresent(String.self, forKey: .domain) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.region, forKey: .region) - try container.encodeIfPresent(self.domain, forKey: .domain) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case region - case domain - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift b/seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift deleted file mode 100644 index c0c54d0a6b65..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/Identifiable.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct Identifiable: Codable, Hashable, Sendable { - /// Unique identifier. - public let id: String - /// Display name from Identifiable. - public let name: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - name: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.name = name - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decodeIfPresent(String.self, forKey: .name) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.name, forKey: .name) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case name - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/Organization.swift b/seed/swift-sdk/allof/Sources/Schemas/Organization.swift deleted file mode 100644 index fe270f91811e..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/Organization.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation - -public struct Organization: Codable, Hashable, Sendable { - public let name: String - public let id: String - public let metadata: BaseOrgMetadata? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - name: String, - id: String, - metadata: BaseOrgMetadata? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.name = name - self.id = id - self.metadata = metadata - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.id = try container.decode(String.self, forKey: .id) - self.metadata = try container.decodeIfPresent(BaseOrgMetadata.self, forKey: .metadata) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.name, forKey: .name) - try container.encode(self.id, forKey: .id) - try container.encodeIfPresent(self.metadata, forKey: .metadata) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case name - case id - case metadata - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift b/seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift deleted file mode 100644 index 60920a570d88..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/PaginatedResult.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation - -public struct PaginatedResult: Codable, Hashable, Sendable { - public let paging: PagingCursors - /// Current page of results from the requested resource. - public let results: [JSONValue] - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - paging: PagingCursors, - results: [JSONValue], - additionalProperties: [String: JSONValue] = .init() - ) { - self.paging = paging - self.results = results - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.paging = try container.decode(PagingCursors.self, forKey: .paging) - self.results = try container.decode([JSONValue].self, forKey: .results) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.paging, forKey: .paging) - try container.encode(self.results, forKey: .results) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case paging - case results - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift b/seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift deleted file mode 100644 index a6c6369f7a4e..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/PagingCursors.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -public struct PagingCursors: Codable, Hashable, Sendable { - /// Cursor for the next page of results. - public let next: String - /// Cursor for the previous page of results. - public let previous: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - next: String, - previous: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.next = next - self.previous = previous - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.next = try container.decode(String.self, forKey: .next) - self.previous = try container.decodeIfPresent(String.self, forKey: .previous) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.next, forKey: .next) - try container.encodeIfPresent(self.previous, forKey: .previous) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case next - case previous - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift deleted file mode 100644 index b3825cc062d2..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/RuleExecutionContext.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -/// Execution environment for a rule. -public enum RuleExecutionContext: String, Codable, Hashable, CaseIterable, Sendable { - case prod - case staging - case dev -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift deleted file mode 100644 index 23834241d4e1..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/RuleResponse.swift +++ /dev/null @@ -1,78 +0,0 @@ -import Foundation - -public struct RuleResponse: Codable, Hashable, Sendable { - /// The user who created this resource. - public let createdBy: String? - /// When this resource was created. - public let createdDateTime: Date? - /// The user who last modified this resource. - public let modifiedBy: String? - /// When this resource was last modified. - public let modifiedDateTime: Date? - public let id: String - public let name: String - public let status: RuleResponseStatus - public let executionContext: RuleExecutionContext? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - createdBy: String? = nil, - createdDateTime: Date? = nil, - modifiedBy: String? = nil, - modifiedDateTime: Date? = nil, - id: String, - name: String, - status: RuleResponseStatus, - executionContext: RuleExecutionContext? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.createdBy = createdBy - self.createdDateTime = createdDateTime - self.modifiedBy = modifiedBy - self.modifiedDateTime = modifiedDateTime - self.id = id - self.name = name - self.status = status - self.executionContext = executionContext - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.createdBy = try container.decodeIfPresent(String.self, forKey: .createdBy) - self.createdDateTime = try container.decodeIfPresent(Date.self, forKey: .createdDateTime) - self.modifiedBy = try container.decodeIfPresent(String.self, forKey: .modifiedBy) - self.modifiedDateTime = try container.decodeIfPresent(Date.self, forKey: .modifiedDateTime) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decode(String.self, forKey: .name) - self.status = try container.decode(RuleResponseStatus.self, forKey: .status) - self.executionContext = try container.decodeIfPresent(RuleExecutionContext.self, forKey: .executionContext) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.createdBy, forKey: .createdBy) - try container.encodeIfPresent(self.createdDateTime, forKey: .createdDateTime) - try container.encodeIfPresent(self.modifiedBy, forKey: .modifiedBy) - try container.encodeIfPresent(self.modifiedDateTime, forKey: .modifiedDateTime) - try container.encode(self.id, forKey: .id) - try container.encode(self.name, forKey: .name) - try container.encode(self.status, forKey: .status) - try container.encodeIfPresent(self.executionContext, forKey: .executionContext) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case createdBy - case createdDateTime - case modifiedBy - case modifiedDateTime - case id - case name - case status - case executionContext - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift deleted file mode 100644 index 1f8cbe93a34c..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/RuleResponseStatus.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -public enum RuleResponseStatus: String, Codable, Hashable, CaseIterable, Sendable { - case active - case inactive - case draft -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleType.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleType.swift deleted file mode 100644 index 89987c5def90..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/RuleType.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation - -public struct RuleType: Codable, Hashable, Sendable { - public let id: String - public let name: String - public let description: String? - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - name: String, - description: String? = nil, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.name = name - self.description = description - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.name = try container.decode(String.self, forKey: .name) - self.description = try container.decodeIfPresent(String.self, forKey: .description) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encode(self.name, forKey: .name) - try container.encodeIfPresent(self.description, forKey: .description) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case name - case description - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift b/seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift deleted file mode 100644 index 47a4aae72f09..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/RuleTypeSearchResponse.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation - -public struct RuleTypeSearchResponse: Codable, Hashable, Sendable { - /// Current page of results from the requested resource. - public let results: [RuleType]? - public let paging: PagingCursors - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - results: [RuleType]? = nil, - paging: PagingCursors, - additionalProperties: [String: JSONValue] = .init() - ) { - self.results = results - self.paging = paging - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.results = try container.decodeIfPresent([RuleType].self, forKey: .results) - self.paging = try container.decode(PagingCursors.self, forKey: .paging) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.results, forKey: .results) - try container.encode(self.paging, forKey: .paging) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case results - case paging - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/User.swift b/seed/swift-sdk/allof/Sources/Schemas/User.swift deleted file mode 100644 index 3e9a5ec45166..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/User.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -public struct User: Codable, Hashable, Sendable { - public let id: String - public let email: String - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - id: String, - email: String, - additionalProperties: [String: JSONValue] = .init() - ) { - self.id = id - self.email = email - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.email = try container.decode(String.self, forKey: .email) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encode(self.id, forKey: .id) - try container.encode(self.email, forKey: .email) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case id - case email - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift b/seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift deleted file mode 100644 index 079620fbcf3f..000000000000 --- a/seed/swift-sdk/allof/Sources/Schemas/UserSearchResponse.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation - -public struct UserSearchResponse: Codable, Hashable, Sendable { - /// Current page of results from the requested resource. - public let results: [User]? - public let paging: PagingCursors - /// Additional properties that are not explicitly defined in the schema - public let additionalProperties: [String: JSONValue] - - public init( - results: [User]? = nil, - paging: PagingCursors, - additionalProperties: [String: JSONValue] = .init() - ) { - self.results = results - self.paging = paging - self.additionalProperties = additionalProperties - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.results = try container.decodeIfPresent([User].self, forKey: .results) - self.paging = try container.decode(PagingCursors.self, forKey: .paging) - self.additionalProperties = try decoder.decodeAdditionalProperties(using: CodingKeys.self) - } - - public func encode(to encoder: Encoder) throws -> Void { - var container = encoder.container(keyedBy: CodingKeys.self) - try encoder.encodeAdditionalProperties(self.additionalProperties) - try container.encodeIfPresent(self.results, forKey: .results) - try container.encode(self.paging, forKey: .paging) - } - - /// Keys for encoding/decoding struct properties. - enum CodingKeys: String, CodingKey, CaseIterable { - case results - case paging - } -} \ No newline at end of file diff --git a/seed/swift-sdk/allof/Sources/Version.swift b/seed/swift-sdk/allof/Sources/Version.swift deleted file mode 100644 index b17ac8c77973..000000000000 --- a/seed/swift-sdk/allof/Sources/Version.swift +++ /dev/null @@ -1 +0,0 @@ -public let sdkVersion = "0.0.1" diff --git a/seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift b/seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift deleted file mode 100644 index 63de33596c7b..000000000000 --- a/seed/swift-sdk/allof/Tests/Core/ClientErrorTests.swift +++ /dev/null @@ -1,222 +0,0 @@ -import Api -import Foundation -import Testing - -@Suite("Client Error & HTTP Error Tests") struct ClientErrorTests { - // MARK: - 4xx client errors - - @Test func testClientErrorFor400BadRequest() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 400, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Bad request"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 400) - try #require(httpError.kind == .client) - try #require(httpError.body?.message == "Bad request") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorFor404NotFound() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 404, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Not found"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 404) - try #require(httpError.kind == .notFound) - try #require(httpError.body?.message == "Not found") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorFor422ValidationError() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 422, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Validation failed"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 422) - try #require(httpError.kind == .validation) - try #require(httpError.body?.message == "Validation failed") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - // MARK: - 5xx server errors - - @Test func testClientErrorFor500ServerError() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 500, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Internal error"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 500) - try #require(httpError.kind == .server) - try #require(httpError.body?.message == "Internal error") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorFor503ServiceUnavailable() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 503, - headers: ["Content-Type": "application/json"], - body: Data(#"{"message":"Unavailable"}"#.utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 503) - try #require(httpError.kind == .serviceUnavailable) - try #require(httpError.body?.message == "Unavailable") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - // MARK: - 3xx redirect & plain-text bodies - - @Test func testClientErrorFor302RedirectNoBody() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 302, - headers: ["Location": "https://example.com"], - body: Data() - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 302) - try #require(httpError.kind == .redirect) - try #require(httpError.body == nil) - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } - - @Test func testClientErrorPlainTextBodyIsDecoded() async throws { - let stub = HTTPStub() - stub.setResponse( - statusCode: 500, - headers: ["Content-Type": "text/plain"], - body: Data("Plain text error".utf8) - ) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch let error as ApiError { - guard case .httpError(let httpError) = error else { - Issue.record("Expected ApiError.httpError, got \(error)") - return - } - try #require(httpError.statusCode == 500) - try #require(httpError.kind == .server) - try #require(httpError.body?.message == "Plain text error") - } catch { - Issue.record("Expected ApiError, got \(error)") - } - } -} - diff --git a/seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift b/seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift deleted file mode 100644 index 8ebbf4c73e22..000000000000 --- a/seed/swift-sdk/allof/Tests/Core/ClientRetryTests.swift +++ /dev/null @@ -1,355 +0,0 @@ -import Api -import Foundation -import Testing - -@Suite("Client Retry Tests") struct ClientRetryTests { - @Test func testRetryOn408RequestTimeout() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 408, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 3) - } - - @Test func testRetryOn429TooManyRequests() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 429, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 3) - } - - @Test func testRetryOn500InternalServerError() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 3) - } - - @Test func testRetryOn503ServiceUnavailable() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 503, headers: ["Content-Type": "application/json"], body: Data()), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 2) - } - - @Test func testNoRetryOn400BadRequest() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 400, headers: ["Content-Type": "application/json"], - body: Data("{\"errorName\":\"BadRequest\"}".utf8) - ) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 1) - } - } - - @Test func testNoRetryOn404NotFound() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 404, headers: ["Content-Type": "application/json"], - body: Data("{\"errorName\":\"NotFound\"}".utf8) - ) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 1) - } - } - - @Test func testMaxRetriesExhausted() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 3) - } - } - - @Test func testRetryAfterHeaderWithSeconds() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 429, headers: ["Content-Type": "application/json", "Retry-After": "1"], - body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - let startTime = Date() - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - let elapsed = Date().timeIntervalSince(startTime) - - try #require(stub.getRequestCount() == 2) - try #require(elapsed >= 1.0) - } - - @Test func testRetryAfterHeaderWithHTTPDate() async throws { - let stub = HTTPStub() - let futureEpoch = ceil(Date().timeIntervalSince1970) + 1.0 - let futureDate = Date(timeIntervalSince1970: futureEpoch) - let formatter = DateFormatter() - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(abbreviation: "GMT") - formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" - let httpDate = formatter.string(from: futureDate) - - stub.setResponseSequence([ - ( - statusCode: 429, - headers: ["Content-Type": "application/json", "Retry-After": httpDate], body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - let startTime = Date() - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - let elapsed = Date().timeIntervalSince(startTime) - - try #require(stub.getRequestCount() == 2) - try #require(elapsed >= 0.9) - } - - @Test func testXRateLimitResetHeader() async throws { - let stub = HTTPStub() - let futureTimestamp = Int(ceil(Date().timeIntervalSince1970)) + 1 - - stub.setResponseSequence([ - ( - statusCode: 429, - headers: [ - "Content-Type": "application/json", "X-RateLimit-Reset": "\(futureTimestamp)", - ], body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - let startTime = Date() - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - let elapsed = Date().timeIntervalSince(startTime) - - try #require(stub.getRequestCount() == 2) - try #require(elapsed >= 0.9) - } - - @Test func testEndpointLevelMaxRetriesOverride() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 500, - headers: ["Content-Type": "application/json", "Retry-After": "0.1"], body: Data() - ), - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ), - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 5, additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 6) - } - - @Test func testEndpointLevelMaxRetriesZero() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - (statusCode: 500, headers: ["Content-Type": "application/json"], body: Data()) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(maxRetries: 0, additionalHeaders: stub.headers)) - - Issue.record("Expected error to be thrown") - } catch { - try #require(stub.getRequestCount() == 1) - } - } - - @Test func testSuccessOnFirstAttempt() async throws { - let stub = HTTPStub() - stub.setResponseSequence([ - ( - statusCode: 200, headers: ["Content-Type": "application/json"], - body: Data("true".utf8) - ) - ]) - - let client = ApiClient( - baseURL: "https://api.fern.com", - urlSession: stub.urlSession - ) - - do { - _ = try await client.searchRuleTypes(requestOptions: RequestOptions(additionalHeaders: stub.headers)) - - } catch { - } - try #require(stub.getRequestCount() == 1) - } -} diff --git a/seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift b/seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift deleted file mode 100644 index 82a3aa80ad22..000000000000 --- a/seed/swift-sdk/allof/Tests/Utilities/HTTPStub.swift +++ /dev/null @@ -1,317 +0,0 @@ -import Api -import Foundation - -final class HTTPStub { - private static func buildURLSession(stubId: String, operationQueue: OperationQueue) - -> Networking.URLSession - { - let config = buildURLSessionConfiguration(stubId: stubId) - if let uuid = UUID(uuidString: stubId) { - StubURLProtocol.register(queue: operationQueue, id: uuid) - } - return Networking.URLSession(configuration: config, delegate: nil, delegateQueue: operationQueue) - } - - private static func buildURLSessionConfiguration(stubId: String) -> Networking.URLSessionConfiguration { - let config = Networking.URLSessionConfiguration.ephemeral - config.protocolClasses = [StubURLProtocol.self] - config.requestCachePolicy = .reloadIgnoringLocalCacheData - config.urlCache = nil - config.httpAdditionalHeaders = ["Stub-ID": stubId] - return config - } - - private static func buildOperationQueue() -> OperationQueue { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - queue.qualityOfService = .userInitiated - return queue - } - - private let session: Networking.URLSession - private let delegateQueue: OperationQueue - private let identifier: UUID - - init() { - self.identifier = UUID() - self.delegateQueue = Self.buildOperationQueue() - self.session = Self.buildURLSession( - stubId: identifier.uuidString, operationQueue: delegateQueue) - #if !canImport(Darwin) - // On Linux, URLProtocol doesn't get the additional headers at canInit time. - // Track the active stub id so the protocol can resolve responses without headers. - StubURLProtocol.setActiveStubId(identifier) - #endif - } - - var urlSession: Networking.URLSession { - session - } - - var headers: [String: String] { - ["Stub-ID": identifier.uuidString] - } - - func setResponse( - statusCode: Int = 200, - headers: [String: String] = ["Content-Type": "application/json"], - body: Data - ) { - StubURLProtocol.configure( - id: identifier, - statusCode: statusCode, - headers: headers, - body: body - ) - } - - func setResponseSequence( - _ responses: [(statusCode: Int, headers: [String: String], body: Data)] - ) { - StubURLProtocol.configureSequence(id: identifier, responses: responses) - } - - func takeLastRequest() -> Networking.URLRequest? { - StubURLProtocol.takeLastRequest(for: identifier) - } - - func getRequestCount() -> Int { - StubURLProtocol.getRequestCount(for: identifier) - } - - deinit { - StubURLProtocol.reset(id: identifier) - StubURLProtocol.deregister(queue: delegateQueue) - #if !canImport(Darwin) - StubURLProtocol.clearActiveStubId(identifier) - #endif - } -} - -private final class StubURLProtocol: Networking.URLProtocol { - struct Response { - let statusCode: Int - let headers: [String: String] - let body: Data - var lastRequest: Networking.URLRequest? - } - - struct ResponseSequence { - var responses: [Response] - var currentIndex: Int = 0 - var lastRequest: Networking.URLRequest? - - mutating func nextResponse() -> Response? { - guard currentIndex < responses.count else { return nil } - let response = responses[currentIndex] - currentIndex += 1 - return response - } - } - - private static var responses: [UUID: Response] = [:] - private static var responseSequences: [UUID: ResponseSequence] = [:] - private static let lock = NSLock() - private static var queueIdMap: [ObjectIdentifier: UUID] = [:] - #if !canImport(Darwin) - // Fallback for Linux where request headers may not be visible in canInit. - private static var activeStubIds: [UUID] = [] - #endif - - static func configure( - id: UUID, - statusCode: Int, - headers: [String: String], - body: Data - ) { - lock.lock() - responses[id] = Response( - statusCode: statusCode, headers: headers, body: body, lastRequest: nil) - responseSequences[id] = nil - lock.unlock() - } - - static func configureSequence( - id: UUID, - responses: [(statusCode: Int, headers: [String: String], body: Data)] - ) { - lock.lock() - let responseList = responses.map { - Response( - statusCode: $0.statusCode, headers: $0.headers, body: $0.body, lastRequest: nil) - } - responseSequences[id] = ResponseSequence(responses: responseList) - StubURLProtocol.responses[id] = nil - lock.unlock() - } - - static func takeLastRequest(for id: UUID) -> Networking.URLRequest? { - lock.lock() - defer { lock.unlock() } - - if var sequence = responseSequences[id], let request = sequence.lastRequest { - sequence.lastRequest = nil - responseSequences[id] = sequence - return request - } - - guard var response = responses[id], let request = response.lastRequest else { - return nil - } - response.lastRequest = nil - responses[id] = response - return request - } - - static func getRequestCount(for id: UUID) -> Int { - lock.lock() - defer { lock.unlock() } - - if let sequence = responseSequences[id] { - return sequence.currentIndex - } - - return responses[id]?.lastRequest != nil ? 1 : 0 - } - - static func reset(id: UUID) { - lock.lock() - responses[id] = nil - responseSequences[id] = nil - lock.unlock() - } - - static func register(queue: OperationQueue, id: UUID) { - lock.lock() - queueIdMap[ObjectIdentifier(queue)] = id - lock.unlock() - } - - static func deregister(queue: OperationQueue) { - lock.lock() - queueIdMap[ObjectIdentifier(queue)] = nil - lock.unlock() - } - - #if !canImport(Darwin) - static func setActiveStubId(_ id: UUID) { - lock.lock() - activeStubIds.append(id) - lock.unlock() - } - - static func clearActiveStubId(_ id: UUID) { - lock.lock() - if let idx = activeStubIds.lastIndex(of: id) { - activeStubIds.remove(at: idx) - } - lock.unlock() - } - #endif - - override class func canInit(with request: Networking.URLRequest) -> Bool { - #if canImport(Darwin) - return request.value(forHTTPHeaderField: "Stub-ID") != nil - #else - // On Linux, intercept all requests created by the session that installed this protocol. - // We'll resolve the correct stub id during startLoading using the active id stack. - return true - #endif - } - - override class func canonicalRequest(for request: Networking.URLRequest) -> Networking.URLRequest { - request - } - - override func startLoading() { - guard let client else { return } - #if canImport(Darwin) - guard let idValue = request.value(forHTTPHeaderField: "Stub-ID"), - let id = UUID(uuidString: idValue) - else { - client.urlProtocol(self, didFailWithError: URLError(.cannotFindHost)) - return - } - #else - // Prefer the Stub-ID header if available on Linux; fall back to the active id stack. - var resolvedId: UUID? - if let idValue = request.value(forHTTPHeaderField: "Stub-ID"), - let headerId = UUID(uuidString: idValue) - { - resolvedId = headerId - } else { - if let currentQueue = OperationQueue.current { - StubURLProtocol.lock.lock() - if let mapped = StubURLProtocol.queueIdMap[ObjectIdentifier(currentQueue)] { - resolvedId = mapped - } - StubURLProtocol.lock.unlock() - } - StubURLProtocol.lock.lock() - resolvedId = StubURLProtocol.activeStubIds.last - StubURLProtocol.lock.unlock() - } - guard let id = resolvedId else { - client.urlProtocol(self, didFailWithError: URLError(.unknown)) - return - } - #endif - - StubURLProtocol.lock.lock() - - if var sequence = StubURLProtocol.responseSequences[id] { - guard let response = sequence.nextResponse() else { - StubURLProtocol.lock.unlock() - client.urlProtocol(self, didFailWithError: URLError(.unknown)) - return - } - sequence.lastRequest = request - StubURLProtocol.responseSequences[id] = sequence - StubURLProtocol.lock.unlock() - - guard let url = request.url else { - client.urlProtocol(self, didFailWithError: URLError(.badURL)) - return - } - - let httpResponse = Networking.HTTPURLResponse( - url: url, - statusCode: response.statusCode, - httpVersion: "HTTP/1.1", - headerFields: response.headers - )! - - client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) - client.urlProtocol(self, didLoad: response.body) - client.urlProtocolDidFinishLoading(self) - return - } - - guard var response = StubURLProtocol.responses[id] else { - StubURLProtocol.lock.unlock() - client.urlProtocol(self, didFailWithError: URLError(.unknown)) - return - } - response.lastRequest = request - StubURLProtocol.responses[id] = response - StubURLProtocol.lock.unlock() - - guard let url = request.url else { - client.urlProtocol(self, didFailWithError: URLError(.badURL)) - return - } - - let httpResponse = Networking.HTTPURLResponse( - url: url, - statusCode: response.statusCode, - httpVersion: "HTTP/1.1", - headerFields: response.headers - )! - - client.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed) - client.urlProtocol(self, didLoad: response.body) - client.urlProtocolDidFinishLoading(self) - } - - override func stopLoading() {} -} diff --git a/seed/swift-sdk/allof/reference.md b/seed/swift-sdk/allof/reference.md deleted file mode 100644 index 75ea9a025cca..000000000000 --- a/seed/swift-sdk/allof/reference.md +++ /dev/null @@ -1,265 +0,0 @@ -# Reference -
client.searchRuleTypes(query: String?, requestOptions: RequestOptions?) -> RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.searchRuleTypes() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `String?` - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.createRule(request: Requests.RuleCreateRequest, requestOptions: RequestOptions?) -> RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.createRule(request: .init( - name: "name", - executionContext: .prod - )) -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `Requests.RuleCreateRequest` - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.listUsers(requestOptions: RequestOptions?) -> UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.listUsers() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.getEntity(requestOptions: RequestOptions?) -> CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.getEntity() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- -
client.getOrganization(requestOptions: RequestOptions?) -> Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```swift -import Foundation -import Api - -private func main() async throws { - let client = ApiClient() - - _ = try await client.getOrganization() -} - -try await main() -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `RequestOptions?` — Additional options for configuring the request, such as custom headers or timeout settings. - -
-
-
-
- - -
-
-
- diff --git a/seed/swift-sdk/allof/snippet.json b/seed/swift-sdk/allof/snippet.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/ts-sdk/allof-inline/.fern/metadata.json b/seed/ts-sdk/allof-inline/.fern/metadata.json deleted file mode 100644 index 7b69b32c1f15..000000000000 --- a/seed/ts-sdk/allof-inline/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} diff --git a/seed/ts-sdk/allof-inline/.github/workflows/ci.yml b/seed/ts-sdk/allof-inline/.github/workflows/ci.yml deleted file mode 100644 index 93fba226cb67..000000000000 --- a/seed/ts-sdk/allof-inline/.github/workflows/ci.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Set up node - uses: actions/setup-node@v6 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Compile - run: pnpm build - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Set up node - uses: actions/setup-node@v6 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Test - run: pnpm test diff --git a/seed/ts-sdk/allof-inline/.gitignore b/seed/ts-sdk/allof-inline/.gitignore deleted file mode 100644 index 72271e049c02..000000000000 --- a/seed/ts-sdk/allof-inline/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -.DS_Store -/dist \ No newline at end of file diff --git a/seed/ts-sdk/allof-inline/CONTRIBUTING.md b/seed/ts-sdk/allof-inline/CONTRIBUTING.md deleted file mode 100644 index fe5bc2f77e0b..000000000000 --- a/seed/ts-sdk/allof-inline/CONTRIBUTING.md +++ /dev/null @@ -1,133 +0,0 @@ -# Contributing - -Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project. - -## Getting Started - -### Prerequisites - -- Node.js 20 or higher -- pnpm package manager - -### Installation - -Install the project dependencies: - -```bash -pnpm install -``` - -### Building - -Build the project: - -```bash -pnpm build -``` - -### Testing - -Run the test suite: - -```bash -pnpm test -``` - -Run specific test types: -- `pnpm test:unit` - Run unit tests -- `pnpm test:wire` - Run wire/integration tests - -### Linting and Formatting - -Check code style: - -```bash -pnpm run lint -pnpm run format:check -``` - -Fix code style issues: - -```bash -pnpm run lint:fix -pnpm run format:fix -``` - -Or use the combined check command: - -```bash -pnpm run check:fix -``` - -## About Generated Code - -**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated. - -### Generated Files - -The following directories contain generated code: -- `src/api/` - API client classes and types -- `src/serialization/` - Serialization/deserialization logic -- Most TypeScript files in `src/` - -### How to Customize - -If you need to customize the SDK, you have two options: - -#### Option 1: Use `.fernignore` - -For custom code that should persist across SDK regenerations: - -1. Create a `.fernignore` file in the project root -2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax) -3. Add your custom code to those files - -Files listed in `.fernignore` will not be overwritten when the SDK is regenerated. - -For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code). - -#### Option 2: Contribute to the Generator - -If you want to change how code is generated for all users of this SDK: - -1. The TypeScript SDK generator lives in the [Fern repository](https://github.com/fern-api/fern) -2. Generator code is located at `generators/typescript/sdk/` -3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md) -4. Submit a pull request with your changes to the generator - -This approach is best for: -- Bug fixes in generated code -- New features that would benefit all users -- Improvements to code generation patterns - -## Making Changes - -### Workflow - -1. Create a new branch for your changes -2. Make your modifications -3. Run tests to ensure nothing breaks: `pnpm test` -4. Run linting and formatting: `pnpm run check:fix` -5. Build the project: `pnpm build` -6. Commit your changes with a clear commit message -7. Push your branch and create a pull request - -### Commit Messages - -Write clear, descriptive commit messages that explain what changed and why. - -### Code Style - -This project uses automated code formatting and linting. Run `pnpm run check:fix` before committing to ensure your code meets the project's style guidelines. - -## Questions or Issues? - -If you have questions or run into issues: - -1. Check the [Fern documentation](https://buildwithfern.com) -2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues) -3. Open a new issue if your question hasn't been addressed - -## License - -By contributing to this project, you agree that your contributions will be licensed under the same license as the project. diff --git a/seed/ts-sdk/allof-inline/README.md b/seed/ts-sdk/allof-inline/README.md deleted file mode 100644 index 1744d31370b2..000000000000 --- a/seed/ts-sdk/allof-inline/README.md +++ /dev/null @@ -1,292 +0,0 @@ -# Seed TypeScript Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) -[![npm shield](https://img.shields.io/npm/v/@fern/allof-inline)](https://www.npmjs.com/package/@fern/allof-inline) - -The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Request and Response Types](#request-and-response-types) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Aborting Requests](#aborting-requests) - - [Access Raw Response Data](#access-raw-response-data) - - [Logging](#logging) - - [Custom Fetch](#custom-fetch) - - [Runtime Compatibility](#runtime-compatibility) -- [Contributing](#contributing) - -## Installation - -```sh -npm i -s @fern/allof-inline -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```typescript -import { SeedApiClient } from "@fern/allof-inline"; - -const client = new SeedApiClient; -await client.createRule({ - name: "name", - executionContext: "prod" -}); -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```typescript -import { SeedApiClient, SeedApiEnvironment } from "@fern/allof-inline"; - -const client = new SeedApiClient({ - environment: SeedApiEnvironment.Default, -}); -``` - -## Request and Response Types - -The SDK exports all request and response types as TypeScript interfaces. Simply import them with the -following namespace: - -```typescript -import { SeedApi } from "@fern/allof-inline"; - -const request: SeedApi.SearchRuleTypesRequest = { - ... -}; -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```typescript -import { SeedApiError } from "@fern/allof-inline"; - -try { - await client.createRule(...); -} catch (err) { - if (err instanceof SeedApiError) { - console.log(err.statusCode); - console.log(err.message); - console.log(err.body); - console.log(err.rawResponse); - } -} -``` - -## Advanced - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `headers` request option. - -```typescript -import { SeedApiClient } from "@fern/allof-inline"; - -const client = new SeedApiClient({ - ... - headers: { - 'X-Custom-Header': 'custom value' - } -}); - -const response = await client.createRule(..., { - headers: { - 'X-Custom-Header': 'custom value' - } -}); -``` - -### Additional Query String Parameters - -If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. - -```typescript -const response = await client.createRule(..., { - queryParams: { - 'customQueryParamKey': 'custom query param value' - } -}); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` request option to configure this behavior. - -```typescript -const response = await client.createRule(..., { - maxRetries: 0 // override maxRetries at the request level -}); -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. - -```typescript -const response = await client.createRule(..., { - timeoutInSeconds: 30 // override timeout to 30s -}); -``` - -### Aborting Requests - -The SDK allows users to abort requests at any point by passing in an abort signal. - -```typescript -const controller = new AbortController(); -const response = await client.createRule(..., { - abortSignal: controller.signal -}); -controller.abort(); // aborts the request -``` - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. -The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. - -```typescript -const { data, rawResponse } = await client.createRule(...).withRawResponse(); - -console.log(data); -console.log(rawResponse.headers['X-My-Header']); -``` - -### Logging - -The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. - -```typescript -import { SeedApiClient, logging } from "@fern/allof-inline"; - -const client = new SeedApiClient({ - ... - logging: { - level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info - logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger - silent: false, // defaults to true, set to false to enable logging - } -}); -``` -The `logging` object can have the following properties: -- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. -- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. -- `silent`: Whether to silence the logger. Defaults to `true`. - -The `level` property can be one of the following values: -- `logging.LogLevel.Debug` -- `logging.LogLevel.Info` -- `logging.LogLevel.Warn` -- `logging.LogLevel.Error` - -To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. - -
-Custom logger examples - -Here's an example using the popular `winston` logging library. -```ts -import winston from 'winston'; - -const winstonLogger = winston.createLogger({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => winstonLogger.debug(msg, ...args), - info: (msg, ...args) => winstonLogger.info(msg, ...args), - warn: (msg, ...args) => winstonLogger.warn(msg, ...args), - error: (msg, ...args) => winstonLogger.error(msg, ...args), -}; -``` - -Here's an example using the popular `pino` logging library. - -```ts -import pino from 'pino'; - -const pinoLogger = pino({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => pinoLogger.debug(args, msg), - info: (msg, ...args) => pinoLogger.info(args, msg), - warn: (msg, ...args) => pinoLogger.warn(args, msg), - error: (msg, ...args) => pinoLogger.error(args, msg), -}; -``` -
- - -### Custom Fetch - -The SDK provides a low-level `fetch` method for making custom HTTP requests while still -benefiting from SDK-level configuration like authentication, retries, timeouts, and logging. -This is useful for calling API endpoints not yet supported in the SDK. - -```typescript -const response = await client.fetch("/v1/custom/endpoint", { - method: "GET", -}, { - timeoutInSeconds: 30, - maxRetries: 3, - headers: { - "X-Custom-Header": "custom-value", - }, -}); - -const data = await response.json(); -``` - -### Runtime Compatibility - - -The SDK works in the following runtimes: - - - -- Node.js 18+ -- Vercel -- Cloudflare Workers -- Deno v1.25+ -- Bun 1.0+ -- React Native - - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/allof-inline/biome.json b/seed/ts-sdk/allof-inline/biome.json deleted file mode 100644 index 6b89164f9f99..000000000000 --- a/seed/ts-sdk/allof-inline/biome.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", - "root": true, - "vcs": { - "enabled": false - }, - "files": { - "ignoreUnknown": true, - "includes": [ - "**", - "!!dist", - "!!**/dist", - "!!lib", - "!!**/lib", - "!!_tmp_*", - "!!**/_tmp_*", - "!!*.tmp", - "!!**/*.tmp", - "!!.tmp/", - "!!**/.tmp/", - "!!*.log", - "!!**/*.log", - "!!**/.DS_Store", - "!!**/Thumbs.db" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 4, - "lineWidth": 120 - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - }, - "linter": { - "rules": { - "style": { - "useNodejsImportProtocol": "off" - }, - "suspicious": { - "noAssignInExpressions": "warn", - "noUselessEscapeInString": { - "level": "warn", - "fix": "none", - "options": {} - }, - "noThenProperty": "warn", - "useIterableCallbackReturn": "warn", - "noShadowRestrictedNames": "warn", - "noTsIgnore": { - "level": "warn", - "fix": "none", - "options": {} - }, - "noConfusingVoidType": { - "level": "warn", - "fix": "none", - "options": {} - } - } - } - } -} diff --git a/seed/ts-sdk/allof-inline/package.json b/seed/ts-sdk/allof-inline/package.json deleted file mode 100644 index ade4e972fce8..000000000000 --- a/seed/ts-sdk/allof-inline/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@fern/allof-inline", - "version": "0.0.1", - "private": false, - "repository": { - "type": "git", - "url": "git+https://github.com/allof-inline/fern.git" - }, - "type": "commonjs", - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.mjs", - "types": "./dist/cjs/index.d.ts", - "exports": { - ".": { - "import": { - "types": "./dist/esm/index.d.mts", - "default": "./dist/esm/index.mjs" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - }, - "default": "./dist/cjs/index.js" - }, - "./package.json": "./package.json" - }, - "files": [ - "dist", - "reference.md", - "README.md", - "LICENSE" - ], - "scripts": { - "format": "biome format --write --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "format:check": "biome format --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "lint": "biome lint --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "lint:fix": "biome lint --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "check": "biome check --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "check:fix": "biome check --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "build": "pnpm build:cjs && pnpm build:esm", - "build:cjs": "tsc --project ./tsconfig.cjs.json", - "build:esm": "tsc --project ./tsconfig.esm.json && node scripts/rename-to-esm-files.js dist/esm", - "test": "vitest", - "test:unit": "vitest --project unit", - "test:wire": "vitest --project wire" - }, - "dependencies": {}, - "devDependencies": { - "webpack": "^5.105.4", - "ts-loader": "^9.5.4", - "vitest": "^4.1.1", - "msw": "2.11.2", - "@types/node": "^18.19.70", - "typescript": "~5.9.3", - "@biomejs/biome": "2.4.10" - }, - "browser": { - "fs": false, - "os": false, - "path": false, - "stream": false, - "crypto": false - }, - "packageManager": "pnpm@10.33.0", - "engines": { - "node": ">=18.0.0" - }, - "sideEffects": false -} diff --git a/seed/ts-sdk/allof-inline/pnpm-workspace.yaml b/seed/ts-sdk/allof-inline/pnpm-workspace.yaml deleted file mode 100644 index 6e4c395107df..000000000000 --- a/seed/ts-sdk/allof-inline/pnpm-workspace.yaml +++ /dev/null @@ -1 +0,0 @@ -packages: ['.'] \ No newline at end of file diff --git a/seed/ts-sdk/allof-inline/reference.md b/seed/ts-sdk/allof-inline/reference.md deleted file mode 100644 index 6d75591df79d..000000000000 --- a/seed/ts-sdk/allof-inline/reference.md +++ /dev/null @@ -1,225 +0,0 @@ -# Reference -
client.searchRuleTypes({ ...params }) -> SeedApi.RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.searchRuleTypes(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `SeedApi.SearchRuleTypesRequest` - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.createRule({ ...params }) -> SeedApi.RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.createRule({ - name: "name", - executionContext: "prod" -}); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `SeedApi.RuleCreateRequest` - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.listUsers() -> SeedApi.UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.listUsers(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.getEntity() -> SeedApi.CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.getEntity(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.getOrganization() -> SeedApi.Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.getOrganization(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- diff --git a/seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js b/seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js deleted file mode 100644 index dc1df1cbbacb..000000000000 --- a/seed/ts-sdk/allof-inline/scripts/rename-to-esm-files.js +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env node - -const fs = require("fs").promises; -const path = require("path"); - -const extensionMap = { - ".js": ".mjs", - ".d.ts": ".d.mts", -}; -const oldExtensions = Object.keys(extensionMap); - -async function findFiles(rootPath) { - const files = []; - - async function scan(directory) { - const entries = await fs.readdir(directory, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(directory, entry.name); - - if (entry.isDirectory()) { - if (entry.name !== "node_modules" && !entry.name.startsWith(".")) { - await scan(fullPath); - } - } else if (entry.isFile()) { - if (oldExtensions.some((ext) => entry.name.endsWith(ext))) { - files.push(fullPath); - } - } - } - } - - await scan(rootPath); - return files; -} - -async function updateFiles(files) { - const updatedFiles = []; - for (const file of files) { - const updated = await updateFileContents(file); - updatedFiles.push(updated); - } - - console.log(`Updated imports in ${updatedFiles.length} files.`); -} - -async function updateFileContents(file) { - const content = await fs.readFile(file, "utf8"); - - let newContent = content; - // Update each extension type defined in the map - for (const [oldExt, newExt] of Object.entries(extensionMap)) { - // Handle static imports/exports - const staticRegex = new RegExp(`(import|export)(.+from\\s+['"])(\\.\\.?\\/[^'"]+)(\\${oldExt})(['"])`, "g"); - newContent = newContent.replace(staticRegex, `$1$2$3${newExt}$5`); - - // Handle dynamic imports (yield import, await import, regular import()) - const dynamicRegex = new RegExp( - `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, - "g", - ); - newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); - } - - if (content !== newContent) { - await fs.writeFile(file, newContent, "utf8"); - return true; - } - return false; -} - -async function renameFiles(files) { - let counter = 0; - for (const file of files) { - const ext = oldExtensions.find((ext) => file.endsWith(ext)); - const newExt = extensionMap[ext]; - - if (newExt) { - const newPath = file.slice(0, -ext.length) + newExt; - await fs.rename(file, newPath); - counter++; - } - } - - console.log(`Renamed ${counter} files.`); -} - -async function main() { - try { - const targetDir = process.argv[2]; - if (!targetDir) { - console.error("Please provide a target directory"); - process.exit(1); - } - - const targetPath = path.resolve(targetDir); - const targetStats = await fs.stat(targetPath); - - if (!targetStats.isDirectory()) { - console.error("The provided path is not a directory"); - process.exit(1); - } - - console.log(`Scanning directory: ${targetDir}`); - - const files = await findFiles(targetDir); - - if (files.length === 0) { - console.log("No matching files found."); - process.exit(0); - } - - console.log(`Found ${files.length} files.`); - await updateFiles(files); - await renameFiles(files); - console.log("\nDone!"); - } catch (error) { - console.error("An error occurred:", error.message); - process.exit(1); - } -} - -main(); diff --git a/seed/ts-sdk/allof-inline/snippet.json b/seed/ts-sdk/allof-inline/snippet.json deleted file mode 100644 index 8b75c9d2992f..000000000000 --- a/seed/ts-sdk/allof-inline/snippet.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "endpoints": [ - { - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.searchRuleTypes();\n" - } - }, - { - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.createRule({\n name: \"name\",\n executionContext: \"prod\"\n});\n" - } - }, - { - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.listUsers();\n" - } - }, - { - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.getEntity();\n" - } - }, - { - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof-inline\";\n\nconst client = new SeedApiClient;\nawait client.getOrganization();\n" - } - } - ], - "types": {} -} \ No newline at end of file diff --git a/seed/ts-sdk/allof-inline/src/BaseClient.ts b/seed/ts-sdk/allof-inline/src/BaseClient.ts deleted file mode 100644 index 8bad31218ef2..000000000000 --- a/seed/ts-sdk/allof-inline/src/BaseClient.ts +++ /dev/null @@ -1,60 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import { mergeHeaders } from "./core/headers.js"; -import * as core from "./core/index.js"; -import type * as environments from "./environments.js"; - -export interface BaseClientOptions { - environment?: core.Supplier; - /** Specify a custom URL to connect the client to. */ - baseUrl?: core.Supplier; - /** Additional headers to include in requests. */ - headers?: Record | null | undefined>; - /** The default maximum time to wait for a response in seconds. */ - timeoutInSeconds?: number; - /** The default number of times to retry the request. Defaults to 2. */ - maxRetries?: number; - /** Provide a custom fetch implementation. Useful for platforms that don't have a built-in fetch or need a custom implementation. */ - fetch?: typeof fetch; - /** Configure logging for the client. */ - logging?: core.logging.LogConfig | core.logging.Logger; -} - -export interface BaseRequestOptions { - /** The maximum time to wait for a response in seconds. */ - timeoutInSeconds?: number; - /** The number of times to retry the request. Defaults to 2. */ - maxRetries?: number; - /** A hook to abort the request. */ - abortSignal?: AbortSignal; - /** Additional query string parameters to include in the request. */ - queryParams?: Record; - /** Additional headers to include in the request. */ - headers?: Record | null | undefined>; -} - -export type NormalizedClientOptions = T & { - logging: core.logging.Logger; -}; - -export function normalizeClientOptions( - options: T, -): NormalizedClientOptions { - const headers = mergeHeaders( - { - "X-Fern-Language": "JavaScript", - "X-Fern-SDK-Name": "@fern/allof-inline", - "X-Fern-SDK-Version": "0.0.1", - "User-Agent": "@fern/allof-inline/0.0.1", - "X-Fern-Runtime": core.RUNTIME.type, - "X-Fern-Runtime-Version": core.RUNTIME.version, - }, - options?.headers, - ); - - return { - ...options, - logging: core.logging.createLogger(options?.logging), - headers, - } as NormalizedClientOptions; -} diff --git a/seed/ts-sdk/allof-inline/src/Client.ts b/seed/ts-sdk/allof-inline/src/Client.ts deleted file mode 100644 index 01e5b4de5533..000000000000 --- a/seed/ts-sdk/allof-inline/src/Client.ts +++ /dev/null @@ -1,303 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "./api/index.js"; -import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; -import { type NormalizedClientOptions, normalizeClientOptions } from "./BaseClient.js"; -import { mergeHeaders } from "./core/headers.js"; -import * as core from "./core/index.js"; -import * as environments from "./environments.js"; -import { handleNonStatusCodeError } from "./errors/handleNonStatusCodeError.js"; -import * as errors from "./errors/index.js"; - -export declare namespace SeedApiClient { - export type Options = BaseClientOptions; - - export interface RequestOptions extends BaseRequestOptions {} -} - -export class SeedApiClient { - protected readonly _options: NormalizedClientOptions; - - constructor(options: SeedApiClient.Options = {}) { - this._options = normalizeClientOptions(options); - } - - /** - * @param {SeedApi.SearchRuleTypesRequest} request - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.searchRuleTypes() - */ - public searchRuleTypes( - request: SeedApi.SearchRuleTypesRequest = {}, - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__searchRuleTypes(request, requestOptions)); - } - - private async __searchRuleTypes( - request: SeedApi.SearchRuleTypesRequest = {}, - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const { query } = request; - const _queryParams: Record = { - query, - }; - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "rule-types", - ), - method: "GET", - headers: _headers, - queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.RuleTypeSearchResponse, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/rule-types"); - } - - /** - * @param {SeedApi.RuleCreateRequest} request - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.createRule({ - * name: "name", - * executionContext: "prod" - * }) - */ - public createRule( - request: SeedApi.RuleCreateRequest, - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__createRule(request, requestOptions)); - } - - private async __createRule( - request: SeedApi.RuleCreateRequest, - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "rules", - ), - method: "POST", - headers: _headers, - contentType: "application/json", - queryParameters: requestOptions?.queryParams, - requestType: "json", - body: request, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.RuleResponse, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/rules"); - } - - /** - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.listUsers() - */ - public listUsers( - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__listUsers(requestOptions)); - } - - private async __listUsers( - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "users", - ), - method: "GET", - headers: _headers, - queryParameters: requestOptions?.queryParams, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.UserSearchResponse, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/users"); - } - - /** - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.getEntity() - */ - public getEntity(requestOptions?: SeedApiClient.RequestOptions): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__getEntity(requestOptions)); - } - - private async __getEntity( - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "entities", - ), - method: "GET", - headers: _headers, - queryParameters: requestOptions?.queryParams, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.CombinedEntity, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/entities"); - } - - /** - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.getOrganization() - */ - public getOrganization( - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__getOrganization(requestOptions)); - } - - private async __getOrganization( - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "organizations", - ), - method: "GET", - headers: _headers, - queryParameters: requestOptions?.queryParams, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.Organization, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/organizations"); - } - - /** - * Make a passthrough request using the SDK's configured auth, retry, logging, etc. - * This is useful for making requests to endpoints not yet supported in the SDK. - * The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL. - * - * @param {Request | string | URL} input - The URL, path, or Request object. - * @param {RequestInit} init - Standard fetch RequestInit options. - * @param {core.PassthroughRequest.RequestOptions} requestOptions - Per-request overrides (timeout, retries, headers, abort signal). - * @returns {Promise} A standard Response object. - */ - public async fetch( - input: Request | string | URL, - init?: RequestInit, - requestOptions?: core.PassthroughRequest.RequestOptions, - ): Promise { - return core.makePassthroughRequest( - input, - init, - { - baseUrl: this._options.baseUrl ?? this._options.environment, - headers: this._options.headers, - timeoutInSeconds: this._options.timeoutInSeconds, - maxRetries: this._options.maxRetries, - fetch: this._options.fetch, - logging: this._options.logging, - }, - requestOptions, - ); - } -} diff --git a/seed/ts-sdk/allof-inline/src/api/client/index.ts b/seed/ts-sdk/allof-inline/src/api/client/index.ts deleted file mode 100644 index 195f9aa8a846..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/client/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./requests/index.js"; diff --git a/seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts b/seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts deleted file mode 100644 index bb42c4d24ac9..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/client/requests/RuleCreateRequest.ts +++ /dev/null @@ -1,15 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../../index.js"; - -/** - * @example - * { - * name: "name", - * executionContext: "prod" - * } - */ -export interface RuleCreateRequest { - name: string; - executionContext: SeedApi.RuleExecutionContext; -} diff --git a/seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts b/seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts deleted file mode 100644 index 502888d9c4e3..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/client/requests/SearchRuleTypesRequest.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** - * @example - * {} - */ -export interface SearchRuleTypesRequest { - query?: string; -} diff --git a/seed/ts-sdk/allof-inline/src/api/client/requests/index.ts b/seed/ts-sdk/allof-inline/src/api/client/requests/index.ts deleted file mode 100644 index 07aecd81dd45..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/client/requests/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { RuleCreateRequest } from "./RuleCreateRequest.js"; -export type { SearchRuleTypesRequest } from "./SearchRuleTypesRequest.js"; diff --git a/seed/ts-sdk/allof-inline/src/api/index.ts b/seed/ts-sdk/allof-inline/src/api/index.ts deleted file mode 100644 index d9adb1af9a93..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./client/index.js"; -export * from "./types/index.js"; diff --git a/seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts b/seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts deleted file mode 100644 index 535489319123..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/AuditInfo.ts +++ /dev/null @@ -1,15 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** - * Common audit metadata. - */ -export interface AuditInfo { - /** The user who created this resource. */ - createdBy?: string | undefined; - /** When this resource was created. */ - createdDateTime?: string | undefined; - /** The user who last modified this resource. */ - modifiedBy?: string | undefined; - /** When this resource was last modified. */ - modifiedDateTime?: string | undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts b/seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts deleted file mode 100644 index eec0dea9a386..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/BaseOrg.ts +++ /dev/null @@ -1,15 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface BaseOrg { - id: string; - metadata?: BaseOrg.Metadata | undefined; -} - -export namespace BaseOrg { - export interface Metadata { - /** Deployment region from BaseOrg. */ - region: string; - /** Subscription tier. */ - tier?: string | undefined; - } -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts b/seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts deleted file mode 100644 index bba206ea76fe..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/CombinedEntity.ts +++ /dev/null @@ -1,19 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface CombinedEntity { - /** Unique identifier. */ - id: string; - /** Display name from Describable. */ - name?: string | undefined; - /** A short summary. */ - summary?: string | undefined; - status: CombinedEntity.Status; -} - -export namespace CombinedEntity { - export const Status = { - Active: "active", - Archived: "archived", - } as const; - export type Status = (typeof Status)[keyof typeof Status]; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/Describable.ts b/seed/ts-sdk/allof-inline/src/api/types/Describable.ts deleted file mode 100644 index b5c82cac5a67..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/Describable.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface Describable { - /** Display name from Describable. */ - name?: string | undefined; - /** A short summary. */ - summary?: string | undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts b/seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts deleted file mode 100644 index 84e0ef063cab..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/DetailedOrg.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface DetailedOrg { - metadata?: DetailedOrg.Metadata | undefined; -} - -export namespace DetailedOrg { - export interface Metadata { - /** Deployment region from DetailedOrg. */ - region: string; - /** Custom domain name. */ - domain?: string | undefined; - } -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts b/seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts deleted file mode 100644 index 65d2053f6cb8..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/Identifiable.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface Identifiable { - /** Unique identifier. */ - id: string; - /** Display name from Identifiable. */ - name?: string | undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/Organization.ts b/seed/ts-sdk/allof-inline/src/api/types/Organization.ts deleted file mode 100644 index 4c9106000c07..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/Organization.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface Organization { - id: string; - metadata?: Organization.Metadata | undefined; - name: string; -} - -export namespace Organization { - export interface Metadata { - /** Deployment region from DetailedOrg. */ - region: string; - /** Custom domain name. */ - domain?: string | undefined; - } -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts b/seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts deleted file mode 100644 index ed373200a8e1..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/PaginatedResult.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface PaginatedResult { - paging: SeedApi.PagingCursors; - /** Current page of results from the requested resource. */ - results: unknown[]; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts b/seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts deleted file mode 100644 index 2ff3fa532101..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/PagingCursors.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface PagingCursors { - /** Cursor for the next page of results. */ - next: string; - /** Cursor for the previous page of results. */ - previous?: string | undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts deleted file mode 100644 index fe794a8856da..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/RuleExecutionContext.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** Execution environment for a rule. */ -export const RuleExecutionContext = { - Prod: "prod", - Staging: "staging", - Dev: "dev", -} as const; -export type RuleExecutionContext = (typeof RuleExecutionContext)[keyof typeof RuleExecutionContext]; diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts deleted file mode 100644 index 54edd763c0a2..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/RuleResponse.ts +++ /dev/null @@ -1,27 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface RuleResponse { - /** The user who created this resource. */ - createdBy?: string | undefined; - /** When this resource was created. */ - createdDateTime?: string | undefined; - /** The user who last modified this resource. */ - modifiedBy?: string | undefined; - /** When this resource was last modified. */ - modifiedDateTime?: string | undefined; - id: string; - name: string; - status: RuleResponse.Status; - executionContext?: SeedApi.RuleExecutionContext | undefined; -} - -export namespace RuleResponse { - export const Status = { - Active: "active", - Inactive: "inactive", - Draft: "draft", - } as const; - export type Status = (typeof Status)[keyof typeof Status]; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleType.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleType.ts deleted file mode 100644 index ac2bde7133b2..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/RuleType.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface RuleType { - id: string; - name: string; - description?: string | undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts b/seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts deleted file mode 100644 index c5d63a1d383e..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/RuleTypeSearchResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface RuleTypeSearchResponse { - paging: SeedApi.PagingCursors; - /** Current page of results from the requested resource. */ - results?: SeedApi.RuleType[] | undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/User.ts b/seed/ts-sdk/allof-inline/src/api/types/User.ts deleted file mode 100644 index 7d0e30eaf136..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/User.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface User { - id: string; - email: string; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts b/seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts deleted file mode 100644 index f9e22de3ec39..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/UserSearchResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface UserSearchResponse { - paging: SeedApi.PagingCursors; - /** Current page of results from the requested resource. */ - results?: SeedApi.User[] | undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/api/types/index.ts b/seed/ts-sdk/allof-inline/src/api/types/index.ts deleted file mode 100644 index ae8a133ce81f..000000000000 --- a/seed/ts-sdk/allof-inline/src/api/types/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export * from "./AuditInfo.js"; -export * from "./BaseOrg.js"; -export * from "./CombinedEntity.js"; -export * from "./Describable.js"; -export * from "./DetailedOrg.js"; -export * from "./Identifiable.js"; -export * from "./Organization.js"; -export * from "./PaginatedResult.js"; -export * from "./PagingCursors.js"; -export * from "./RuleExecutionContext.js"; -export * from "./RuleResponse.js"; -export * from "./RuleType.js"; -export * from "./RuleTypeSearchResponse.js"; -export * from "./User.js"; -export * from "./UserSearchResponse.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/exports.ts b/seed/ts-sdk/allof-inline/src/core/exports.ts deleted file mode 100644 index 69296d7100d6..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/exports.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./logging/exports.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts deleted file mode 100644 index 97ab83c2b195..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/APIResponse.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { RawResponse } from "./RawResponse.js"; - -/** - * The response of an API call. - * It is a successful response or a failed response. - */ -export type APIResponse = SuccessfulResponse | FailedResponse; - -export interface SuccessfulResponse { - ok: true; - body: T; - /** - * @deprecated Use `rawResponse` instead - */ - headers?: Record; - rawResponse: RawResponse; -} - -export interface FailedResponse { - ok: false; - error: T; - rawResponse: RawResponse; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts deleted file mode 100644 index b9e40fb62cc4..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/BinaryResponse.ts +++ /dev/null @@ -1,34 +0,0 @@ -export type BinaryResponse = { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ - bodyUsed: Response["bodyUsed"]; - /** - * Returns a ReadableStream of the response body. - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) - */ - stream: () => Response["body"]; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ - arrayBuffer: () => ReturnType; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ - blob: () => ReturnType; - /** - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) - * Some versions of the Fetch API may not support this method. - */ - bytes?(): Promise; -}; - -export function getBinaryResponse(response: Response): BinaryResponse { - const binaryResponse: BinaryResponse = { - get bodyUsed() { - return response.bodyUsed; - }, - stream: () => response.body, - arrayBuffer: response.arrayBuffer.bind(response), - blob: response.blob.bind(response), - }; - if ("bytes" in response && typeof response.bytes === "function") { - binaryResponse.bytes = response.bytes.bind(response); - } - - return binaryResponse; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts deleted file mode 100644 index 998d68f5c20c..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointMetadata.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type SecuritySchemeKey = string; -/** - * A collection of security schemes, where the key is the name of the security scheme and the value is the list of scopes required for that scheme. - * All schemes in the collection must be satisfied for authentication to be successful. - */ -export type SecuritySchemeCollection = Record; -export type AuthScope = string; -export type EndpointMetadata = { - /** - * An array of security scheme collections. Each collection represents an alternative way to authenticate. - */ - security?: SecuritySchemeCollection[]; -}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts deleted file mode 100644 index aad81f0d9040..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/EndpointSupplier.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { EndpointMetadata } from "./EndpointMetadata.js"; -import type { Supplier } from "./Supplier.js"; - -type EndpointSupplierFn = (arg: { endpointMetadata?: EndpointMetadata }) => T | Promise; -export type EndpointSupplier = Supplier | EndpointSupplierFn; -export const EndpointSupplier = { - get: async (supplier: EndpointSupplier, arg: { endpointMetadata?: EndpointMetadata }): Promise => { - if (typeof supplier === "function") { - return (supplier as EndpointSupplierFn)(arg); - } else { - return supplier; - } - }, -}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts deleted file mode 100644 index 928dfeaabae6..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts +++ /dev/null @@ -1,404 +0,0 @@ -import { toJson } from "../json.js"; -import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; -import type { APIResponse } from "./APIResponse.js"; -import { createRequestUrl } from "./createRequestUrl.js"; -import type { EndpointMetadata } from "./EndpointMetadata.js"; -import { EndpointSupplier } from "./EndpointSupplier.js"; -import { getErrorResponseBody } from "./getErrorResponseBody.js"; -import { getFetchFn } from "./getFetchFn.js"; -import { getRequestBody } from "./getRequestBody.js"; -import { getResponseBody } from "./getResponseBody.js"; -import { Headers } from "./Headers.js"; -import { makeRequest } from "./makeRequest.js"; -import { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; -import { requestWithRetries } from "./requestWithRetries.js"; - -export type FetchFunction = (args: Fetcher.Args) => Promise>; - -export declare namespace Fetcher { - export interface Args { - url: string; - method: string; - contentType?: string; - headers?: Record; - queryParameters?: Record; - body?: unknown; - timeoutMs?: number; - maxRetries?: number; - withCredentials?: boolean; - abortSignal?: AbortSignal; - requestType?: "json" | "file" | "bytes" | "form" | "other"; - responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer" | "binary-response"; - duplex?: "half"; - endpointMetadata?: EndpointMetadata; - fetchFn?: typeof fetch; - logging?: LogConfig | Logger; - } - - export type Error = FailedStatusCodeError | NonJsonError | BodyIsNullError | TimeoutError | UnknownError; - - export interface FailedStatusCodeError { - reason: "status-code"; - statusCode: number; - body: unknown; - } - - export interface NonJsonError { - reason: "non-json"; - statusCode: number; - rawBody: string; - } - - export interface BodyIsNullError { - reason: "body-is-null"; - statusCode: number; - } - - export interface TimeoutError { - reason: "timeout"; - cause?: unknown; - } - - export interface UnknownError { - reason: "unknown"; - errorMessage: string; - cause?: unknown; - } -} - -const SENSITIVE_HEADERS = new Set([ - "authorization", - "www-authenticate", - "x-api-key", - "api-key", - "apikey", - "x-api-token", - "x-auth-token", - "auth-token", - "cookie", - "set-cookie", - "proxy-authorization", - "proxy-authenticate", - "x-csrf-token", - "x-xsrf-token", - "x-session-token", - "x-access-token", -]); - -function redactHeaders(headers: Headers | Record): Record { - const filtered: Record = {}; - for (const [key, value] of headers instanceof Headers ? headers.entries() : Object.entries(headers)) { - if (SENSITIVE_HEADERS.has(key.toLowerCase())) { - filtered[key] = "[REDACTED]"; - } else { - filtered[key] = value; - } - } - return filtered; -} - -const SENSITIVE_QUERY_PARAMS = new Set([ - "api_key", - "api-key", - "apikey", - "token", - "access_token", - "access-token", - "auth_token", - "auth-token", - "password", - "passwd", - "secret", - "api_secret", - "api-secret", - "apisecret", - "key", - "session", - "session_id", - "session-id", -]); - -function redactQueryParameters(queryParameters?: Record): Record | undefined { - if (queryParameters == null) { - return queryParameters; - } - const redacted: Record = {}; - for (const [key, value] of Object.entries(queryParameters)) { - if (SENSITIVE_QUERY_PARAMS.has(key.toLowerCase())) { - redacted[key] = "[REDACTED]"; - } else { - redacted[key] = value; - } - } - return redacted; -} - -function redactUrl(url: string): string { - const protocolIndex = url.indexOf("://"); - if (protocolIndex === -1) return url; - - const afterProtocol = protocolIndex + 3; - - // Find the first delimiter that marks the end of the authority section - const pathStart = url.indexOf("/", afterProtocol); - let queryStart = url.indexOf("?", afterProtocol); - let fragmentStart = url.indexOf("#", afterProtocol); - - const firstDelimiter = Math.min( - pathStart === -1 ? url.length : pathStart, - queryStart === -1 ? url.length : queryStart, - fragmentStart === -1 ? url.length : fragmentStart, - ); - - // Find the LAST @ before the delimiter (handles multiple @ in credentials) - let atIndex = -1; - for (let i = afterProtocol; i < firstDelimiter; i++) { - if (url[i] === "@") { - atIndex = i; - } - } - - if (atIndex !== -1) { - url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`; - } - - // Recalculate queryStart since url might have changed - queryStart = url.indexOf("?"); - if (queryStart === -1) return url; - - fragmentStart = url.indexOf("#", queryStart); - const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length; - const queryString = url.slice(queryStart + 1, queryEnd); - - if (queryString.length === 0) return url; - - // FAST PATH: Quick check if any sensitive keywords present - // Using indexOf is faster than regex for simple substring matching - const lower = queryString.toLowerCase(); - const hasSensitive = - lower.includes("token") || - lower.includes("key") || - lower.includes("password") || - lower.includes("passwd") || - lower.includes("secret") || - lower.includes("session") || - lower.includes("auth"); - - if (!hasSensitive) { - return url; - } - - // SLOW PATH: Parse and redact - const redactedParams: string[] = []; - const params = queryString.split("&"); - - for (const param of params) { - const equalIndex = param.indexOf("="); - if (equalIndex === -1) { - redactedParams.push(param); - continue; - } - - const key = param.slice(0, equalIndex); - let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()); - - if (!shouldRedact && key.includes("%")) { - try { - const decodedKey = decodeURIComponent(key); - shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase()); - } catch {} - } - - redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param); - } - - return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd); -} - -async function getHeaders(args: Fetcher.Args): Promise { - const newHeaders: Headers = new Headers(); - - newHeaders.set( - "Accept", - args.responseType === "json" - ? "application/json" - : args.responseType === "text" - ? "text/plain" - : args.responseType === "sse" - ? "text/event-stream" - : "*/*", - ); - if (args.body !== undefined && args.contentType != null) { - newHeaders.set("Content-Type", args.contentType); - } - - if (args.headers == null) { - return newHeaders; - } - - for (const [key, value] of Object.entries(args.headers)) { - const result = await EndpointSupplier.get(value, { endpointMetadata: args.endpointMetadata ?? {} }); - if (typeof result === "string") { - newHeaders.set(key, result); - continue; - } - if (result == null) { - continue; - } - newHeaders.set(key, `${result}`); - } - return newHeaders; -} - -export async function fetcherImpl(args: Fetcher.Args): Promise> { - const url = createRequestUrl(args.url, args.queryParameters); - const requestBody: BodyInit | undefined = await getRequestBody({ - body: args.body, - type: args.requestType ?? "other", - }); - const fetchFn = args.fetchFn ?? (await getFetchFn()); - const headers = await getHeaders(args); - const logger = createLogger(args.logging); - - if (logger.isDebug()) { - const metadata = { - method: args.method, - url: redactUrl(url), - headers: redactHeaders(headers), - queryParameters: redactQueryParameters(args.queryParameters), - hasBody: requestBody != null, - }; - logger.debug("Making HTTP request", metadata); - } - - try { - const response = await requestWithRetries( - async () => - makeRequest( - fetchFn, - url, - args.method, - headers, - requestBody, - args.timeoutMs, - args.abortSignal, - args.withCredentials, - args.duplex, - args.responseType === "streaming" || args.responseType === "sse", - ), - args.maxRetries, - ); - - if (response.status >= 200 && response.status < 400) { - if (logger.isDebug()) { - const metadata = { - method: args.method, - url: redactUrl(url), - statusCode: response.status, - responseHeaders: redactHeaders(response.headers), - }; - logger.debug("HTTP request succeeded", metadata); - } - const body = await getResponseBody(response, args.responseType); - return { - ok: true, - body: body as R, - headers: response.headers, - rawResponse: toRawResponse(response), - }; - } else { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - statusCode: response.status, - responseHeaders: redactHeaders(Object.fromEntries(response.headers.entries())), - }; - logger.error("HTTP request failed with error status", metadata); - } - return { - ok: false, - error: { - reason: "status-code", - statusCode: response.status, - body: await getErrorResponseBody(response), - }, - rawResponse: toRawResponse(response), - }; - } - } catch (error) { - if (args.abortSignal?.aborted) { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - }; - logger.error("HTTP request was aborted", metadata); - } - return { - ok: false, - error: { - reason: "unknown", - errorMessage: "The user aborted a request", - cause: error, - }, - rawResponse: abortRawResponse, - }; - } else if (error instanceof Error && error.name === "AbortError") { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - timeoutMs: args.timeoutMs, - }; - logger.error("HTTP request timed out", metadata); - } - return { - ok: false, - error: { - reason: "timeout", - cause: error, - }, - rawResponse: abortRawResponse, - }; - } else if (error instanceof Error) { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - errorMessage: error.message, - }; - logger.error("HTTP request failed with error", metadata); - } - return { - ok: false, - error: { - reason: "unknown", - errorMessage: error.message, - cause: error, - }, - rawResponse: unknownRawResponse, - }; - } - - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - error: toJson(error), - }; - logger.error("HTTP request failed with unknown error", metadata); - } - return { - ok: false, - error: { - reason: "unknown", - errorMessage: toJson(error), - cause: error, - }, - rawResponse: unknownRawResponse, - }; - } -} - -export const fetcher: FetchFunction = fetcherImpl; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts deleted file mode 100644 index af841aa24f55..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/Headers.ts +++ /dev/null @@ -1,93 +0,0 @@ -let Headers: typeof globalThis.Headers; - -if (typeof globalThis.Headers !== "undefined") { - Headers = globalThis.Headers; -} else { - Headers = class Headers implements Headers { - private headers: Map; - - constructor(init?: HeadersInit) { - this.headers = new Map(); - - if (init) { - if (init instanceof Headers) { - init.forEach((value, key) => this.append(key, value)); - } else if (Array.isArray(init)) { - for (const [key, value] of init) { - if (typeof key === "string" && typeof value === "string") { - this.append(key, value); - } else { - throw new TypeError("Each header entry must be a [string, string] tuple"); - } - } - } else { - for (const [key, value] of Object.entries(init)) { - if (typeof value === "string") { - this.append(key, value); - } else { - throw new TypeError("Header values must be strings"); - } - } - } - } - } - - append(name: string, value: string): void { - const key = name.toLowerCase(); - const existing = this.headers.get(key) || []; - this.headers.set(key, [...existing, value]); - } - - delete(name: string): void { - const key = name.toLowerCase(); - this.headers.delete(key); - } - - get(name: string): string | null { - const key = name.toLowerCase(); - const values = this.headers.get(key); - return values ? values.join(", ") : null; - } - - has(name: string): boolean { - const key = name.toLowerCase(); - return this.headers.has(key); - } - - set(name: string, value: string): void { - const key = name.toLowerCase(); - this.headers.set(key, [value]); - } - - forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: unknown): void { - const boundCallback = thisArg ? callbackfn.bind(thisArg) : callbackfn; - this.headers.forEach((values, key) => boundCallback(values.join(", "), key, this)); - } - - getSetCookie(): string[] { - return this.headers.get("set-cookie") || []; - } - - *entries(): HeadersIterator<[string, string]> { - for (const [key, values] of this.headers.entries()) { - yield [key, values.join(", ")]; - } - } - - *keys(): HeadersIterator { - yield* this.headers.keys(); - } - - *values(): HeadersIterator { - for (const values of this.headers.values()) { - yield values.join(", "); - } - } - - [Symbol.iterator](): HeadersIterator<[string, string]> { - return this.entries(); - } - }; -} - -export { Headers }; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts deleted file mode 100644 index 692ca7d795f0..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/HttpResponsePromise.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { WithRawResponse } from "./RawResponse.js"; - -/** - * A promise that returns the parsed response and lets you retrieve the raw response too. - */ -export class HttpResponsePromise extends Promise { - private innerPromise: Promise>; - private unwrappedPromise: Promise | undefined; - - private constructor(promise: Promise>) { - // Initialize with a no-op to avoid premature parsing - super((resolve) => { - resolve(undefined as unknown as T); - }); - this.innerPromise = promise; - } - - /** - * Creates an `HttpResponsePromise` from a function that returns a promise. - * - * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. - * @param args - Arguments to pass to the function. - * @returns An `HttpResponsePromise` instance. - */ - public static fromFunction Promise>, T>( - fn: F, - ...args: Parameters - ): HttpResponsePromise { - return new HttpResponsePromise(fn(...args)); - } - - /** - * Creates a function that returns an `HttpResponsePromise` from a function that returns a promise. - * - * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. - * @returns A function that returns an `HttpResponsePromise` instance. - */ - public static interceptFunction< - F extends (...args: never[]) => Promise>, - T = Awaited>["data"], - >(fn: F): (...args: Parameters) => HttpResponsePromise { - return (...args: Parameters): HttpResponsePromise => { - return HttpResponsePromise.fromPromise(fn(...args)); - }; - } - - /** - * Creates an `HttpResponsePromise` from an existing promise. - * - * @param promise - A promise resolving to a `WithRawResponse` object. - * @returns An `HttpResponsePromise` instance. - */ - public static fromPromise(promise: Promise>): HttpResponsePromise { - return new HttpResponsePromise(promise); - } - - /** - * Creates an `HttpResponsePromise` from an executor function. - * - * @param executor - A function that takes resolve and reject callbacks to create a promise. - * @returns An `HttpResponsePromise` instance. - */ - public static fromExecutor( - executor: (resolve: (value: WithRawResponse) => void, reject: (reason?: unknown) => void) => void, - ): HttpResponsePromise { - const promise = new Promise>(executor); - return new HttpResponsePromise(promise); - } - - /** - * Creates an `HttpResponsePromise` from a resolved result. - * - * @param result - A `WithRawResponse` object to resolve immediately. - * @returns An `HttpResponsePromise` instance. - */ - public static fromResult(result: WithRawResponse): HttpResponsePromise { - const promise = Promise.resolve(result); - return new HttpResponsePromise(promise); - } - - private unwrap(): Promise { - if (!this.unwrappedPromise) { - this.unwrappedPromise = this.innerPromise.then(({ data }) => data); - } - return this.unwrappedPromise; - } - - /** @inheritdoc */ - public override then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, - ): Promise { - return this.unwrap().then(onfulfilled, onrejected); - } - - /** @inheritdoc */ - public override catch( - onrejected?: ((reason: unknown) => TResult | PromiseLike) | null, - ): Promise { - return this.unwrap().catch(onrejected); - } - - /** @inheritdoc */ - public override finally(onfinally?: (() => void) | null): Promise { - return this.unwrap().finally(onfinally); - } - - /** - * Retrieves the data and raw response. - * - * @returns A promise resolving to a `WithRawResponse` object. - */ - public async withRawResponse(): Promise> { - return await this.innerPromise; - } -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts deleted file mode 100644 index 37fb44e2aa99..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/RawResponse.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Headers } from "./Headers.js"; - -/** - * The raw response from the fetch call excluding the body. - */ -export type RawResponse = Omit< - { - [K in keyof Response as Response[K] extends Function ? never : K]: Response[K]; // strips out functions - }, - "ok" | "body" | "bodyUsed" ->; // strips out body and bodyUsed - -/** - * A raw response indicating that the request was aborted. - */ -export const abortRawResponse: RawResponse = { - headers: new Headers(), - redirected: false, - status: 499, - statusText: "Client Closed Request", - type: "error", - url: "", -} as const; - -/** - * A raw response indicating an unknown error. - */ -export const unknownRawResponse: RawResponse = { - headers: new Headers(), - redirected: false, - status: 0, - statusText: "Unknown Error", - type: "error", - url: "", -} as const; - -/** - * Converts a `RawResponse` object into a `RawResponse` by extracting its properties, - * excluding the `body` and `bodyUsed` fields. - * - * @param response - The `RawResponse` object to convert. - * @returns A `RawResponse` object containing the extracted properties of the input response. - */ -export function toRawResponse(response: Response): RawResponse { - return { - headers: response.headers, - redirected: response.redirected, - status: response.status, - statusText: response.statusText, - type: response.type, - url: response.url, - }; -} - -/** - * Creates a `RawResponse` from a standard `Response` object. - */ -export interface WithRawResponse { - readonly data: T; - readonly rawResponse: RawResponse; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts deleted file mode 100644 index 867c931c02f4..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/Supplier.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type Supplier = T | Promise | (() => T | Promise); - -export const Supplier = { - get: async (supplier: Supplier): Promise => { - if (typeof supplier === "function") { - return (supplier as () => T)(); - } else { - return supplier; - } - }, -}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts deleted file mode 100644 index 88e13265e112..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/createRequestUrl.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { toQueryString } from "../url/qs.js"; - -export function createRequestUrl(baseUrl: string, queryParameters?: Record): string { - const queryString = toQueryString(queryParameters, { arrayFormat: "repeat" }); - return queryString ? `${baseUrl}?${queryString}` : baseUrl; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts deleted file mode 100644 index 7cf4e623c2f5..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/getErrorResponseBody.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { fromJson } from "../json.js"; -import { getResponseBody } from "./getResponseBody.js"; - -export async function getErrorResponseBody(response: Response): Promise { - let contentType = response.headers.get("Content-Type")?.toLowerCase(); - if (contentType == null || contentType.length === 0) { - return getResponseBody(response); - } - - if (contentType.indexOf(";") !== -1) { - contentType = contentType.split(";")[0]?.trim() ?? ""; - } - switch (contentType) { - case "application/hal+json": - case "application/json": - case "application/ld+json": - case "application/problem+json": - case "application/vnd.api+json": - case "text/json": { - const text = await response.text(); - return text.length > 0 ? fromJson(text) : undefined; - } - default: - if (contentType.startsWith("application/vnd.") && contentType.endsWith("+json")) { - const text = await response.text(); - return text.length > 0 ? fromJson(text) : undefined; - } - - // Fallback to plain text if content type is not recognized - // Even if no body is present, the response will be an empty string - return await response.text(); - } -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts deleted file mode 100644 index 9f845b956392..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/getFetchFn.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function getFetchFn(): Promise { - return fetch; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts deleted file mode 100644 index 50f922b0e87f..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/getHeader.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function getHeader(headers: Record, header: string): string | undefined { - for (const [headerKey, headerValue] of Object.entries(headers)) { - if (headerKey.toLowerCase() === header.toLowerCase()) { - return headerValue; - } - } - return undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts deleted file mode 100644 index 91d9d81f50e5..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/getRequestBody.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { toJson } from "../json.js"; -import { toQueryString } from "../url/qs.js"; - -export declare namespace GetRequestBody { - interface Args { - body: unknown; - type: "json" | "file" | "bytes" | "form" | "other"; - } -} - -export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { - if (type === "form") { - return toQueryString(body, { arrayFormat: "repeat", encode: true }); - } - if (type.includes("json")) { - return toJson(body); - } else { - return body as BodyInit; - } -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts deleted file mode 100644 index 708d55728f2b..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/getResponseBody.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { fromJson } from "../json.js"; -import { getBinaryResponse } from "./BinaryResponse.js"; - -export async function getResponseBody(response: Response, responseType?: string): Promise { - switch (responseType) { - case "binary-response": - return getBinaryResponse(response); - case "blob": - return await response.blob(); - case "arrayBuffer": - return await response.arrayBuffer(); - case "sse": - if (response.body == null) { - return { - ok: false, - error: { - reason: "body-is-null", - statusCode: response.status, - }, - }; - } - return response.body; - case "streaming": - if (response.body == null) { - return { - ok: false, - error: { - reason: "body-is-null", - statusCode: response.status, - }, - }; - } - - return response.body; - - case "text": - return await response.text(); - } - - // if responseType is "json" or not specified, try to parse as JSON - const text = await response.text(); - if (text.length > 0) { - try { - const responseBody = fromJson(text); - return responseBody; - } catch (_err) { - return { - ok: false, - error: { - reason: "non-json", - statusCode: response.status, - rawBody: text, - }, - }; - } - } - return undefined; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/index.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/index.ts deleted file mode 100644 index bd5db362c778..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type { APIResponse } from "./APIResponse.js"; -export type { BinaryResponse } from "./BinaryResponse.js"; -export type { EndpointMetadata } from "./EndpointMetadata.js"; -export { EndpointSupplier } from "./EndpointSupplier.js"; -export type { Fetcher, FetchFunction } from "./Fetcher.js"; -export { fetcher } from "./Fetcher.js"; -export { getHeader } from "./getHeader.js"; -export { HttpResponsePromise } from "./HttpResponsePromise.js"; -export type { PassthroughRequest } from "./makePassthroughRequest.js"; -export { makePassthroughRequest } from "./makePassthroughRequest.js"; -export type { RawResponse, WithRawResponse } from "./RawResponse.js"; -export { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; -export { Supplier } from "./Supplier.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts deleted file mode 100644 index f5ba761400f8..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/makePassthroughRequest.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; -import { join } from "../url/join.js"; -import { EndpointSupplier } from "./EndpointSupplier.js"; -import { getFetchFn } from "./getFetchFn.js"; -import { makeRequest } from "./makeRequest.js"; -import { requestWithRetries } from "./requestWithRetries.js"; -import { Supplier } from "./Supplier.js"; - -export declare namespace PassthroughRequest { - /** - * Per-request options that can override the SDK client defaults. - */ - export interface RequestOptions { - /** Override the default timeout for this request (in seconds). */ - timeoutInSeconds?: number; - /** Override the default number of retries for this request. */ - maxRetries?: number; - /** Additional headers to include in this request. */ - headers?: Record; - /** Abort signal for this request. */ - abortSignal?: AbortSignal; - } - - /** - * SDK client configuration used by the passthrough fetch method. - */ - export interface ClientOptions { - /** The base URL or environment for the client. */ - environment?: Supplier; - /** Override the base URL. */ - baseUrl?: Supplier; - /** Default headers to include in requests. */ - headers?: Record; - /** Default maximum time to wait for a response in seconds. */ - timeoutInSeconds?: number; - /** Default number of times to retry the request. Defaults to 2. */ - maxRetries?: number; - /** A custom fetch function. */ - fetch?: typeof fetch; - /** Logging configuration. */ - logging?: LogConfig | Logger; - /** A function that returns auth headers. */ - getAuthHeaders?: () => Promise>; - } -} - -/** - * Makes a passthrough HTTP request using the SDK's configuration (auth, retry, logging, etc.) - * while mimicking the standard `fetch` API. - * - * @param input - The URL, path, or Request object. If a relative path, it will be resolved against the configured base URL. - * @param init - Standard RequestInit options (method, headers, body, signal, etc.) - * @param clientOptions - SDK client options (auth, default headers, logging, etc.) - * @param requestOptions - Per-request overrides (timeout, retries, extra headers, abort signal). - * @returns A standard Response object. - */ -export async function makePassthroughRequest( - input: Request | string | URL, - init: RequestInit | undefined, - clientOptions: PassthroughRequest.ClientOptions, - requestOptions?: PassthroughRequest.RequestOptions, -): Promise { - const logger = createLogger(clientOptions.logging); - - // Extract URL and default init properties from Request object if provided - let url: string; - let effectiveInit: RequestInit | undefined = init; - if (input instanceof Request) { - url = input.url; - // If no explicit init provided, extract properties from the Request object - if (init == null) { - effectiveInit = { - method: input.method, - headers: Object.fromEntries(input.headers.entries()), - body: input.body, - signal: input.signal, - credentials: input.credentials, - cache: input.cache as RequestCache, - redirect: input.redirect, - referrer: input.referrer, - integrity: input.integrity, - mode: input.mode, - }; - } - } else { - url = input instanceof URL ? input.toString() : input; - } - - // Resolve the base URL - const baseUrl = - (clientOptions.baseUrl != null ? await Supplier.get(clientOptions.baseUrl) : undefined) ?? - (clientOptions.environment != null ? await Supplier.get(clientOptions.environment) : undefined); - - // Determine the full URL - let fullUrl: string; - if (url.startsWith("http://") || url.startsWith("https://")) { - fullUrl = url; - } else if (baseUrl != null) { - fullUrl = join(baseUrl, url); - } else { - fullUrl = url; - } - - // Merge headers: SDK default headers -> auth headers -> user-provided headers - const mergedHeaders: Record = {}; - - // Apply SDK default headers (resolve suppliers) - if (clientOptions.headers != null) { - for (const [key, value] of Object.entries(clientOptions.headers)) { - const resolved = await EndpointSupplier.get(value, { endpointMetadata: {} }); - if (resolved != null) { - mergedHeaders[key.toLowerCase()] = `${resolved}`; - } - } - } - - // Apply auth headers - if (clientOptions.getAuthHeaders != null) { - const authHeaders = await clientOptions.getAuthHeaders(); - for (const [key, value] of Object.entries(authHeaders)) { - mergedHeaders[key.toLowerCase()] = value; - } - } - - // Apply user-provided headers from init - if (effectiveInit?.headers != null) { - const initHeaders = - effectiveInit.headers instanceof Headers - ? Object.fromEntries(effectiveInit.headers.entries()) - : Array.isArray(effectiveInit.headers) - ? Object.fromEntries(effectiveInit.headers) - : effectiveInit.headers; - for (const [key, value] of Object.entries(initHeaders)) { - if (value != null) { - mergedHeaders[key.toLowerCase()] = value; - } - } - } - - // Apply per-request option headers (highest priority) - if (requestOptions?.headers != null) { - for (const [key, value] of Object.entries(requestOptions.headers)) { - mergedHeaders[key.toLowerCase()] = value; - } - } - - const method = effectiveInit?.method ?? "GET"; - const body = effectiveInit?.body; - const timeoutInSeconds = requestOptions?.timeoutInSeconds ?? clientOptions.timeoutInSeconds; - const timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : undefined; - const maxRetries = requestOptions?.maxRetries ?? clientOptions.maxRetries; - const abortSignal = requestOptions?.abortSignal ?? effectiveInit?.signal ?? undefined; - const fetchFn = clientOptions.fetch ?? (await getFetchFn()); - - if (logger.isDebug()) { - logger.debug("Making passthrough HTTP request", { - method, - url: fullUrl, - hasBody: body != null, - }); - } - - const response = await requestWithRetries( - async () => - makeRequest( - fetchFn, - fullUrl, - method, - mergedHeaders, - body ?? undefined, - timeoutMs, - abortSignal, - effectiveInit?.credentials === "include", - undefined, // duplex - false, // disableCache - ), - maxRetries, - ); - - if (logger.isDebug()) { - logger.debug("Passthrough HTTP request completed", { - method, - url: fullUrl, - statusCode: response.status, - }); - } - - return response; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts deleted file mode 100644 index 360a86df40ad..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/makeRequest.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { anySignal, getTimeoutSignal } from "./signals.js"; - -/** - * Cached result of checking whether the current runtime supports - * the `cache` option in `Request`. Some runtimes (e.g. Cloudflare Workers) - * throw a TypeError when this option is used. - */ -let _cacheNoStoreSupported: boolean | undefined; -export function isCacheNoStoreSupported(): boolean { - if (_cacheNoStoreSupported != null) { - return _cacheNoStoreSupported; - } - try { - new Request("http://localhost", { cache: "no-store" }); - _cacheNoStoreSupported = true; - } catch { - _cacheNoStoreSupported = false; - } - return _cacheNoStoreSupported; -} - -/** - * Reset the cached result of `isCacheNoStoreSupported`. Exposed for testing only. - */ -export function resetCacheNoStoreSupported(): void { - _cacheNoStoreSupported = undefined; -} - -export const makeRequest = async ( - fetchFn: (url: string, init: RequestInit) => Promise, - url: string, - method: string, - headers: Headers | Record, - requestBody: BodyInit | undefined, - timeoutMs?: number, - abortSignal?: AbortSignal, - withCredentials?: boolean, - duplex?: "half", - disableCache?: boolean, -): Promise => { - const signals: AbortSignal[] = []; - - let timeoutAbortId: ReturnType | undefined; - if (timeoutMs != null) { - const { signal, abortId } = getTimeoutSignal(timeoutMs); - timeoutAbortId = abortId; - signals.push(signal); - } - - if (abortSignal != null) { - signals.push(abortSignal); - } - const newSignals = anySignal(signals); - const response = await fetchFn(url, { - method: method, - headers, - body: requestBody, - signal: newSignals, - credentials: withCredentials ? "include" : undefined, - // @ts-ignore - duplex, - ...(disableCache && isCacheNoStoreSupported() ? { cache: "no-store" as RequestCache } : {}), - }); - - if (timeoutAbortId != null) { - clearTimeout(timeoutAbortId); - } - - return response; -}; diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts deleted file mode 100644 index 1f689688c4b2..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts +++ /dev/null @@ -1,64 +0,0 @@ -const INITIAL_RETRY_DELAY = 1000; // in milliseconds -const MAX_RETRY_DELAY = 60000; // in milliseconds -const DEFAULT_MAX_RETRIES = 2; -const JITTER_FACTOR = 0.2; // 20% random jitter - -function addPositiveJitter(delay: number): number { - const jitterMultiplier = 1 + Math.random() * JITTER_FACTOR; - return delay * jitterMultiplier; -} - -function addSymmetricJitter(delay: number): number { - const jitterMultiplier = 1 + (Math.random() - 0.5) * JITTER_FACTOR; - return delay * jitterMultiplier; -} - -function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { - const retryAfter = response.headers.get("Retry-After"); - if (retryAfter) { - const retryAfterSeconds = parseInt(retryAfter, 10); - if (!Number.isNaN(retryAfterSeconds) && retryAfterSeconds > 0) { - return Math.min(retryAfterSeconds * 1000, MAX_RETRY_DELAY); - } - - const retryAfterDate = new Date(retryAfter); - if (!Number.isNaN(retryAfterDate.getTime())) { - const delay = retryAfterDate.getTime() - Date.now(); - if (delay > 0) { - return Math.min(Math.max(delay, 0), MAX_RETRY_DELAY); - } - } - } - - const rateLimitReset = response.headers.get("X-RateLimit-Reset"); - if (rateLimitReset) { - const resetTime = parseInt(rateLimitReset, 10); - if (!Number.isNaN(resetTime)) { - const delay = resetTime * 1000 - Date.now(); - if (delay > 0) { - return addPositiveJitter(Math.min(delay, MAX_RETRY_DELAY)); - } - } - } - - return addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** retryAttempt, MAX_RETRY_DELAY)); -} - -export async function requestWithRetries( - requestFn: () => Promise, - maxRetries: number = DEFAULT_MAX_RETRIES, -): Promise { - let response: Response = await requestFn(); - - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { - const delay = getRetryDelayFromHeaders(response, i); - - await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; - } - } - return response!; -} diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts deleted file mode 100644 index 7bd3757ec3a7..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/signals.ts +++ /dev/null @@ -1,26 +0,0 @@ -const TIMEOUT = "timeout"; - -export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: ReturnType } { - const controller = new AbortController(); - const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); - return { signal: controller.signal, abortId }; -} - -export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { - const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args) as AbortSignal[]; - - const controller = new AbortController(); - - for (const signal of signals) { - if (signal.aborted) { - controller.abort((signal as any)?.reason); - break; - } - - signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { - signal: controller.signal, - }); - } - - return controller.signal; -} diff --git a/seed/ts-sdk/allof-inline/src/core/headers.ts b/seed/ts-sdk/allof-inline/src/core/headers.ts deleted file mode 100644 index be45c4552a35..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/headers.ts +++ /dev/null @@ -1,33 +0,0 @@ -export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { - const result: Record = {}; - - for (const [key, value] of headersArray - .filter((headers) => headers != null) - .flatMap((headers) => Object.entries(headers))) { - const insensitiveKey = key.toLowerCase(); - if (value != null) { - result[insensitiveKey] = value; - } else if (insensitiveKey in result) { - delete result[insensitiveKey]; - } - } - - return result; -} - -export function mergeOnlyDefinedHeaders( - ...headersArray: (Record | null | undefined)[] -): Record { - const result: Record = {}; - - for (const [key, value] of headersArray - .filter((headers) => headers != null) - .flatMap((headers) => Object.entries(headers))) { - const insensitiveKey = key.toLowerCase(); - if (value != null) { - result[insensitiveKey] = value; - } - } - - return result; -} diff --git a/seed/ts-sdk/allof-inline/src/core/index.ts b/seed/ts-sdk/allof-inline/src/core/index.ts deleted file mode 100644 index afa8351fcf85..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./fetcher/index.js"; -export * as logging from "./logging/index.js"; -export * from "./runtime/index.js"; -export * as url from "./url/index.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/json.ts b/seed/ts-sdk/allof-inline/src/core/json.ts deleted file mode 100644 index c052f3249f4f..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/json.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Serialize a value to JSON - * @param value A JavaScript value, usually an object or array, to be converted. - * @param replacer A function that transforms the results. - * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. - * @returns JSON string - */ -export const toJson = ( - value: unknown, - replacer?: (this: unknown, key: string, value: unknown) => unknown, - space?: string | number, -): string => { - return JSON.stringify(value, replacer, space); -}; - -/** - * Parse JSON string to object, array, or other type - * @param text A valid JSON string. - * @param reviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is. - * @returns Parsed object, array, or other type - */ -export function fromJson( - text: string, - reviver?: (this: unknown, key: string, value: unknown) => unknown, -): T { - return JSON.parse(text, reviver); -} diff --git a/seed/ts-sdk/allof-inline/src/core/logging/exports.ts b/seed/ts-sdk/allof-inline/src/core/logging/exports.ts deleted file mode 100644 index 88f6c00db0cf..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/logging/exports.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as logger from "./logger.js"; - -export namespace logging { - /** - * Configuration for logger instances. - */ - export type LogConfig = logger.LogConfig; - export type LogLevel = logger.LogLevel; - export const LogLevel: typeof logger.LogLevel = logger.LogLevel; - export type ILogger = logger.ILogger; - /** - * Console logger implementation that outputs to the console. - */ - export type ConsoleLogger = logger.ConsoleLogger; - /** - * Console logger implementation that outputs to the console. - */ - export const ConsoleLogger: typeof logger.ConsoleLogger = logger.ConsoleLogger; -} diff --git a/seed/ts-sdk/allof-inline/src/core/logging/index.ts b/seed/ts-sdk/allof-inline/src/core/logging/index.ts deleted file mode 100644 index d81cc32c40f9..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/logging/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./logger.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/logging/logger.ts b/seed/ts-sdk/allof-inline/src/core/logging/logger.ts deleted file mode 100644 index a3f3673cda93..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/logging/logger.ts +++ /dev/null @@ -1,203 +0,0 @@ -export const LogLevel = { - Debug: "debug", - Info: "info", - Warn: "warn", - Error: "error", -} as const; -export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; -const logLevelMap: Record = { - [LogLevel.Debug]: 1, - [LogLevel.Info]: 2, - [LogLevel.Warn]: 3, - [LogLevel.Error]: 4, -}; - -export interface ILogger { - /** - * Logs a debug message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - debug(message: string, ...args: unknown[]): void; - /** - * Logs an info message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - info(message: string, ...args: unknown[]): void; - /** - * Logs a warning message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - warn(message: string, ...args: unknown[]): void; - /** - * Logs an error message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - error(message: string, ...args: unknown[]): void; -} - -/** - * Configuration for logger initialization. - */ -export interface LogConfig { - /** - * Minimum log level to output. - * @default LogLevel.Info - */ - level?: LogLevel; - /** - * Logger implementation to use. - * @default new ConsoleLogger() - */ - logger?: ILogger; - /** - * Whether logging should be silenced. - * @default true - */ - silent?: boolean; -} - -/** - * Default console-based logger implementation. - */ -export class ConsoleLogger implements ILogger { - debug(message: string, ...args: unknown[]): void { - console.debug(message, ...args); - } - info(message: string, ...args: unknown[]): void { - console.info(message, ...args); - } - warn(message: string, ...args: unknown[]): void { - console.warn(message, ...args); - } - error(message: string, ...args: unknown[]): void { - console.error(message, ...args); - } -} - -/** - * Logger class that provides level-based logging functionality. - */ -export class Logger { - private readonly level: number; - private readonly logger: ILogger; - private readonly silent: boolean; - - /** - * Creates a new logger instance. - * @param config - Logger configuration - */ - constructor(config: Required) { - this.level = logLevelMap[config.level]; - this.logger = config.logger; - this.silent = config.silent; - } - - /** - * Checks if a log level should be output based on configuration. - * @param level - The log level to check - * @returns True if the level should be logged - */ - public shouldLog(level: LogLevel): boolean { - return !this.silent && this.level <= logLevelMap[level]; - } - - /** - * Checks if debug logging is enabled. - * @returns True if debug logs should be output - */ - public isDebug(): boolean { - return this.shouldLog(LogLevel.Debug); - } - - /** - * Logs a debug message if debug logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public debug(message: string, ...args: unknown[]): void { - if (this.isDebug()) { - this.logger.debug(message, ...args); - } - } - - /** - * Checks if info logging is enabled. - * @returns True if info logs should be output - */ - public isInfo(): boolean { - return this.shouldLog(LogLevel.Info); - } - - /** - * Logs an info message if info logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public info(message: string, ...args: unknown[]): void { - if (this.isInfo()) { - this.logger.info(message, ...args); - } - } - - /** - * Checks if warning logging is enabled. - * @returns True if warning logs should be output - */ - public isWarn(): boolean { - return this.shouldLog(LogLevel.Warn); - } - - /** - * Logs a warning message if warning logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public warn(message: string, ...args: unknown[]): void { - if (this.isWarn()) { - this.logger.warn(message, ...args); - } - } - - /** - * Checks if error logging is enabled. - * @returns True if error logs should be output - */ - public isError(): boolean { - return this.shouldLog(LogLevel.Error); - } - - /** - * Logs an error message if error logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public error(message: string, ...args: unknown[]): void { - if (this.isError()) { - this.logger.error(message, ...args); - } - } -} - -export function createLogger(config?: LogConfig | Logger): Logger { - if (config == null) { - return defaultLogger; - } - if (config instanceof Logger) { - return config; - } - config = config ?? {}; - config.level ??= LogLevel.Info; - config.logger ??= new ConsoleLogger(); - config.silent ??= true; - return new Logger(config as Required); -} - -const defaultLogger: Logger = new Logger({ - level: LogLevel.Info, - logger: new ConsoleLogger(), - silent: true, -}); diff --git a/seed/ts-sdk/allof-inline/src/core/runtime/index.ts b/seed/ts-sdk/allof-inline/src/core/runtime/index.ts deleted file mode 100644 index cfab23f9a834..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/runtime/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RUNTIME } from "./runtime.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts b/seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts deleted file mode 100644 index e6e66b2a7bce..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/runtime/runtime.ts +++ /dev/null @@ -1,134 +0,0 @@ -interface DenoGlobal { - version: { - deno: string; - }; -} - -interface BunGlobal { - version: string; -} - -declare const Deno: DenoGlobal | undefined; -declare const Bun: BunGlobal | undefined; -declare const EdgeRuntime: string | undefined; -declare const self: typeof globalThis.self & { - importScripts?: unknown; -}; - -/** - * A constant that indicates which environment and version the SDK is running in. - */ -export const RUNTIME: Runtime = evaluateRuntime(); - -export interface Runtime { - type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd" | "edge-runtime"; - version?: string; - parsedVersion?: number; -} - -function evaluateRuntime(): Runtime { - /** - * A constant that indicates whether the environment the code is running is a Web Browser. - */ - const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; - if (isBrowser) { - return { - type: "browser", - version: window.navigator.userAgent, - }; - } - - /** - * A constant that indicates whether the environment the code is running is Cloudflare. - * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent - */ - const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; - if (isCloudflare) { - return { - type: "workerd", - }; - } - - /** - * A constant that indicates whether the environment the code is running is Edge Runtime. - * https://vercel.com/docs/functions/runtimes/edge-runtime#check-if-you're-running-on-the-edge-runtime - */ - const isEdgeRuntime = typeof EdgeRuntime === "string"; - if (isEdgeRuntime) { - return { - type: "edge-runtime", - }; - } - - /** - * A constant that indicates whether the environment the code is running is a Web Worker. - */ - const isWebWorker = - typeof self === "object" && - typeof self?.importScripts === "function" && - (self.constructor?.name === "DedicatedWorkerGlobalScope" || - self.constructor?.name === "ServiceWorkerGlobalScope" || - self.constructor?.name === "SharedWorkerGlobalScope"); - if (isWebWorker) { - return { - type: "web-worker", - }; - } - - /** - * A constant that indicates whether the environment the code is running is Deno. - * FYI Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions - */ - const isDeno = - typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; - if (isDeno) { - return { - type: "deno", - version: Deno.version.deno, - }; - } - - /** - * A constant that indicates whether the environment the code is running is Bun.sh. - */ - const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; - if (isBun) { - return { - type: "bun", - version: Bun.version, - }; - } - - /** - * A constant that indicates whether the environment the code is running is in React-Native. - * This check should come before Node.js detection since React Native may have a process polyfill. - * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js - */ - const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; - if (isReactNative) { - return { - type: "react-native", - }; - } - - /** - * A constant that indicates whether the environment the code is running is Node.JS. - * - * We assign `process` to a local variable first to avoid being flagged by - * bundlers that perform static analysis on `process.versions` (e.g. Next.js - * Edge Runtime warns about Node.js APIs even when they are guarded). - */ - const _process = typeof process !== "undefined" ? process : undefined; - const isNode = typeof _process !== "undefined" && typeof _process.versions?.node === "string"; - if (isNode) { - return { - type: "node", - version: _process.versions.node, - parsedVersion: Number(_process.versions.node.split(".")[0]), - }; - } - - return { - type: "unknown", - }; -} diff --git a/seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts b/seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts deleted file mode 100644 index 19b901244218..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/url/encodePathParam.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function encodePathParam(param: unknown): string { - if (param === null) { - return "null"; - } - const typeofParam = typeof param; - switch (typeofParam) { - case "undefined": - return "undefined"; - case "string": - case "number": - case "boolean": - break; - default: - param = String(param); - break; - } - return encodeURIComponent(param as string | number | boolean); -} diff --git a/seed/ts-sdk/allof-inline/src/core/url/index.ts b/seed/ts-sdk/allof-inline/src/core/url/index.ts deleted file mode 100644 index f2e0fa2d2221..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/url/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { encodePathParam } from "./encodePathParam.js"; -export { join } from "./join.js"; -export { toQueryString } from "./qs.js"; diff --git a/seed/ts-sdk/allof-inline/src/core/url/join.ts b/seed/ts-sdk/allof-inline/src/core/url/join.ts deleted file mode 100644 index 7ca7daef094d..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/url/join.ts +++ /dev/null @@ -1,79 +0,0 @@ -export function join(base: string, ...segments: string[]): string { - if (!base) { - return ""; - } - - if (segments.length === 0) { - return base; - } - - if (base.includes("://")) { - let url: URL; - try { - url = new URL(base); - } catch { - return joinPath(base, ...segments); - } - - const lastSegment = segments[segments.length - 1]; - const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); - - for (const segment of segments) { - const cleanSegment = trimSlashes(segment); - if (cleanSegment) { - url.pathname = joinPathSegments(url.pathname, cleanSegment); - } - } - - if (shouldPreserveTrailingSlash && !url.pathname.endsWith("/")) { - url.pathname += "/"; - } - - return url.toString(); - } - - return joinPath(base, ...segments); -} - -function joinPath(base: string, ...segments: string[]): string { - if (segments.length === 0) { - return base; - } - - let result = base; - - const lastSegment = segments[segments.length - 1]; - const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); - - for (const segment of segments) { - const cleanSegment = trimSlashes(segment); - if (cleanSegment) { - result = joinPathSegments(result, cleanSegment); - } - } - - if (shouldPreserveTrailingSlash && !result.endsWith("/")) { - result += "/"; - } - - return result; -} - -function joinPathSegments(left: string, right: string): string { - if (left.endsWith("/")) { - return left + right; - } - return `${left}/${right}`; -} - -function trimSlashes(str: string): string { - if (!str) return str; - - let start = 0; - let end = str.length; - - if (str.startsWith("/")) start = 1; - if (str.endsWith("/")) end = str.length - 1; - - return start === 0 && end === str.length ? str : str.slice(start, end); -} diff --git a/seed/ts-sdk/allof-inline/src/core/url/qs.ts b/seed/ts-sdk/allof-inline/src/core/url/qs.ts deleted file mode 100644 index 13e89be9d9a6..000000000000 --- a/seed/ts-sdk/allof-inline/src/core/url/qs.ts +++ /dev/null @@ -1,74 +0,0 @@ -interface QueryStringOptions { - arrayFormat?: "indices" | "repeat"; - encode?: boolean; -} - -const defaultQsOptions: Required = { - arrayFormat: "indices", - encode: true, -} as const; - -function encodeValue(value: unknown, shouldEncode: boolean): string { - if (value === undefined) { - return ""; - } - if (value === null) { - return ""; - } - const stringValue = String(value); - return shouldEncode ? encodeURIComponent(stringValue) : stringValue; -} - -function stringifyObject(obj: Record, prefix = "", options: Required): string[] { - const parts: string[] = []; - - for (const [key, value] of Object.entries(obj)) { - const fullKey = prefix ? `${prefix}[${key}]` : key; - - if (value === undefined) { - continue; - } - - if (Array.isArray(value)) { - if (value.length === 0) { - continue; - } - for (let i = 0; i < value.length; i++) { - const item = value[i]; - if (item === undefined) { - continue; - } - if (typeof item === "object" && !Array.isArray(item) && item !== null) { - const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; - parts.push(...stringifyObject(item as Record, arrayKey, options)); - } else { - const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; - const encodedKey = options.encode ? encodeURIComponent(arrayKey) : arrayKey; - parts.push(`${encodedKey}=${encodeValue(item, options.encode)}`); - } - } - } else if (typeof value === "object" && value !== null) { - if (Object.keys(value as Record).length === 0) { - continue; - } - parts.push(...stringifyObject(value as Record, fullKey, options)); - } else { - const encodedKey = options.encode ? encodeURIComponent(fullKey) : fullKey; - parts.push(`${encodedKey}=${encodeValue(value, options.encode)}`); - } - } - - return parts; -} - -export function toQueryString(obj: unknown, options?: QueryStringOptions): string { - if (obj == null || typeof obj !== "object") { - return ""; - } - - const parts = stringifyObject(obj as Record, "", { - ...defaultQsOptions, - ...options, - }); - return parts.join("&"); -} diff --git a/seed/ts-sdk/allof-inline/src/environments.ts b/seed/ts-sdk/allof-inline/src/environments.ts deleted file mode 100644 index 92d0fc94dd2c..000000000000 --- a/seed/ts-sdk/allof-inline/src/environments.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export const SeedApiEnvironment = { - Default: "https://api.example.com", -} as const; - -export type SeedApiEnvironment = typeof SeedApiEnvironment.Default; diff --git a/seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts b/seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts deleted file mode 100644 index ec2bc570e2a7..000000000000 --- a/seed/ts-sdk/allof-inline/src/errors/SeedApiError.ts +++ /dev/null @@ -1,64 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as core from "../core/index.js"; -import { toJson } from "../core/json.js"; - -export class SeedApiError extends Error { - public readonly statusCode?: number; - public readonly body?: unknown; - public readonly rawResponse?: core.RawResponse; - public readonly cause?: unknown; - - constructor({ - message, - statusCode, - body, - rawResponse, - cause, - }: { - message?: string; - statusCode?: number; - body?: unknown; - rawResponse?: core.RawResponse; - cause?: unknown; - }) { - super(buildMessage({ message, statusCode, body })); - Object.setPrototypeOf(this, new.target.prototype); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - - this.name = this.constructor.name; - this.statusCode = statusCode; - this.body = body; - this.rawResponse = rawResponse; - if (cause != null) { - this.cause = cause; - } - } -} - -function buildMessage({ - message, - statusCode, - body, -}: { - message: string | undefined; - statusCode: number | undefined; - body: unknown | undefined; -}): string { - const lines: string[] = []; - if (message != null) { - lines.push(message); - } - - if (statusCode != null) { - lines.push(`Status code: ${statusCode.toString()}`); - } - - if (body != null) { - lines.push(`Body: ${toJson(body, undefined, 2)}`); - } - - return lines.join("\n"); -} diff --git a/seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts b/seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts deleted file mode 100644 index f8f6a5f95430..000000000000 --- a/seed/ts-sdk/allof-inline/src/errors/SeedApiTimeoutError.ts +++ /dev/null @@ -1,18 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export class SeedApiTimeoutError extends Error { - public readonly cause?: unknown; - - constructor(message: string, opts?: { cause?: unknown }) { - super(message); - Object.setPrototypeOf(this, new.target.prototype); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - - this.name = this.constructor.name; - if (opts?.cause != null) { - this.cause = opts.cause; - } - } -} diff --git a/seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts deleted file mode 100644 index 27d1ebec132d..000000000000 --- a/seed/ts-sdk/allof-inline/src/errors/handleNonStatusCodeError.ts +++ /dev/null @@ -1,40 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as core from "../core/index.js"; -import * as errors from "./index.js"; - -export function handleNonStatusCodeError( - error: core.Fetcher.Error, - rawResponse: core.RawResponse, - method: string, - path: string, -): never { - switch (error.reason) { - case "non-json": - throw new errors.SeedApiError({ - statusCode: error.statusCode, - body: error.rawBody, - rawResponse: rawResponse, - }); - case "body-is-null": - throw new errors.SeedApiError({ - statusCode: error.statusCode, - rawResponse: rawResponse, - }); - case "timeout": - throw new errors.SeedApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`, { - cause: error.cause, - }); - case "unknown": - throw new errors.SeedApiError({ - message: error.errorMessage, - rawResponse: rawResponse, - cause: error.cause, - }); - default: - throw new errors.SeedApiError({ - message: "Unknown error", - rawResponse: rawResponse, - }); - } -} diff --git a/seed/ts-sdk/allof-inline/src/errors/index.ts b/seed/ts-sdk/allof-inline/src/errors/index.ts deleted file mode 100644 index 09e82b954c26..000000000000 --- a/seed/ts-sdk/allof-inline/src/errors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { SeedApiError } from "./SeedApiError.js"; -export { SeedApiTimeoutError } from "./SeedApiTimeoutError.js"; diff --git a/seed/ts-sdk/allof-inline/src/exports.ts b/seed/ts-sdk/allof-inline/src/exports.ts deleted file mode 100644 index 7b70ee14fc02..000000000000 --- a/seed/ts-sdk/allof-inline/src/exports.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./core/exports.js"; diff --git a/seed/ts-sdk/allof-inline/src/index.ts b/seed/ts-sdk/allof-inline/src/index.ts deleted file mode 100644 index a11386c163bd..000000000000 --- a/seed/ts-sdk/allof-inline/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * as SeedApi from "./api/index.js"; -export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; -export { SeedApiClient } from "./Client.js"; -export { SeedApiEnvironment } from "./environments.js"; -export { SeedApiError, SeedApiTimeoutError } from "./errors/index.js"; -export * from "./exports.js"; diff --git a/seed/ts-sdk/allof-inline/src/version.ts b/seed/ts-sdk/allof-inline/src/version.ts deleted file mode 100644 index b643a3e3ea27..000000000000 --- a/seed/ts-sdk/allof-inline/src/version.ts +++ /dev/null @@ -1 +0,0 @@ -export const SDK_VERSION = "0.0.1"; diff --git a/seed/ts-sdk/allof-inline/tests/custom.test.ts b/seed/ts-sdk/allof-inline/tests/custom.test.ts deleted file mode 100644 index 7f5e031c8396..000000000000 --- a/seed/ts-sdk/allof-inline/tests/custom.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This is a custom test file, if you wish to add more tests - * to your SDK. - * Be sure to mark this file in `.fernignore`. - * - * If you include example requests/responses in your fern definition, - * you will have tests automatically generated for you. - */ -describe("test", () => { - it("default", () => { - expect(true).toBe(true); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts b/seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts deleted file mode 100644 index 954872157d52..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/MockServer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { RequestHandlerOptions } from "msw"; -import type { SetupServer } from "msw/node"; - -import { mockEndpointBuilder } from "./mockEndpointBuilder"; - -export interface MockServerOptions { - baseUrl: string; - server: SetupServer; -} - -export class MockServer { - private readonly server: SetupServer; - public readonly baseUrl: string; - - constructor({ baseUrl, server }: MockServerOptions) { - this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl; - this.server = server; - } - - public mockEndpoint(options?: RequestHandlerOptions): ReturnType { - const builder = mockEndpointBuilder({ - once: options?.once ?? true, - onBuild: (handler) => { - this.server.use(handler); - }, - }).baseUrl(this.baseUrl); - return builder; - } -} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts b/seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts deleted file mode 100644 index d7d891a2d80b..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/MockServerPool.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { setupServer } from "msw/node"; - -import { fromJson, toJson } from "../../src/core/json"; -import { MockServer } from "./MockServer"; -import { randomBaseUrl } from "./randomBaseUrl"; - -const mswServer = setupServer(); -interface MockServerOptions { - baseUrl?: string; -} - -async function formatHttpRequest(request: Request, id?: string): Promise { - try { - const clone = request.clone(); - const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); - - let body = ""; - try { - const contentType = clone.headers.get("content-type"); - if (contentType?.includes("application/json")) { - body = toJson(fromJson(await clone.text()), undefined, 2); - } else if (clone.body) { - body = await clone.text(); - } - } catch (_e) { - body = "(unable to parse body)"; - } - - const title = id ? `### Request ${id} ###\n` : ""; - const firstLine = `${title}${request.method} ${request.url.toString()} HTTP/1.1`; - - return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; - } catch (e) { - return `Error formatting request: ${e}`; - } -} - -async function formatHttpResponse(response: Response, id?: string): Promise { - try { - const clone = response.clone(); - const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); - - let body = ""; - try { - const contentType = clone.headers.get("content-type"); - if (contentType?.includes("application/json")) { - body = toJson(fromJson(await clone.text()), undefined, 2); - } else if (clone.body) { - body = await clone.text(); - } - } catch (_e) { - body = "(unable to parse body)"; - } - - const title = id ? `### Response for ${id} ###\n` : ""; - const firstLine = `${title}HTTP/1.1 ${response.status} ${response.statusText}`; - - return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; - } catch (e) { - return `Error formatting response: ${e}`; - } -} - -class MockServerPool { - private servers: MockServer[] = []; - - public createServer(options?: Partial): MockServer { - const baseUrl = options?.baseUrl || randomBaseUrl(); - const server = new MockServer({ baseUrl, server: mswServer }); - this.servers.push(server); - return server; - } - - public getServers(): MockServer[] { - return [...this.servers]; - } - - public listen(): void { - const onUnhandledRequest = process.env.LOG_LEVEL === "debug" ? "warn" : "bypass"; - mswServer.listen({ onUnhandledRequest }); - - if (process.env.LOG_LEVEL === "debug") { - mswServer.events.on("request:start", async ({ request, requestId }) => { - const formattedRequest = await formatHttpRequest(request, requestId); - console.debug(`request:start\n${formattedRequest}`); - }); - - mswServer.events.on("request:unhandled", async ({ request, requestId }) => { - const formattedRequest = await formatHttpRequest(request, requestId); - console.debug(`request:unhandled\n${formattedRequest}`); - }); - - mswServer.events.on("response:mocked", async ({ request, response, requestId }) => { - const formattedResponse = await formatHttpResponse(response, requestId); - console.debug(`response:mocked\n${formattedResponse}`); - }); - } - } - - public close(): void { - this.servers = []; - mswServer.close(); - } -} - -export const mockServerPool: MockServerPool = new MockServerPool(); diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts b/seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts deleted file mode 100644 index 3e8540a3ba5a..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/mockEndpointBuilder.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { type DefaultBodyType, type HttpHandler, HttpResponse, type HttpResponseResolver, http } from "msw"; - -import { url } from "../../src/core"; -import { toJson } from "../../src/core/json"; -import { type WithFormUrlEncodedOptions, withFormUrlEncoded } from "./withFormUrlEncoded"; -import { withHeaders } from "./withHeaders"; -import { type WithJsonOptions, withJson } from "./withJson"; - -type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; - -interface MethodStage { - baseUrl(baseUrl: string): MethodStage; - all(path: string): RequestHeadersStage; - get(path: string): RequestHeadersStage; - post(path: string): RequestHeadersStage; - put(path: string): RequestHeadersStage; - delete(path: string): RequestHeadersStage; - patch(path: string): RequestHeadersStage; - options(path: string): RequestHeadersStage; - head(path: string): RequestHeadersStage; -} - -interface RequestHeadersStage extends RequestBodyStage, ResponseStage { - header(name: string, value: string): RequestHeadersStage; - headers(headers: Record): RequestBodyStage; -} - -interface RequestBodyStage extends ResponseStage { - jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage; - formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage; -} - -interface ResponseStage { - respondWith(): ResponseStatusStage; -} -interface ResponseStatusStage { - statusCode(statusCode: number): ResponseHeaderStage; -} - -interface ResponseHeaderStage extends ResponseBodyStage, BuildStage { - header(name: string, value: string): ResponseHeaderStage; - headers(headers: Record): ResponseHeaderStage; -} - -interface ResponseBodyStage { - jsonBody(body: unknown): BuildStage; - sseBody(body: string): BuildStage; -} - -interface BuildStage { - build(): HttpHandler; -} - -export interface HttpHandlerBuilderOptions { - onBuild?: (handler: HttpHandler) => void; - once?: boolean; -} - -class RequestBuilder implements MethodStage, RequestHeadersStage, RequestBodyStage, ResponseStage { - private method: HttpMethod = "get"; - private _baseUrl: string = ""; - private path: string = "/"; - private readonly predicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[] = []; - private readonly handlerOptions?: HttpHandlerBuilderOptions; - - constructor(options?: HttpHandlerBuilderOptions) { - this.handlerOptions = options; - } - - baseUrl(baseUrl: string): MethodStage { - this._baseUrl = baseUrl; - return this; - } - - all(path: string): RequestHeadersStage { - this.method = "all"; - this.path = path; - return this; - } - - get(path: string): RequestHeadersStage { - this.method = "get"; - this.path = path; - return this; - } - - post(path: string): RequestHeadersStage { - this.method = "post"; - this.path = path; - return this; - } - - put(path: string): RequestHeadersStage { - this.method = "put"; - this.path = path; - return this; - } - - delete(path: string): RequestHeadersStage { - this.method = "delete"; - this.path = path; - return this; - } - - patch(path: string): RequestHeadersStage { - this.method = "patch"; - this.path = path; - return this; - } - - options(path: string): RequestHeadersStage { - this.method = "options"; - this.path = path; - return this; - } - - head(path: string): RequestHeadersStage { - this.method = "head"; - this.path = path; - return this; - } - - header(name: string, value: string): RequestHeadersStage { - this.predicates.push((resolver) => withHeaders({ [name]: value }, resolver)); - return this; - } - - headers(headers: Record): RequestBodyStage { - this.predicates.push((resolver) => withHeaders(headers, resolver)); - return this; - } - - jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage { - if (body === undefined) { - throw new Error("Undefined is not valid JSON. Do not call jsonBody if you want an empty body."); - } - this.predicates.push((resolver) => withJson(body, resolver, options)); - return this; - } - - formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage { - if (body === undefined) { - throw new Error( - "Undefined is not valid for form-urlencoded. Do not call formUrlEncodedBody if you want an empty body.", - ); - } - this.predicates.push((resolver) => withFormUrlEncoded(body, resolver, options)); - return this; - } - - respondWith(): ResponseStatusStage { - return new ResponseBuilder(this.method, this.buildUrl(), this.predicates, this.handlerOptions); - } - - private buildUrl(): string { - return url.join(this._baseUrl, this.path); - } -} - -class ResponseBuilder implements ResponseStatusStage, ResponseHeaderStage, ResponseBodyStage, BuildStage { - private readonly method: HttpMethod; - private readonly url: string; - private readonly requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[]; - private readonly handlerOptions?: HttpHandlerBuilderOptions; - - private responseStatusCode: number = 200; - private responseHeaders: Record = {}; - private responseBody: DefaultBodyType = undefined; - - constructor( - method: HttpMethod, - url: string, - requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[], - options?: HttpHandlerBuilderOptions, - ) { - this.method = method; - this.url = url; - this.requestPredicates = requestPredicates; - this.handlerOptions = options; - } - - public statusCode(code: number): ResponseHeaderStage { - this.responseStatusCode = code; - return this; - } - - public header(name: string, value: string): ResponseHeaderStage { - this.responseHeaders[name] = value; - return this; - } - - public headers(headers: Record): ResponseHeaderStage { - this.responseHeaders = { ...this.responseHeaders, ...headers }; - return this; - } - - public jsonBody(body: unknown): BuildStage { - if (body === undefined) { - throw new Error("Undefined is not valid JSON. Do not call jsonBody if you expect an empty body."); - } - this.responseBody = toJson(body); - return this; - } - - public sseBody(body: string): BuildStage { - this.responseHeaders["Content-Type"] = "text/event-stream"; - this.responseBody = body; - return this; - } - - public build(): HttpHandler { - const responseResolver: HttpResponseResolver = () => { - const response = new HttpResponse(this.responseBody, { - status: this.responseStatusCode, - headers: this.responseHeaders, - }); - // if no Content-Type header is set, delete the default text content type that is set - if (Object.keys(this.responseHeaders).some((key) => key.toLowerCase() === "content-type") === false) { - response.headers.delete("Content-Type"); - } - return response; - }; - - const finalResolver = this.requestPredicates.reduceRight((acc, predicate) => predicate(acc), responseResolver); - - const handler = http[this.method](this.url, finalResolver, this.handlerOptions); - this.handlerOptions?.onBuild?.(handler); - return handler; - } -} - -export function mockEndpointBuilder(options?: HttpHandlerBuilderOptions): MethodStage { - return new RequestBuilder(options); -} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts b/seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts deleted file mode 100644 index 031aa6408aca..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/randomBaseUrl.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function randomBaseUrl(): string { - const randomString = Math.random().toString(36).substring(2, 15); - return `http://${randomString}.localhost`; -} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/setup.ts b/seed/ts-sdk/allof-inline/tests/mock-server/setup.ts deleted file mode 100644 index aeb3a95af7dc..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/setup.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { afterAll, beforeAll } from "vitest"; - -import { mockServerPool } from "./MockServerPool"; - -beforeAll(() => { - mockServerPool.listen(); -}); -afterAll(() => { - mockServerPool.close(); -}); diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts b/seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts deleted file mode 100644 index 2b23448e3102..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/withFormUrlEncoded.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { type HttpResponseResolver, passthrough } from "msw"; - -import { toJson } from "../../src/core/json"; - -export interface WithFormUrlEncodedOptions { - /** - * List of field names to ignore when comparing request bodies. - * This is useful for pagination cursor fields that change between requests. - */ - ignoredFields?: string[]; -} - -/** - * Creates a request matcher that validates if the request form-urlencoded body exactly matches the expected object - * @param expectedBody - The exact body object to match against - * @param resolver - Response resolver to execute if body matches - * @param options - Optional configuration including fields to ignore - */ -export function withFormUrlEncoded( - expectedBody: unknown, - resolver: HttpResponseResolver, - options?: WithFormUrlEncodedOptions, -): HttpResponseResolver { - const ignoredFields = options?.ignoredFields ?? []; - return async (args) => { - const { request } = args; - - let clonedRequest: Request; - let bodyText: string | undefined; - let actualBody: Record; - try { - clonedRequest = request.clone(); - bodyText = await clonedRequest.text(); - if (bodyText === "") { - // Empty body is valid if expected body is also empty - const isExpectedEmpty = - expectedBody != null && - typeof expectedBody === "object" && - Object.keys(expectedBody as Record).length === 0; - if (!isExpectedEmpty) { - console.error("Request body is empty, expected a form-urlencoded body."); - return passthrough(); - } - actualBody = {}; - } else { - const params = new URLSearchParams(bodyText); - actualBody = {}; - for (const [key, value] of params.entries()) { - actualBody[key] = value; - } - } - } catch (error) { - console.error(`Error processing form-urlencoded request body:\n\tError: ${error}\n\tBody: ${bodyText}`); - return passthrough(); - } - - const mismatches = findMismatches(actualBody, expectedBody); - const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); - if (filteredMismatches.length > 0) { - console.error("Form-urlencoded body mismatch:", toJson(mismatches, undefined, 2)); - return passthrough(); - } - - return resolver(args); - }; -} - -function findMismatches(actual: any, expected: any): Record { - const mismatches: Record = {}; - - if (typeof actual !== typeof expected) { - return { value: { actual, expected } }; - } - - if (typeof actual !== "object" || actual === null || expected === null) { - if (actual !== expected) { - return { value: { actual, expected } }; - } - return {}; - } - - const actualKeys = Object.keys(actual); - const expectedKeys = Object.keys(expected); - - const allKeys = new Set([...actualKeys, ...expectedKeys]); - - for (const key of allKeys) { - if (!expectedKeys.includes(key)) { - if (actual[key] === undefined) { - continue; - } - mismatches[key] = { actual: actual[key], expected: undefined }; - } else if (!actualKeys.includes(key)) { - if (expected[key] === undefined) { - continue; - } - mismatches[key] = { actual: undefined, expected: expected[key] }; - } else if (actual[key] !== expected[key]) { - mismatches[key] = { actual: actual[key], expected: expected[key] }; - } - } - - return mismatches; -} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts b/seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts deleted file mode 100644 index 6599d2b4a92d..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/withHeaders.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { type HttpResponseResolver, passthrough } from "msw"; - -/** - * Creates a request matcher that validates if request headers match specified criteria - * @param expectedHeaders - Headers to match against - * @param resolver - Response resolver to execute if headers match - */ -export function withHeaders( - expectedHeaders: Record boolean)>, - resolver: HttpResponseResolver, -): HttpResponseResolver { - return (args) => { - const { request } = args; - const { headers } = request; - - const mismatches: Record< - string, - { actual: string | null; expected: string | RegExp | ((value: string) => boolean) } - > = {}; - - for (const [key, expectedValue] of Object.entries(expectedHeaders)) { - const actualValue = headers.get(key); - - if (actualValue === null) { - mismatches[key] = { actual: null, expected: expectedValue }; - continue; - } - - if (typeof expectedValue === "function") { - if (!expectedValue(actualValue)) { - mismatches[key] = { actual: actualValue, expected: expectedValue }; - } - } else if (expectedValue instanceof RegExp) { - if (!expectedValue.test(actualValue)) { - mismatches[key] = { actual: actualValue, expected: expectedValue }; - } - } else if (expectedValue !== actualValue) { - mismatches[key] = { actual: actualValue, expected: expectedValue }; - } - } - - if (Object.keys(mismatches).length > 0) { - const formattedMismatches = formatHeaderMismatches(mismatches); - console.error("Header mismatch:", formattedMismatches); - return passthrough(); - } - - return resolver(args); - }; -} - -function formatHeaderMismatches( - mismatches: Record boolean) }>, -): Record { - const formatted: Record = {}; - - for (const [key, { actual, expected }] of Object.entries(mismatches)) { - formatted[key] = { - actual, - expected: - expected instanceof RegExp - ? expected.toString() - : typeof expected === "function" - ? "[Function]" - : expected, - }; - } - - return formatted; -} diff --git a/seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts b/seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts deleted file mode 100644 index 3e8800a0c374..000000000000 --- a/seed/ts-sdk/allof-inline/tests/mock-server/withJson.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { type HttpResponseResolver, passthrough } from "msw"; - -import { fromJson, toJson } from "../../src/core/json"; - -export interface WithJsonOptions { - /** - * List of field names to ignore when comparing request bodies. - * This is useful for pagination cursor fields that change between requests. - */ - ignoredFields?: string[]; -} - -/** - * Creates a request matcher that validates if the request JSON body exactly matches the expected object - * @param expectedBody - The exact body object to match against - * @param resolver - Response resolver to execute if body matches - * @param options - Optional configuration including fields to ignore - */ -export function withJson( - expectedBody: unknown, - resolver: HttpResponseResolver, - options?: WithJsonOptions, -): HttpResponseResolver { - const ignoredFields = options?.ignoredFields ?? []; - return async (args) => { - const { request } = args; - - let clonedRequest: Request; - let bodyText: string | undefined; - let actualBody: unknown; - try { - clonedRequest = request.clone(); - bodyText = await clonedRequest.text(); - if (bodyText === "") { - console.error("Request body is empty, expected a JSON object."); - return passthrough(); - } - actualBody = fromJson(bodyText); - } catch (error) { - console.error(`Error processing request body:\n\tError: ${error}\n\tBody: ${bodyText}`); - return passthrough(); - } - - const mismatches = findMismatches(actualBody, expectedBody); - const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); - if (filteredMismatches.length > 0) { - console.error("JSON body mismatch:", toJson(mismatches, undefined, 2)); - return passthrough(); - } - - return resolver(args); - }; -} - -function findMismatches(actual: any, expected: any): Record { - const mismatches: Record = {}; - - if (typeof actual !== typeof expected) { - if (areEquivalent(actual, expected)) { - return {}; - } - return { value: { actual, expected } }; - } - - if (typeof actual !== "object" || actual === null || expected === null) { - if (actual !== expected) { - if (areEquivalent(actual, expected)) { - return {}; - } - return { value: { actual, expected } }; - } - return {}; - } - - if (Array.isArray(actual) && Array.isArray(expected)) { - if (actual.length !== expected.length) { - return { length: { actual: actual.length, expected: expected.length } }; - } - - const arrayMismatches: Record = {}; - for (let i = 0; i < actual.length; i++) { - const itemMismatches = findMismatches(actual[i], expected[i]); - if (Object.keys(itemMismatches).length > 0) { - for (const [mismatchKey, mismatchValue] of Object.entries(itemMismatches)) { - arrayMismatches[`[${i}]${mismatchKey === "value" ? "" : `.${mismatchKey}`}`] = mismatchValue; - } - } - } - return arrayMismatches; - } - - const actualKeys = Object.keys(actual); - const expectedKeys = Object.keys(expected); - - const allKeys = new Set([...actualKeys, ...expectedKeys]); - - for (const key of allKeys) { - if (!expectedKeys.includes(key)) { - if (actual[key] === undefined) { - continue; // Skip undefined values in actual - } - mismatches[key] = { actual: actual[key], expected: undefined }; - } else if (!actualKeys.includes(key)) { - if (expected[key] === undefined) { - continue; // Skip undefined values in expected - } - mismatches[key] = { actual: undefined, expected: expected[key] }; - } else if ( - typeof actual[key] === "object" && - actual[key] !== null && - typeof expected[key] === "object" && - expected[key] !== null - ) { - const nestedMismatches = findMismatches(actual[key], expected[key]); - if (Object.keys(nestedMismatches).length > 0) { - for (const [nestedKey, nestedValue] of Object.entries(nestedMismatches)) { - mismatches[`${key}${nestedKey === "value" ? "" : `.${nestedKey}`}`] = nestedValue; - } - } - } else if (actual[key] !== expected[key]) { - if (areEquivalent(actual[key], expected[key])) { - continue; - } - mismatches[key] = { actual: actual[key], expected: expected[key] }; - } - } - - return mismatches; -} - -function areEquivalent(actual: unknown, expected: unknown): boolean { - if (actual === expected) { - return true; - } - if (isEquivalentBigInt(actual, expected)) { - return true; - } - if (isEquivalentDatetime(actual, expected)) { - return true; - } - return false; -} - -function isEquivalentBigInt(actual: unknown, expected: unknown) { - if (typeof actual === "number") { - actual = BigInt(actual); - } - if (typeof expected === "number") { - expected = BigInt(expected); - } - if (typeof actual === "bigint" && typeof expected === "bigint") { - return actual === expected; - } - return false; -} - -function isEquivalentDatetime(str1: unknown, str2: unknown): boolean { - if (typeof str1 !== "string" || typeof str2 !== "string") { - return false; - } - const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/; - if (!isoDatePattern.test(str1) || !isoDatePattern.test(str2)) { - return false; - } - - try { - const date1 = new Date(str1).getTime(); - const date2 = new Date(str2).getTime(); - return date1 === date2; - } catch { - return false; - } -} diff --git a/seed/ts-sdk/allof-inline/tests/setup.ts b/seed/ts-sdk/allof-inline/tests/setup.ts deleted file mode 100644 index a5651f81ba10..000000000000 --- a/seed/ts-sdk/allof-inline/tests/setup.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { expect } from "vitest"; - -interface CustomMatchers { - toContainHeaders(expectedHeaders: Record): R; -} - -declare module "vitest" { - interface Assertion extends CustomMatchers {} - interface AsymmetricMatchersContaining extends CustomMatchers {} -} - -expect.extend({ - toContainHeaders(actual: unknown, expectedHeaders: Record) { - const isHeaders = actual instanceof Headers; - const isPlainObject = typeof actual === "object" && actual !== null && !Array.isArray(actual); - - if (!isHeaders && !isPlainObject) { - throw new TypeError("Received value must be an instance of Headers or a plain object!"); - } - - if (typeof expectedHeaders !== "object" || expectedHeaders === null || Array.isArray(expectedHeaders)) { - throw new TypeError("Expected headers must be a plain object!"); - } - - const missingHeaders: string[] = []; - const mismatchedHeaders: Array<{ key: string; expected: string; actual: string | null }> = []; - - for (const [key, value] of Object.entries(expectedHeaders)) { - let actualValue: string | null = null; - - if (isHeaders) { - // Headers.get() is already case-insensitive - actualValue = (actual as Headers).get(key); - } else { - // For plain objects, do case-insensitive lookup - const actualObj = actual as Record; - const lowerKey = key.toLowerCase(); - const foundKey = Object.keys(actualObj).find((k) => k.toLowerCase() === lowerKey); - actualValue = foundKey ? actualObj[foundKey] : null; - } - - if (actualValue === null || actualValue === undefined) { - missingHeaders.push(key); - } else if (actualValue !== value) { - mismatchedHeaders.push({ key, expected: value, actual: actualValue }); - } - } - - const pass = missingHeaders.length === 0 && mismatchedHeaders.length === 0; - - const actualType = isHeaders ? "Headers" : "object"; - - if (pass) { - return { - message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, - pass: true, - }; - } else { - const messages: string[] = []; - - if (missingHeaders.length > 0) { - messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); - } - - if (mismatchedHeaders.length > 0) { - const mismatches = mismatchedHeaders.map( - ({ key, expected, actual }) => - `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, - ); - messages.push(mismatches.join("\n")); - } - - return { - message: () => - `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, - pass: false, - }; - } - }, -}); diff --git a/seed/ts-sdk/allof-inline/tests/tsconfig.json b/seed/ts-sdk/allof-inline/tests/tsconfig.json deleted file mode 100644 index ac39744de7b2..000000000000 --- a/seed/ts-sdk/allof-inline/tests/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": null, - "rootDir": "..", - "types": ["vitest/globals"] - }, - "include": ["../src", "../tests"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts deleted file mode 100644 index 6c17624228bb..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/Fetcher.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import fs from "fs"; -import { join } from "path"; -import stream from "stream"; -import type { BinaryResponse } from "../../../src/core"; -import { type Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; - -describe("Test fetcherImpl", () => { - it("should handle successful request", async () => { - const mockArgs: Fetcher.Args = { - url: "https://httpbin.org/post", - method: "POST", - headers: { "X-Test": "x-test-header" }, - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - maxRetries: 0, - responseType: "json", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - }), - ); - - const result = await fetcherImpl(mockArgs); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.body).toEqual({ data: "test" }); - } - - expect(global.fetch).toHaveBeenCalledWith( - "https://httpbin.org/post", - expect.objectContaining({ - method: "POST", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - body: JSON.stringify({ data: "test" }), - }), - ); - }); - - it("should send octet stream", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "POST", - headers: { "X-Test": "x-test-header" }, - contentType: "application/octet-stream", - requestType: "bytes", - maxRetries: 0, - responseType: "json", - body: fs.createReadStream(join(__dirname, "test-file.txt")), - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - }), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "POST", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - body: expect.any(fs.ReadStream), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.body).toEqual({ data: "test" }); - } - }); - - it("should receive file as stream", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.stream).toBe("function"); - const stream = body.stream(); - expect(stream).toBeInstanceOf(ReadableStream); - const readableStream = stream as ReadableStream; - const reader = readableStream.getReader(); - const { value } = await reader.read(); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(value); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); - - it("should receive file as blob", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.blob).toBe("function"); - const blob = await body.blob(); - expect(blob).toBeInstanceOf(Blob); - const reader = blob.stream().getReader(); - const { value } = await reader.read(); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(value); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); - - it("should receive file as arraybuffer", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.arrayBuffer).toBe("function"); - const arrayBuffer = await body.arrayBuffer(); - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(new Uint8Array(arrayBuffer)); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); - - it("should receive file as bytes", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.bytes).toBe("function"); - if (!body.bytes) { - return; - } - const bytes = await body.bytes(); - expect(bytes).toBeInstanceOf(Uint8Array); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(bytes); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts deleted file mode 100644 index 2ec008e581d8..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/HttpResponsePromise.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -import { HttpResponsePromise } from "../../../src/core/fetcher/HttpResponsePromise"; -import type { RawResponse, WithRawResponse } from "../../../src/core/fetcher/RawResponse"; - -describe("HttpResponsePromise", () => { - const mockRawResponse: RawResponse = { - headers: new Headers(), - redirected: false, - status: 200, - statusText: "OK", - type: "basic" as ResponseType, - url: "https://example.com", - }; - const mockData = { id: "123", name: "test" }; - const mockWithRawResponse: WithRawResponse = { - data: mockData, - rawResponse: mockRawResponse, - }; - - describe("fromFunction", () => { - it("should create an HttpResponsePromise from a function", async () => { - const mockFn = vi - .fn<(arg1: string, arg2: string) => Promise>>() - .mockResolvedValue(mockWithRawResponse); - - const responsePromise = HttpResponsePromise.fromFunction(mockFn, "arg1", "arg2"); - - const result = await responsePromise; - expect(result).toEqual(mockData); - expect(mockFn).toHaveBeenCalledWith("arg1", "arg2"); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("fromPromise", () => { - it("should create an HttpResponsePromise from a promise", async () => { - const promise = Promise.resolve(mockWithRawResponse); - - const responsePromise = HttpResponsePromise.fromPromise(promise); - - const result = await responsePromise; - expect(result).toEqual(mockData); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("fromExecutor", () => { - it("should create an HttpResponsePromise from an executor function", async () => { - const responsePromise = HttpResponsePromise.fromExecutor((resolve) => { - resolve(mockWithRawResponse); - }); - - const result = await responsePromise; - expect(result).toEqual(mockData); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("fromResult", () => { - it("should create an HttpResponsePromise from a result", async () => { - const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); - - const result = await responsePromise; - expect(result).toEqual(mockData); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("Promise methods", () => { - let responsePromise: HttpResponsePromise; - - beforeEach(() => { - responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); - }); - - it("should support then() method", async () => { - const result = await responsePromise.then((data) => ({ - ...data, - modified: true, - })); - - expect(result).toEqual({ - ...mockData, - modified: true, - }); - }); - - it("should support catch() method", async () => { - const errorResponsePromise = HttpResponsePromise.fromExecutor((_, reject) => { - reject(new Error("Test error")); - }); - - const catchSpy = vi.fn(); - await errorResponsePromise.catch(catchSpy); - - expect(catchSpy).toHaveBeenCalled(); - const error = catchSpy.mock.calls[0]?.[0]; - expect(error).toBeInstanceOf(Error); - expect((error as Error).message).toBe("Test error"); - }); - - it("should support finally() method", async () => { - const finallySpy = vi.fn(); - await responsePromise.finally(finallySpy); - - expect(finallySpy).toHaveBeenCalled(); - }); - }); - - describe("withRawResponse", () => { - it("should return both data and raw response", async () => { - const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); - - const result = await responsePromise.withRawResponse(); - - expect(result).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts deleted file mode 100644 index 375ee3f38064..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/RawResponse.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { toRawResponse } from "../../../src/core/fetcher/RawResponse"; - -describe("RawResponse", () => { - describe("toRawResponse", () => { - it("should convert Response to RawResponse by removing body, bodyUsed, and ok properties", () => { - const mockHeaders = new Headers({ "content-type": "application/json" }); - const mockResponse = { - body: "test body", - bodyUsed: false, - ok: true, - headers: mockHeaders, - redirected: false, - status: 200, - statusText: "OK", - type: "basic" as ResponseType, - url: "https://example.com", - }; - - const result = toRawResponse(mockResponse as unknown as Response); - - expect("body" in result).toBe(false); - expect("bodyUsed" in result).toBe(false); - expect("ok" in result).toBe(false); - expect(result.headers).toBe(mockHeaders); - expect(result.redirected).toBe(false); - expect(result.status).toBe(200); - expect(result.statusText).toBe("OK"); - expect(result.type).toBe("basic"); - expect(result.url).toBe("https://example.com"); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts deleted file mode 100644 index a92f1b5e81d1..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/createRequestUrl.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; - -describe("Test createRequestUrl", () => { - const BASE_URL = "https://api.example.com"; - - interface TestCase { - description: string; - baseUrl: string; - queryParams?: Record; - expected: string; - } - - const testCases: TestCase[] = [ - { - description: "should return the base URL when no query parameters are provided", - baseUrl: BASE_URL, - expected: BASE_URL, - }, - { - description: "should append simple query parameters", - baseUrl: BASE_URL, - queryParams: { key: "value", another: "param" }, - expected: "https://api.example.com?key=value&another=param", - }, - { - description: "should handle array query parameters", - baseUrl: BASE_URL, - queryParams: { items: ["a", "b", "c"] }, - expected: "https://api.example.com?items=a&items=b&items=c", - }, - { - description: "should handle object query parameters", - baseUrl: BASE_URL, - queryParams: { filter: { name: "John", age: 30 } }, - expected: "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30", - }, - { - description: "should handle mixed types of query parameters", - baseUrl: BASE_URL, - queryParams: { - simple: "value", - array: ["x", "y"], - object: { key: "value" }, - }, - expected: "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value", - }, - { - description: "should handle empty query parameters object", - baseUrl: BASE_URL, - queryParams: {}, - expected: BASE_URL, - }, - { - description: "should encode special characters in query parameters", - baseUrl: BASE_URL, - queryParams: { special: "a&b=c d" }, - expected: "https://api.example.com?special=a%26b%3Dc%20d", - }, - { - description: "should handle numeric values", - baseUrl: BASE_URL, - queryParams: { count: 42, price: 19.99, active: 1, inactive: 0 }, - expected: "https://api.example.com?count=42&price=19.99&active=1&inactive=0", - }, - { - description: "should handle boolean values", - baseUrl: BASE_URL, - queryParams: { enabled: true, disabled: false }, - expected: "https://api.example.com?enabled=true&disabled=false", - }, - { - description: "should handle null and undefined values", - baseUrl: BASE_URL, - queryParams: { - valid: "value", - nullValue: null, - undefinedValue: undefined, - emptyString: "", - }, - expected: "https://api.example.com?valid=value&nullValue=&emptyString=", - }, - { - description: "should handle deeply nested objects", - baseUrl: BASE_URL, - queryParams: { - user: { - profile: { - name: "John", - settings: { theme: "dark" }, - }, - }, - }, - expected: - "https://api.example.com?user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", - }, - { - description: "should handle arrays of objects", - baseUrl: BASE_URL, - queryParams: { - users: [ - { name: "John", age: 30 }, - { name: "Jane", age: 25 }, - ], - }, - expected: - "https://api.example.com?users%5Bname%5D=John&users%5Bage%5D=30&users%5Bname%5D=Jane&users%5Bage%5D=25", - }, - { - description: "should handle mixed arrays", - baseUrl: BASE_URL, - queryParams: { - mixed: ["string", 42, true, { key: "value" }], - }, - expected: "https://api.example.com?mixed=string&mixed=42&mixed=true&mixed%5Bkey%5D=value", - }, - { - description: "should handle empty arrays", - baseUrl: BASE_URL, - queryParams: { emptyArray: [] }, - expected: BASE_URL, - }, - { - description: "should handle empty objects", - baseUrl: BASE_URL, - queryParams: { emptyObject: {} }, - expected: BASE_URL, - }, - { - description: "should handle special characters in keys", - baseUrl: BASE_URL, - queryParams: { "key with spaces": "value", "key[with]brackets": "value" }, - expected: "https://api.example.com?key%20with%20spaces=value&key%5Bwith%5Dbrackets=value", - }, - { - description: "should handle URL with existing query parameters", - baseUrl: "https://api.example.com?existing=param", - queryParams: { new: "value" }, - expected: "https://api.example.com?existing=param?new=value", - }, - { - description: "should handle complex nested structures", - baseUrl: BASE_URL, - queryParams: { - filters: { - status: ["active", "pending"], - category: { - type: "electronics", - subcategories: ["phones", "laptops"], - }, - }, - sort: { field: "name", direction: "asc" }, - }, - expected: - "https://api.example.com?filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", - }, - ]; - - testCases.forEach(({ description, baseUrl, queryParams, expected }) => { - it(description, () => { - expect(createRequestUrl(baseUrl, queryParams)).toBe(expected); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts deleted file mode 100644 index 8a6c3a57e211..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/getRequestBody.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; -import { RUNTIME } from "../../../src/core/runtime"; - -describe("Test getRequestBody", () => { - interface TestCase { - description: string; - input: any; - type: "json" | "form" | "file" | "bytes" | "other"; - expected: any; - skipCondition?: () => boolean; - } - - const testCases: TestCase[] = [ - { - description: "should stringify body if not FormData in Node environment", - input: { key: "value" }, - type: "json", - expected: '{"key":"value"}', - skipCondition: () => RUNTIME.type !== "node", - }, - { - description: "should stringify body if not FormData in browser environment", - input: { key: "value" }, - type: "json", - expected: '{"key":"value"}', - skipCondition: () => RUNTIME.type !== "browser", - }, - { - description: "should return the Uint8Array", - input: new Uint8Array([1, 2, 3]), - type: "bytes", - expected: new Uint8Array([1, 2, 3]), - }, - { - description: "should serialize objects for form-urlencoded content type", - input: { username: "johndoe", email: "john@example.com" }, - type: "form", - expected: "username=johndoe&email=john%40example.com", - }, - { - description: "should serialize complex nested objects and arrays for form-urlencoded content type", - input: { - user: { - profile: { - name: "John Doe", - settings: { - theme: "dark", - notifications: true, - }, - }, - tags: ["admin", "user"], - contacts: [ - { type: "email", value: "john@example.com" }, - { type: "phone", value: "+1234567890" }, - ], - }, - filters: { - status: ["active", "pending"], - metadata: { - created: "2024-01-01", - categories: ["electronics", "books"], - }, - }, - preferences: ["notifications", "updates"], - }, - type: "form", - expected: - "user%5Bprofile%5D%5Bname%5D=John%20Doe&" + - "user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark&" + - "user%5Bprofile%5D%5Bsettings%5D%5Bnotifications%5D=true&" + - "user%5Btags%5D=admin&" + - "user%5Btags%5D=user&" + - "user%5Bcontacts%5D%5Btype%5D=email&" + - "user%5Bcontacts%5D%5Bvalue%5D=john%40example.com&" + - "user%5Bcontacts%5D%5Btype%5D=phone&" + - "user%5Bcontacts%5D%5Bvalue%5D=%2B1234567890&" + - "filters%5Bstatus%5D=active&" + - "filters%5Bstatus%5D=pending&" + - "filters%5Bmetadata%5D%5Bcreated%5D=2024-01-01&" + - "filters%5Bmetadata%5D%5Bcategories%5D=electronics&" + - "filters%5Bmetadata%5D%5Bcategories%5D=books&" + - "preferences=notifications&" + - "preferences=updates", - }, - { - description: "should return the input for pre-serialized form-urlencoded strings", - input: "key=value&another=param", - type: "other", - expected: "key=value&another=param", - }, - { - description: "should JSON stringify objects", - input: { key: "value" }, - type: "json", - expected: '{"key":"value"}', - }, - ]; - - testCases.forEach(({ description, input, type, expected, skipCondition }) => { - it(description, async () => { - if (skipCondition?.()) { - return; - } - - const result = await getRequestBody({ - body: input, - type, - }); - - if (input instanceof Uint8Array) { - expect(result).toBe(input); - } else { - expect(result).toBe(expected); - } - }); - }); - - it("should return FormData in browser environment", async () => { - if (RUNTIME.type === "browser") { - const formData = new FormData(); - formData.append("key", "value"); - const result = await getRequestBody({ - body: formData, - type: "file", - }); - expect(result).toBe(formData); - } - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts deleted file mode 100644 index ad6be7fc2c9b..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/getResponseBody.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; - -import { RUNTIME } from "../../../src/core/runtime"; - -describe("Test getResponseBody", () => { - interface SimpleTestCase { - description: string; - responseData: string | Record; - responseType?: "blob" | "sse" | "streaming" | "text"; - expected: any; - skipCondition?: () => boolean; - } - - const simpleTestCases: SimpleTestCase[] = [ - { - description: "should handle text response type", - responseData: "test text", - responseType: "text", - expected: "test text", - }, - { - description: "should handle JSON response", - responseData: { key: "value" }, - expected: { key: "value" }, - }, - { - description: "should handle empty response", - responseData: "", - expected: undefined, - }, - { - description: "should handle non-JSON response", - responseData: "invalid json", - expected: { - ok: false, - error: { - reason: "non-json", - statusCode: 200, - rawBody: "invalid json", - }, - }, - }, - ]; - - simpleTestCases.forEach(({ description, responseData, responseType, expected, skipCondition }) => { - it(description, async () => { - if (skipCondition?.()) { - return; - } - - const mockResponse = new Response( - typeof responseData === "string" ? responseData : JSON.stringify(responseData), - ); - const result = await getResponseBody(mockResponse, responseType); - expect(result).toEqual(expected); - }); - }); - - it("should handle blob response type", async () => { - const mockBlob = new Blob(["test"], { type: "text/plain" }); - const mockResponse = new Response(mockBlob); - const result = await getResponseBody(mockResponse, "blob"); - // @ts-expect-error - expect(result.constructor.name).toBe("Blob"); - }); - - it("should handle sse response type", async () => { - if (RUNTIME.type === "node") { - const mockStream = new ReadableStream(); - const mockResponse = new Response(mockStream); - const result = await getResponseBody(mockResponse, "sse"); - expect(result).toBe(mockStream); - } - }); - - it("should handle streaming response type", async () => { - const encoder = new TextEncoder(); - const testData = "test stream data"; - const mockStream = new ReadableStream({ - start(controller) { - controller.enqueue(encoder.encode(testData)); - controller.close(); - }, - }); - - const mockResponse = new Response(mockStream); - const result = (await getResponseBody(mockResponse, "streaming")) as ReadableStream; - - expect(result).toBeInstanceOf(ReadableStream); - - const reader = result.getReader(); - const decoder = new TextDecoder(); - const { value } = await reader.read(); - const streamContent = decoder.decode(value); - expect(streamContent).toBe(testData); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts deleted file mode 100644 index 366c9b6ced61..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/logging.test.ts +++ /dev/null @@ -1,517 +0,0 @@ -import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; - -function createMockLogger() { - return { - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }; -} - -function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify(data), { - status, - statusText, - }), - ); -} - -function mockErrorResponse(data: unknown = { error: "Error" }, status = 404, statusText = "Not Found") { - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify(data), { - status, - statusText, - }), - ); -} - -describe("Fetcher Logging Integration", () => { - describe("Request Logging", () => { - it("should log successful request at debug level", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - headers: { "Content-Type": "application/json" }, - body: { test: "data" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "POST", - url: "https://example.com/api", - headers: expect.toContainHeaders({ - "Content-Type": "application/json", - }), - hasBody: true, - }), - ); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - method: "POST", - url: "https://example.com/api", - statusCode: 200, - }), - ); - }); - - it("should not log debug messages at info level for successful requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "info", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - }); - - it("should log request with body flag", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - hasBody: true, - }), - ); - }); - - it("should log request without body flag", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - hasBody: false, - }), - ); - }); - - it("should not log when silent mode is enabled", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: true, - }, - }); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).not.toHaveBeenCalled(); - expect(mockLogger.error).not.toHaveBeenCalled(); - }); - - it("should not log when no logging config is provided", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - }); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - }); - }); - - describe("Error Logging", () => { - it("should log 4xx errors at error level", async () => { - const mockLogger = createMockLogger(); - mockErrorResponse({ error: "Not found" }, 404, "Not Found"); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - statusCode: 404, - }), - ); - }); - - it("should log 5xx errors at error level", async () => { - const mockLogger = createMockLogger(); - mockErrorResponse({ error: "Internal error" }, 500, "Internal Server Error"); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - statusCode: 500, - }), - ); - }); - - it("should log aborted request errors", async () => { - const mockLogger = createMockLogger(); - - const abortController = new AbortController(); - abortController.abort(); - - global.fetch = vi.fn().mockRejectedValue(new Error("Aborted")); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - abortSignal: abortController.signal, - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request was aborted", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - }), - ); - }); - - it("should log timeout errors", async () => { - const mockLogger = createMockLogger(); - - const timeoutError = new Error("Request timeout"); - timeoutError.name = "AbortError"; - - global.fetch = vi.fn().mockRejectedValue(timeoutError); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request timed out", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - timeoutMs: undefined, - }), - ); - }); - - it("should log unknown errors", async () => { - const mockLogger = createMockLogger(); - - const unknownError = new Error("Unknown error"); - - global.fetch = vi.fn().mockRejectedValue(unknownError); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - errorMessage: "Unknown error", - }), - ); - }); - }); - - describe("Logging with Redaction", () => { - it("should redact sensitive data in error logs", async () => { - const mockLogger = createMockLogger(); - mockErrorResponse({ error: "Unauthorized" }, 401, "Unauthorized"); - - await fetcherImpl({ - url: "https://example.com/api?api_key=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - url: "https://example.com/api?api_key=[REDACTED]", - }), - ); - }); - }); - - describe("Different HTTP Methods", () => { - it("should log GET requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "GET", - }), - ); - }); - - it("should log POST requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse({ data: "test" }, 201, "Created"); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "POST", - }), - ); - }); - - it("should log PUT requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "PUT", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "PUT", - }), - ); - }); - - it("should log DELETE requests", async () => { - const mockLogger = createMockLogger(); - global.fetch = vi.fn().mockResolvedValue( - new Response(null, { - status: 200, - statusText: "OK", - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "DELETE", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "DELETE", - }), - ); - }); - }); - - describe("Status Code Logging", () => { - it("should log 2xx success status codes", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse({ data: "test" }, 201, "Created"); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - statusCode: 201, - }), - ); - }); - - it("should log 3xx redirect status codes as success", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse({ data: "test" }, 301, "Moved Permanently"); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - statusCode: 301, - }), - ); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts deleted file mode 100644 index 1850d1fda959..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/makePassthroughRequest.test.ts +++ /dev/null @@ -1,398 +0,0 @@ -import type { Mock } from "vitest"; -import { makePassthroughRequest } from "../../../src/core/fetcher/makePassthroughRequest"; - -describe("makePassthroughRequest", () => { - let mockFetch: Mock; - - beforeEach(() => { - mockFetch = vi.fn(); - mockFetch.mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 200 })); - }); - - describe("URL resolution", () => { - it("should use absolute URL directly", async () => { - await makePassthroughRequest("https://api.example.com/v1/users", undefined, { - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/users"); - }); - - it("should resolve relative path against baseUrl", async () => { - await makePassthroughRequest("/v1/users", undefined, { - baseUrl: "https://api.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/users"); - }); - - it("should resolve relative path against environment when baseUrl is not set", async () => { - await makePassthroughRequest("/v1/users", undefined, { - environment: "https://env.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://env.example.com/v1/users"); - }); - - it("should prefer baseUrl over environment", async () => { - await makePassthroughRequest("/v1/users", undefined, { - baseUrl: "https://base.example.com", - environment: "https://env.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://base.example.com/v1/users"); - }); - - it("should pass relative URL through as-is when no baseUrl or environment", async () => { - await makePassthroughRequest("/v1/users", undefined, { - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("/v1/users"); - }); - - it("should resolve baseUrl supplier", async () => { - await makePassthroughRequest("/v1/users", undefined, { - baseUrl: () => "https://dynamic.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://dynamic.example.com/v1/users"); - }); - - it("should ignore absolute URL even when baseUrl is set", async () => { - await makePassthroughRequest("https://other.example.com/path", undefined, { - baseUrl: "https://base.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://other.example.com/path"); - }); - - it("should accept a URL object", async () => { - await makePassthroughRequest(new URL("https://api.example.com/v1/users"), undefined, { - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/users"); - }); - }); - - describe("header merge order", () => { - it("should merge headers in correct priority: SDK defaults < auth < init < requestOptions", async () => { - await makePassthroughRequest( - "https://api.example.com", - { - headers: { "X-Custom": "from-init", Authorization: "from-init" }, - }, - { - headers: { - "X-Custom": "from-sdk", - "X-SDK-Only": "sdk-value", - Authorization: "from-sdk", - }, - getAuthHeaders: async () => ({ - Authorization: "Bearer auth-token", - "X-Auth-Only": "auth-value", - }), - fetch: mockFetch, - }, - { - headers: { Authorization: "from-request-options" }, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - const headers = calledOptions.headers; - - // requestOptions.headers wins for Authorization (highest priority) - expect(headers.authorization).toBe("from-request-options"); - // init.headers wins over SDK defaults for X-Custom - expect(headers["x-custom"]).toBe("from-init"); - // SDK-only header is preserved - expect(headers["x-sdk-only"]).toBe("sdk-value"); - // Auth-only header is preserved - expect(headers["x-auth-only"]).toBe("auth-value"); - }); - - it("should lowercase all header keys", async () => { - await makePassthroughRequest( - "https://api.example.com", - { - headers: { "Content-Type": "application/json" }, - }, - { - headers: { "X-Fern-Language": "JavaScript" }, - fetch: mockFetch, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - const headers = calledOptions.headers; - expect(headers["content-type"]).toBe("application/json"); - expect(headers["x-fern-language"]).toBe("JavaScript"); - expect(headers["Content-Type"]).toBeUndefined(); - expect(headers["X-Fern-Language"]).toBeUndefined(); - }); - - it("should handle Headers object in init", async () => { - const initHeaders = new Headers(); - initHeaders.set("X-From-Headers-Object", "value"); - await makePassthroughRequest("https://api.example.com", { headers: initHeaders }, { fetch: mockFetch }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-from-headers-object"]).toBe("value"); - }); - - it("should handle array-style headers in init", async () => { - await makePassthroughRequest( - "https://api.example.com", - { headers: [["X-Array-Header", "array-value"]] }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-array-header"]).toBe("array-value"); - }); - - it("should skip null SDK default header values", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - headers: { "X-Present": "value", "X-Null": null }, - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-present"]).toBe("value"); - expect(calledOptions.headers["x-null"]).toBeUndefined(); - }); - }); - - describe("auth headers", () => { - it("should include auth headers when getAuthHeaders is provided", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers.authorization).toBe("Bearer my-token"); - }); - - it("should work without auth headers", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers.authorization).toBeUndefined(); - }); - - it("should allow init headers to override auth headers", async () => { - await makePassthroughRequest( - "https://api.example.com", - { headers: { Authorization: "Bearer override" } }, - { - getAuthHeaders: async () => ({ Authorization: "Bearer sdk-auth" }), - fetch: mockFetch, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers.authorization).toBe("Bearer override"); - }); - }); - - describe("method and body", () => { - it("should default to GET when no method specified", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.method).toBe("GET"); - }); - - it("should use the method from init", async () => { - await makePassthroughRequest( - "https://api.example.com", - { method: "POST", body: JSON.stringify({ key: "value" }) }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.method).toBe("POST"); - expect(calledOptions.body).toBe(JSON.stringify({ key: "value" })); - }); - - it("should pass body as undefined when not provided", async () => { - await makePassthroughRequest("https://api.example.com", { method: "GET" }, { fetch: mockFetch }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.body).toBeUndefined(); - }); - }); - - describe("timeout and retries", () => { - it("should use requestOptions timeout over client timeout", async () => { - await makePassthroughRequest( - "https://api.example.com", - undefined, - { timeoutInSeconds: 30, fetch: mockFetch }, - { timeoutInSeconds: 10 }, - ); - // The timeout is passed to makeRequest which converts to ms - // We verify via the signal timing behavior (indirectly tested through makeRequest) - expect(mockFetch).toHaveBeenCalledTimes(1); - }); - - it("should use client timeout when requestOptions timeout is not set", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - timeoutInSeconds: 30, - fetch: mockFetch, - }); - expect(mockFetch).toHaveBeenCalledTimes(1); - }); - - it("should use requestOptions maxRetries over client maxRetries", async () => { - mockFetch.mockResolvedValue(new Response("", { status: 500 })); - vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - await makePassthroughRequest( - "https://api.example.com", - undefined, - { maxRetries: 5, fetch: mockFetch }, - { maxRetries: 1 }, - ); - // 1 initial + 1 retry = 2 calls - expect(mockFetch).toHaveBeenCalledTimes(2); - - vi.restoreAllMocks(); - }); - }); - - describe("abort signal", () => { - it("should use requestOptions.abortSignal over init.signal", async () => { - const initController = new AbortController(); - const requestController = new AbortController(); - - await makePassthroughRequest( - "https://api.example.com", - { signal: initController.signal }, - { fetch: mockFetch }, - { abortSignal: requestController.signal }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - // The signal passed to makeRequest is combined with timeout signal via anySignal, - // but the requestOptions.abortSignal should be the one that's used (not init.signal) - expect(calledOptions.signal).toBeDefined(); - }); - - it("should use init.signal when requestOptions.abortSignal is not set", async () => { - const initController = new AbortController(); - - await makePassthroughRequest( - "https://api.example.com", - { signal: initController.signal }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.signal).toBeDefined(); - }); - }); - - describe("credentials", () => { - it("should pass credentials include when set", async () => { - await makePassthroughRequest("https://api.example.com", { credentials: "include" }, { fetch: mockFetch }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.credentials).toBe("include"); - }); - - it("should not pass credentials when not set to include", async () => { - await makePassthroughRequest( - "https://api.example.com", - { credentials: "same-origin" }, - { - fetch: mockFetch, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.credentials).toBeUndefined(); - }); - }); - - describe("response", () => { - it("should return the Response object from fetch", async () => { - const mockResponse = new Response(JSON.stringify({ data: "test" }), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - mockFetch.mockResolvedValue(mockResponse); - - const response = await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - expect(response).toBe(mockResponse); - expect(response.status).toBe(200); - }); - - it("should return error responses without throwing", async () => { - const errorResponse = new Response("Not Found", { status: 404 }); - mockFetch.mockResolvedValue(errorResponse); - - const response = await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - expect(response.status).toBe(404); - }); - }); - - describe("Request object input", () => { - it("should extract URL from Request object", async () => { - const request = new Request("https://api.example.com/v1/resource", { method: "POST" }); - await makePassthroughRequest(request, undefined, { - fetch: mockFetch, - }); - const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/resource"); - expect(calledOptions.method).toBe("POST"); - }); - - it("should extract headers from Request object when no init provided", async () => { - const request = new Request("https://api.example.com", { - headers: { "X-From-Request": "request-value" }, - }); - await makePassthroughRequest(request, undefined, { - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-from-request"]).toBe("request-value"); - }); - - it("should use explicit init over Request object properties", async () => { - const request = new Request("https://api.example.com", { - method: "POST", - headers: { "X-From-Request": "request-value" }, - }); - await makePassthroughRequest( - request, - { method: "PUT", headers: { "X-From-Init": "init-value" } }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.method).toBe("PUT"); - expect(calledOptions.headers["x-from-init"]).toBe("init-value"); - // Request headers should NOT be present since explicit init was provided - expect(calledOptions.headers["x-from-request"]).toBeUndefined(); - }); - }); - - describe("SDK default header suppliers", () => { - it("should resolve supplier functions for SDK default headers", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - headers: { - "X-Static": "static-value", - "X-Dynamic": () => "dynamic-value", - }, - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-static"]).toBe("static-value"); - expect(calledOptions.headers["x-dynamic"]).toBe("dynamic-value"); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts deleted file mode 100644 index bde194554dd8..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/makeRequest.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import type { Mock } from "vitest"; -import { - isCacheNoStoreSupported, - makeRequest, - resetCacheNoStoreSupported, -} from "../../../src/core/fetcher/makeRequest"; - -describe("Test makeRequest", () => { - const mockPostUrl = "https://httpbin.org/post"; - const mockGetUrl = "https://httpbin.org/get"; - const mockHeaders = { "Content-Type": "application/json" }; - const mockBody = JSON.stringify({ key: "value" }); - - let mockFetch: Mock; - - beforeEach(() => { - mockFetch = vi.fn(); - mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); - resetCacheNoStoreSupported(); - }); - - it("should handle POST request correctly", async () => { - const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); - const responseBody = await response.json(); - expect(responseBody).toEqual({ test: "successful" }); - expect(mockFetch).toHaveBeenCalledTimes(1); - const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe(mockPostUrl); - expect(calledOptions).toEqual( - expect.objectContaining({ - method: "POST", - headers: mockHeaders, - body: mockBody, - credentials: undefined, - }), - ); - expect(calledOptions.signal).toBeDefined(); - expect(calledOptions.signal).toBeInstanceOf(AbortSignal); - }); - - it("should handle GET request correctly", async () => { - const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); - const responseBody = await response.json(); - expect(responseBody).toEqual({ test: "successful" }); - expect(mockFetch).toHaveBeenCalledTimes(1); - const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe(mockGetUrl); - expect(calledOptions).toEqual( - expect.objectContaining({ - method: "GET", - headers: mockHeaders, - body: undefined, - credentials: undefined, - }), - ); - expect(calledOptions.signal).toBeDefined(); - expect(calledOptions.signal).toBeInstanceOf(AbortSignal); - }); - - it("should not include cache option when disableCache is not set", async () => { - await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBeUndefined(); - }); - - it("should not include cache option when disableCache is false", async () => { - await makeRequest( - mockFetch, - mockGetUrl, - "GET", - mockHeaders, - undefined, - undefined, - undefined, - undefined, - undefined, - false, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBeUndefined(); - }); - - it("should include cache: no-store when disableCache is true and runtime supports it", async () => { - // In Node.js test environment, Request supports the cache option - expect(isCacheNoStoreSupported()).toBe(true); - await makeRequest( - mockFetch, - mockGetUrl, - "GET", - mockHeaders, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBe("no-store"); - }); - - it("should cache the result of isCacheNoStoreSupported", () => { - const first = isCacheNoStoreSupported(); - const second = isCacheNoStoreSupported(); - expect(first).toBe(second); - }); - - it("should reset cache detection state with resetCacheNoStoreSupported", () => { - // First call caches the result - const first = isCacheNoStoreSupported(); - expect(first).toBe(true); - - // Reset clears the cache - resetCacheNoStoreSupported(); - - // After reset, it should re-detect (and still return true in Node.js) - const second = isCacheNoStoreSupported(); - expect(second).toBe(true); - }); - - it("should not include cache option when runtime does not support it (e.g. Cloudflare Workers)", async () => { - // Mock Request constructor to throw when cache option is passed, - // simulating runtimes like Cloudflare Workers - const OriginalRequest = globalThis.Request; - globalThis.Request = class MockRequest { - constructor(_url: string, init?: RequestInit) { - if (init?.cache != null) { - throw new TypeError("The 'cache' field on 'RequestInitializerDict' is not implemented."); - } - } - } as unknown as typeof Request; - - try { - // Reset so the detection runs fresh with the mocked Request - resetCacheNoStoreSupported(); - expect(isCacheNoStoreSupported()).toBe(false); - - await makeRequest( - mockFetch, - mockGetUrl, - "GET", - mockHeaders, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBeUndefined(); - } finally { - // Restore original Request - globalThis.Request = OriginalRequest; - resetCacheNoStoreSupported(); - } - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts deleted file mode 100644 index d599376b9bcf..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/redacting.test.ts +++ /dev/null @@ -1,1115 +0,0 @@ -import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; - -function createMockLogger() { - return { - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }; -} - -function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify(data), { - status, - statusText, - }), - ); -} - -describe("Redacting Logic", () => { - describe("Header Redaction", () => { - it("should redact authorization header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { Authorization: "Bearer secret-token-12345" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - Authorization: "[REDACTED]", - }), - }), - ); - }); - - it("should redact api-key header (case-insensitive)", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "X-API-KEY": "secret-api-key" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "X-API-KEY": "[REDACTED]", - }), - }), - ); - }); - - it("should redact cookie header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { Cookie: "session=abc123; token=xyz789" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - Cookie: "[REDACTED]", - }), - }), - ); - }); - - it("should redact x-auth-token header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "x-auth-token": "auth-token-12345" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "x-auth-token": "[REDACTED]", - }), - }), - ); - }); - - it("should redact proxy-authorization header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "Proxy-Authorization": "Basic credentials" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "Proxy-Authorization": "[REDACTED]", - }), - }), - ); - }); - - it("should redact x-csrf-token header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "X-CSRF-Token": "csrf-token-abc" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "X-CSRF-Token": "[REDACTED]", - }), - }), - ); - }); - - it("should redact www-authenticate header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "WWW-Authenticate": "Bearer realm=example" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "WWW-Authenticate": "[REDACTED]", - }), - }), - ); - }); - - it("should redact x-session-token header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "X-Session-Token": "session-token-xyz" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "X-Session-Token": "[REDACTED]", - }), - }), - ); - }); - - it("should not redact non-sensitive headers", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { - "Content-Type": "application/json", - "User-Agent": "Test/1.0", - Accept: "application/json", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "Content-Type": "application/json", - "User-Agent": "Test/1.0", - Accept: "application/json", - }), - }), - ); - }); - - it("should redact multiple sensitive headers at once", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { - Authorization: "Bearer token", - "X-API-Key": "api-key", - Cookie: "session=123", - "Content-Type": "application/json", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - Authorization: "[REDACTED]", - "X-API-Key": "[REDACTED]", - Cookie: "[REDACTED]", - "Content-Type": "application/json", - }), - }), - ); - }); - }); - - describe("Response Header Redaction", () => { - it("should redact Set-Cookie in response headers", async () => { - const mockLogger = createMockLogger(); - - const mockHeaders = new Headers(); - mockHeaders.set("Set-Cookie", "session=abc123; HttpOnly; Secure"); - mockHeaders.set("Content-Type", "application/json"); - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - headers: mockHeaders, - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - responseHeaders: expect.toContainHeaders({ - "set-cookie": "[REDACTED]", - "content-type": "application/json", - }), - }), - ); - }); - - it("should redact authorization in response headers", async () => { - const mockLogger = createMockLogger(); - - const mockHeaders = new Headers(); - mockHeaders.set("Authorization", "Bearer token-123"); - mockHeaders.set("Content-Type", "application/json"); - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - headers: mockHeaders, - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - responseHeaders: expect.toContainHeaders({ - authorization: "[REDACTED]", - "content-type": "application/json", - }), - }), - ); - }); - - it("should redact response headers in error responses", async () => { - const mockLogger = createMockLogger(); - - const mockHeaders = new Headers(); - mockHeaders.set("WWW-Authenticate", "Bearer realm=example"); - mockHeaders.set("Content-Type", "application/json"); - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ error: "Unauthorized" }), { - status: 401, - statusText: "Unauthorized", - headers: mockHeaders, - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - responseHeaders: expect.toContainHeaders({ - "www-authenticate": "[REDACTED]", - "content-type": "application/json", - }), - }), - ); - }); - }); - - describe("Query Parameter Redaction", () => { - it("should redact api_key query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { api_key: "secret-key" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - api_key: "[REDACTED]", - }), - }), - ); - }); - - it("should redact token query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { token: "secret-token" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - token: "[REDACTED]", - }), - }), - ); - }); - - it("should redact access_token query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { access_token: "secret-access-token" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - access_token: "[REDACTED]", - }), - }), - ); - }); - - it("should redact password query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { password: "secret-password" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - password: "[REDACTED]", - }), - }), - ); - }); - - it("should redact secret query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { secret: "secret-value" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - secret: "[REDACTED]", - }), - }), - ); - }); - - it("should redact session_id query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { session_id: "session-123" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - session_id: "[REDACTED]", - }), - }), - ); - }); - - it("should not redact non-sensitive query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { - page: "1", - limit: "10", - sort: "name", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - page: "1", - limit: "10", - sort: "name", - }), - }), - ); - }); - - it("should not redact parameters containing 'auth' substring like 'author'", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { - author: "john", - authenticate: "false", - authorization_level: "user", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - author: "john", - authenticate: "false", - authorization_level: "user", - }), - }), - ); - }); - - it("should handle undefined query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: undefined, - }), - ); - }); - - it("should redact case-insensitive query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { API_KEY: "secret-key", Token: "secret-token" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - API_KEY: "[REDACTED]", - Token: "[REDACTED]", - }), - }), - ); - }); - }); - - describe("URL Redaction", () => { - it("should redact credentials in URL", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user:password@example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@example.com/api", - }), - ); - }); - - it("should redact api_key in query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?api_key=secret-key&page=1", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?api_key=[REDACTED]&page=1", - }), - ); - }); - - it("should redact token in query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?token=secret-token", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?token=[REDACTED]", - }), - ); - }); - - it("should redact password in query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?username=user&password=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?username=user&password=[REDACTED]", - }), - ); - }); - - it("should not redact non-sensitive query strings", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?page=1&limit=10&sort=name", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?page=1&limit=10&sort=name", - }), - ); - }); - - it("should not redact URL parameters containing 'auth' substring like 'author'", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?author=john&authenticate=false&page=1", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?author=john&authenticate=false&page=1", - }), - ); - }); - - it("should handle URL with fragment", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?token=secret#section", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?token=[REDACTED]#section", - }), - ); - }); - - it("should redact URL-encoded query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?api%5Fkey=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?api%5Fkey=[REDACTED]", - }), - ); - }); - - it("should handle URL without query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api", - }), - ); - }); - - it("should handle empty query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?", - }), - ); - }); - - it("should redact multiple sensitive parameters in URL", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?api_key=secret1&token=secret2&page=1", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?api_key=[REDACTED]&token=[REDACTED]&page=1", - }), - ); - }); - - it("should redact both credentials and query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user:pass@example.com/api?token=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@example.com/api?token=[REDACTED]", - }), - ); - }); - - it("should use fast path for URLs without sensitive keywords", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", - }), - ); - }); - - it("should handle query parameter without value", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?flag&token=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?flag&token=[REDACTED]", - }), - ); - }); - - it("should handle URL with multiple @ symbols in credentials", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user@example.com:pass@host.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@host.com/api", - }), - ); - }); - - it("should handle URL with @ in query parameter but not in credentials", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?email=user@example.com", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?email=user@example.com", - }), - ); - }); - - it("should handle URL with both credentials and @ in path", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user:pass@example.com/users/@username", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@example.com/users/@username", - }), - ); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts deleted file mode 100644 index d22661367f4e..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/requestWithRetries.test.ts +++ /dev/null @@ -1,230 +0,0 @@ -import type { Mock, MockInstance } from "vitest"; -import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; - -describe("requestWithRetries", () => { - let mockFetch: Mock; - let originalMathRandom: typeof Math.random; - let setTimeoutSpy: MockInstance; - - beforeEach(() => { - mockFetch = vi.fn(); - originalMathRandom = Math.random; - - Math.random = vi.fn(() => 0.5); - - vi.useFakeTimers({ - toFake: [ - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "Date", - "performance", - "requestAnimationFrame", - "cancelAnimationFrame", - "requestIdleCallback", - "cancelIdleCallback", - ], - }); - }); - - afterEach(() => { - Math.random = originalMathRandom; - vi.clearAllMocks(); - vi.clearAllTimers(); - }); - - it("should retry on retryable status codes", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const retryableStatuses = [408, 429, 500, 502]; - let callCount = 0; - - mockFetch.mockImplementation(async () => { - if (callCount < retryableStatuses.length) { - return new Response("", { status: retryableStatuses[callCount++] }); - } - return new Response("", { status: 200 }); - }); - - const responsePromise = requestWithRetries(() => mockFetch(), retryableStatuses.length); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(mockFetch).toHaveBeenCalledTimes(retryableStatuses.length + 1); - expect(response.status).toBe(200); - }); - - it("should respect maxRetries limit", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const maxRetries = 2; - mockFetch.mockResolvedValue(new Response("", { status: 500 })); - - const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); - expect(response.status).toBe(500); - }); - - it("should not retry on success status codes", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const successStatuses = [200, 201, 202]; - - for (const status of successStatuses) { - mockFetch.mockReset(); - setTimeoutSpy.mockClear(); - mockFetch.mockResolvedValueOnce(new Response("", { status })); - - const responsePromise = requestWithRetries(() => mockFetch(), 3); - await vi.runAllTimersAsync(); - await responsePromise; - - expect(mockFetch).toHaveBeenCalledTimes(1); - expect(setTimeoutSpy).not.toHaveBeenCalled(); - } - }); - - interface RetryHeaderTestCase { - description: string; - headerName: string; - headerValue: string | (() => string); - expectedDelayMin: number; - expectedDelayMax: number; - } - - const retryHeaderTests: RetryHeaderTestCase[] = [ - { - description: "should respect retry-after header with seconds value", - headerName: "retry-after", - headerValue: "5", - expectedDelayMin: 4000, - expectedDelayMax: 6000, - }, - { - description: "should respect retry-after header with HTTP date value", - headerName: "retry-after", - headerValue: () => new Date(Date.now() + 3000).toUTCString(), - expectedDelayMin: 2000, - expectedDelayMax: 4000, - }, - { - description: "should respect x-ratelimit-reset header", - headerName: "x-ratelimit-reset", - headerValue: () => Math.floor((Date.now() + 4000) / 1000).toString(), - expectedDelayMin: 3000, - expectedDelayMax: 6000, - }, - ]; - - retryHeaderTests.forEach(({ description, headerName, headerValue, expectedDelayMin, expectedDelayMax }) => { - it(description, async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const value = typeof headerValue === "function" ? headerValue() : headerValue; - mockFetch - .mockResolvedValueOnce( - new Response("", { - status: 429, - headers: new Headers({ [headerName]: value }), - }), - ) - .mockResolvedValueOnce(new Response("", { status: 200 })); - - const responsePromise = requestWithRetries(() => mockFetch(), 1); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), expect.any(Number)); - const actualDelay = setTimeoutSpy.mock.calls[0][1]; - expect(actualDelay).toBeGreaterThan(expectedDelayMin); - expect(actualDelay).toBeLessThan(expectedDelayMax); - expect(response.status).toBe(200); - }); - }); - - it("should apply correct exponential backoff with jitter", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - mockFetch.mockResolvedValue(new Response("", { status: 500 })); - const maxRetries = 3; - const expectedDelays = [1000, 2000, 4000]; - - const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); - await vi.runAllTimersAsync(); - await responsePromise; - - expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length); - - expectedDelays.forEach((delay, index) => { - expect(setTimeoutSpy).toHaveBeenNthCalledWith(index + 1, expect.any(Function), delay); - }); - - expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); - }); - - it("should handle concurrent retries independently", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - mockFetch - .mockResolvedValueOnce(new Response("", { status: 500 })) - .mockResolvedValueOnce(new Response("", { status: 500 })) - .mockResolvedValueOnce(new Response("", { status: 200 })) - .mockResolvedValueOnce(new Response("", { status: 200 })); - - const promise1 = requestWithRetries(() => mockFetch(), 1); - const promise2 = requestWithRetries(() => mockFetch(), 1); - - await vi.runAllTimersAsync(); - const [response1, response2] = await Promise.all([promise1, promise2]); - - expect(response1.status).toBe(200); - expect(response2.status).toBe(200); - }); - - it("should cap delay at MAX_RETRY_DELAY for large header values", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - mockFetch - .mockResolvedValueOnce( - new Response("", { - status: 429, - headers: new Headers({ "retry-after": "120" }), // 120 seconds = 120000ms > MAX_RETRY_DELAY (60000ms) - }), - ) - .mockResolvedValueOnce(new Response("", { status: 200 })); - - const responsePromise = requestWithRetries(() => mockFetch(), 1); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 60000); - expect(response.status).toBe(200); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts b/seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts deleted file mode 100644 index d7b6d1e63caa..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/signals.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; - -describe("Test getTimeoutSignal", () => { - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it("should return an object with signal and abortId", () => { - const { signal, abortId } = getTimeoutSignal(1000); - - expect(signal).toBeDefined(); - expect(abortId).toBeDefined(); - expect(signal).toBeInstanceOf(AbortSignal); - expect(signal.aborted).toBe(false); - }); - - it("should create a signal that aborts after the specified timeout", () => { - const timeoutMs = 5000; - const { signal } = getTimeoutSignal(timeoutMs); - - expect(signal.aborted).toBe(false); - - vi.advanceTimersByTime(timeoutMs - 1); - expect(signal.aborted).toBe(false); - - vi.advanceTimersByTime(1); - expect(signal.aborted).toBe(true); - }); -}); - -describe("Test anySignal", () => { - it("should return an AbortSignal", () => { - const signal = anySignal(new AbortController().signal); - expect(signal).toBeInstanceOf(AbortSignal); - }); - - it("should abort when any of the input signals is aborted", () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - const signal = anySignal(controller1.signal, controller2.signal); - - expect(signal.aborted).toBe(false); - controller1.abort(); - expect(signal.aborted).toBe(true); - }); - - it("should handle an array of signals", () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - const signal = anySignal([controller1.signal, controller2.signal]); - - expect(signal.aborted).toBe(false); - controller2.abort(); - expect(signal.aborted).toBe(true); - }); - - it("should abort immediately if one of the input signals is already aborted", () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - controller1.abort(); - - const signal = anySignal(controller1.signal, controller2.signal); - expect(signal.aborted).toBe(true); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt b/seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt deleted file mode 100644 index c66d471e359c..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/fetcher/test-file.txt +++ /dev/null @@ -1 +0,0 @@ -This is a test file! diff --git a/seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts b/seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts deleted file mode 100644 index 2e0b5fe5040c..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/logging/logger.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -import { ConsoleLogger, createLogger, Logger, LogLevel } from "../../../src/core/logging/logger"; - -function createMockLogger() { - return { - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }; -} - -describe("Logger", () => { - describe("LogLevel", () => { - it("should have correct log levels", () => { - expect(LogLevel.Debug).toBe("debug"); - expect(LogLevel.Info).toBe("info"); - expect(LogLevel.Warn).toBe("warn"); - expect(LogLevel.Error).toBe("error"); - }); - }); - - describe("ConsoleLogger", () => { - let consoleLogger: ConsoleLogger; - let consoleSpy: { - debug: ReturnType; - info: ReturnType; - warn: ReturnType; - error: ReturnType; - }; - - beforeEach(() => { - consoleLogger = new ConsoleLogger(); - consoleSpy = { - debug: vi.spyOn(console, "debug").mockImplementation(() => {}), - info: vi.spyOn(console, "info").mockImplementation(() => {}), - warn: vi.spyOn(console, "warn").mockImplementation(() => {}), - error: vi.spyOn(console, "error").mockImplementation(() => {}), - }; - }); - - afterEach(() => { - consoleSpy.debug.mockRestore(); - consoleSpy.info.mockRestore(); - consoleSpy.warn.mockRestore(); - consoleSpy.error.mockRestore(); - }); - - it("should log debug messages", () => { - consoleLogger.debug("debug message", { data: "test" }); - expect(consoleSpy.debug).toHaveBeenCalledWith("debug message", { data: "test" }); - }); - - it("should log info messages", () => { - consoleLogger.info("info message", { data: "test" }); - expect(consoleSpy.info).toHaveBeenCalledWith("info message", { data: "test" }); - }); - - it("should log warn messages", () => { - consoleLogger.warn("warn message", { data: "test" }); - expect(consoleSpy.warn).toHaveBeenCalledWith("warn message", { data: "test" }); - }); - - it("should log error messages", () => { - consoleLogger.error("error message", { data: "test" }); - expect(consoleSpy.error).toHaveBeenCalledWith("error message", { data: "test" }); - }); - - it("should handle multiple arguments", () => { - consoleLogger.debug("message", "arg1", "arg2", { key: "value" }); - expect(consoleSpy.debug).toHaveBeenCalledWith("message", "arg1", "arg2", { key: "value" }); - }); - }); - - describe("Logger with level filtering", () => { - let mockLogger: { - debug: ReturnType; - info: ReturnType; - warn: ReturnType; - error: ReturnType; - }; - - beforeEach(() => { - mockLogger = createMockLogger(); - }); - - describe("Debug level", () => { - it("should log all levels when set to debug", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).toHaveBeenCalledWith("debug"); - expect(mockLogger.info).toHaveBeenCalledWith("info"); - expect(mockLogger.warn).toHaveBeenCalledWith("warn"); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(true); - expect(logger.isInfo()).toBe(true); - expect(logger.isWarn()).toBe(true); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Info level", () => { - it("should log info, warn, and error when set to info", () => { - const logger = new Logger({ - level: LogLevel.Info, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).toHaveBeenCalledWith("info"); - expect(mockLogger.warn).toHaveBeenCalledWith("warn"); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Info, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(true); - expect(logger.isWarn()).toBe(true); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Warn level", () => { - it("should log warn and error when set to warn", () => { - const logger = new Logger({ - level: LogLevel.Warn, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).toHaveBeenCalledWith("warn"); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Warn, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(false); - expect(logger.isWarn()).toBe(true); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Error level", () => { - it("should only log error when set to error", () => { - const logger = new Logger({ - level: LogLevel.Error, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).not.toHaveBeenCalled(); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Error, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(false); - expect(logger.isWarn()).toBe(false); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Silent mode", () => { - it("should not log anything when silent is true", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: true, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).not.toHaveBeenCalled(); - expect(mockLogger.error).not.toHaveBeenCalled(); - }); - - it("should report all level checks as false when silent", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: true, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(false); - expect(logger.isWarn()).toBe(false); - expect(logger.isError()).toBe(false); - }); - }); - - describe("shouldLog", () => { - it("should correctly determine if level should be logged", () => { - const logger = new Logger({ - level: LogLevel.Info, - logger: mockLogger, - silent: false, - }); - - expect(logger.shouldLog(LogLevel.Debug)).toBe(false); - expect(logger.shouldLog(LogLevel.Info)).toBe(true); - expect(logger.shouldLog(LogLevel.Warn)).toBe(true); - expect(logger.shouldLog(LogLevel.Error)).toBe(true); - }); - - it("should return false for all levels when silent", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: true, - }); - - expect(logger.shouldLog(LogLevel.Debug)).toBe(false); - expect(logger.shouldLog(LogLevel.Info)).toBe(false); - expect(logger.shouldLog(LogLevel.Warn)).toBe(false); - expect(logger.shouldLog(LogLevel.Error)).toBe(false); - }); - }); - - describe("Multiple arguments", () => { - it("should pass multiple arguments to logger", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("message", "arg1", { key: "value" }, 123); - expect(mockLogger.debug).toHaveBeenCalledWith("message", "arg1", { key: "value" }, 123); - }); - }); - }); - - describe("createLogger", () => { - it("should return default logger when no config provided", () => { - const logger = createLogger(); - expect(logger).toBeInstanceOf(Logger); - }); - - it("should return same logger instance when Logger is passed", () => { - const customLogger = new Logger({ - level: LogLevel.Debug, - logger: new ConsoleLogger(), - silent: false, - }); - - const result = createLogger(customLogger); - expect(result).toBe(customLogger); - }); - - it("should create logger with custom config", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - level: LogLevel.Warn, - logger: mockLogger, - silent: false, - }); - - expect(logger).toBeInstanceOf(Logger); - logger.warn("test"); - expect(mockLogger.warn).toHaveBeenCalledWith("test"); - }); - - it("should use default values for missing config", () => { - const logger = createLogger({}); - expect(logger).toBeInstanceOf(Logger); - }); - - it("should override default level", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("test"); - expect(mockLogger.debug).toHaveBeenCalledWith("test"); - }); - - it("should override default silent mode", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - logger: mockLogger, - silent: false, - }); - - logger.info("test"); - expect(mockLogger.info).toHaveBeenCalledWith("test"); - }); - - it("should use provided logger implementation", () => { - const customLogger = createMockLogger(); - - const logger = createLogger({ - logger: customLogger, - level: LogLevel.Debug, - silent: false, - }); - - logger.debug("test"); - expect(customLogger.debug).toHaveBeenCalledWith("test"); - }); - - it("should default to silent: true", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - logger: mockLogger, - level: LogLevel.Debug, - }); - - logger.debug("test"); - expect(mockLogger.debug).not.toHaveBeenCalled(); - }); - }); - - describe("Default logger", () => { - it("should have silent: true by default", () => { - const logger = createLogger(); - expect(logger.shouldLog(LogLevel.Info)).toBe(false); - }); - - it("should not log when using default logger", () => { - const logger = createLogger(); - - logger.info("test"); - expect(logger.isInfo()).toBe(false); - }); - }); - - describe("Edge cases", () => { - it("should handle empty message", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug(""); - expect(mockLogger.debug).toHaveBeenCalledWith(""); - }); - - it("should handle no arguments", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("message"); - expect(mockLogger.debug).toHaveBeenCalledWith("message"); - }); - - it("should handle complex objects", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - const complexObject = { - nested: { key: "value" }, - array: [1, 2, 3], - fn: () => "test", - }; - - logger.debug("message", complexObject); - expect(mockLogger.debug).toHaveBeenCalledWith("message", complexObject); - }); - - it("should handle errors as arguments", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Error, - logger: mockLogger, - silent: false, - }); - - const error = new Error("Test error"); - logger.error("Error occurred", error); - expect(mockLogger.error).toHaveBeenCalledWith("Error occurred", error); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts b/seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts deleted file mode 100644 index 123488f084ea..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/url/join.test.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { join } from "../../../src/core/url/index"; - -describe("join", () => { - interface TestCase { - description: string; - base: string; - segments: string[]; - expected: string; - } - - describe("basic functionality", () => { - const basicTests: TestCase[] = [ - { description: "should return empty string for empty base", base: "", segments: [], expected: "" }, - { - description: "should return empty string for empty base with path", - base: "", - segments: ["path"], - expected: "", - }, - { - description: "should handle single segment", - base: "base", - segments: ["segment"], - expected: "base/segment", - }, - { - description: "should handle single segment with trailing slash on base", - base: "base/", - segments: ["segment"], - expected: "base/segment", - }, - { - description: "should handle single segment with leading slash", - base: "base", - segments: ["/segment"], - expected: "base/segment", - }, - { - description: "should handle single segment with both slashes", - base: "base/", - segments: ["/segment"], - expected: "base/segment", - }, - { - description: "should handle multiple segments", - base: "base", - segments: ["path1", "path2", "path3"], - expected: "base/path1/path2/path3", - }, - { - description: "should handle multiple segments with slashes", - base: "base/", - segments: ["/path1/", "/path2/", "/path3/"], - expected: "base/path1/path2/path3/", - }, - ]; - - basicTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("URL handling", () => { - const urlTests: TestCase[] = [ - { - description: "should handle absolute URLs", - base: "https://example.com", - segments: ["api", "v1"], - expected: "https://example.com/api/v1", - }, - { - description: "should handle absolute URLs with slashes", - base: "https://example.com/", - segments: ["/api/", "/v1/"], - expected: "https://example.com/api/v1/", - }, - { - description: "should handle absolute URLs with base path", - base: "https://example.com/base", - segments: ["api", "v1"], - expected: "https://example.com/base/api/v1", - }, - { - description: "should preserve URL query parameters", - base: "https://example.com?query=1", - segments: ["api"], - expected: "https://example.com/api?query=1", - }, - { - description: "should preserve URL fragments", - base: "https://example.com#fragment", - segments: ["api"], - expected: "https://example.com/api#fragment", - }, - { - description: "should preserve URL query and fragments", - base: "https://example.com?query=1#fragment", - segments: ["api"], - expected: "https://example.com/api?query=1#fragment", - }, - { - description: "should handle http protocol", - base: "http://example.com", - segments: ["api"], - expected: "http://example.com/api", - }, - { - description: "should handle ftp protocol", - base: "ftp://example.com", - segments: ["files"], - expected: "ftp://example.com/files", - }, - { - description: "should handle ws protocol", - base: "ws://example.com", - segments: ["socket"], - expected: "ws://example.com/socket", - }, - { - description: "should fallback to path joining for malformed URLs", - base: "not-a-url://", - segments: ["path"], - expected: "not-a-url:///path", - }, - ]; - - urlTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("edge cases", () => { - const edgeCaseTests: TestCase[] = [ - { - description: "should handle empty segments", - base: "base", - segments: ["", "path"], - expected: "base/path", - }, - { - description: "should handle null segments", - base: "base", - segments: [null as any, "path"], - expected: "base/path", - }, - { - description: "should handle undefined segments", - base: "base", - segments: [undefined as any, "path"], - expected: "base/path", - }, - { - description: "should handle segments with only single slash", - base: "base", - segments: ["/", "path"], - expected: "base/path", - }, - { - description: "should handle segments with only double slash", - base: "base", - segments: ["//", "path"], - expected: "base/path", - }, - { - description: "should handle base paths with trailing slashes", - base: "base/", - segments: ["path"], - expected: "base/path", - }, - { - description: "should handle complex nested paths", - base: "api/v1/", - segments: ["/users/", "/123/", "/profile"], - expected: "api/v1/users/123/profile", - }, - ]; - - edgeCaseTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("real-world scenarios", () => { - const realWorldTests: TestCase[] = [ - { - description: "should handle API endpoint construction", - base: "https://api.example.com/v1", - segments: ["users", "123", "posts"], - expected: "https://api.example.com/v1/users/123/posts", - }, - { - description: "should handle file path construction", - base: "/var/www", - segments: ["html", "assets", "images"], - expected: "/var/www/html/assets/images", - }, - { - description: "should handle relative path construction", - base: "../parent", - segments: ["child", "grandchild"], - expected: "../parent/child/grandchild", - }, - { - description: "should handle Windows-style paths", - base: "C:\\Users", - segments: ["Documents", "file.txt"], - expected: "C:\\Users/Documents/file.txt", - }, - ]; - - realWorldTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("performance scenarios", () => { - it("should handle many segments efficiently", () => { - const segments = Array(100).fill("segment"); - const result = join("base", ...segments); - expect(result).toBe(`base/${segments.join("/")}`); - }); - - it("should handle long URLs", () => { - const longPath = "a".repeat(1000); - expect(join("https://example.com", longPath)).toBe(`https://example.com/${longPath}`); - }); - }); - - describe("trailing slash preservation", () => { - const trailingSlashTests: TestCase[] = [ - { - description: - "should preserve trailing slash on final result when base has trailing slash and no segments", - base: "https://api.example.com/", - segments: [], - expected: "https://api.example.com/", - }, - { - description: "should preserve trailing slash on v1 path", - base: "https://api.example.com/v1/", - segments: [], - expected: "https://api.example.com/v1/", - }, - { - description: "should preserve trailing slash when last segment has trailing slash", - base: "https://api.example.com", - segments: ["users/"], - expected: "https://api.example.com/users/", - }, - { - description: "should preserve trailing slash with relative path", - base: "api/v1", - segments: ["users/"], - expected: "api/v1/users/", - }, - { - description: "should preserve trailing slash with multiple segments", - base: "https://api.example.com", - segments: ["v1", "collections/"], - expected: "https://api.example.com/v1/collections/", - }, - { - description: "should preserve trailing slash with base path", - base: "base", - segments: ["path1", "path2/"], - expected: "base/path1/path2/", - }, - ]; - - trailingSlashTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts b/seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts deleted file mode 100644 index 42cdffb9e5ea..000000000000 --- a/seed/ts-sdk/allof-inline/tests/unit/url/qs.test.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { toQueryString } from "../../../src/core/url/index"; - -describe("Test qs toQueryString", () => { - interface BasicTestCase { - description: string; - input: any; - expected: string; - } - - describe("Basic functionality", () => { - const basicTests: BasicTestCase[] = [ - { description: "should return empty string for null", input: null, expected: "" }, - { description: "should return empty string for undefined", input: undefined, expected: "" }, - { description: "should return empty string for string primitive", input: "hello", expected: "" }, - { description: "should return empty string for number primitive", input: 42, expected: "" }, - { description: "should return empty string for true boolean", input: true, expected: "" }, - { description: "should return empty string for false boolean", input: false, expected: "" }, - { description: "should handle empty objects", input: {}, expected: "" }, - { - description: "should handle simple key-value pairs", - input: { name: "John", age: 30 }, - expected: "name=John&age=30", - }, - ]; - - basicTests.forEach(({ description, input, expected }) => { - it(description, () => { - expect(toQueryString(input)).toBe(expected); - }); - }); - }); - - describe("Array handling", () => { - interface ArrayTestCase { - description: string; - input: any; - options?: { arrayFormat?: "repeat" | "indices" }; - expected: string; - } - - const arrayTests: ArrayTestCase[] = [ - { - description: "should handle arrays with indices format (default)", - input: { items: ["a", "b", "c"] }, - expected: "items%5B0%5D=a&items%5B1%5D=b&items%5B2%5D=c", - }, - { - description: "should handle arrays with repeat format", - input: { items: ["a", "b", "c"] }, - options: { arrayFormat: "repeat" }, - expected: "items=a&items=b&items=c", - }, - { - description: "should handle empty arrays", - input: { items: [] }, - expected: "", - }, - { - description: "should handle arrays with mixed types", - input: { mixed: ["string", 42, true, false] }, - expected: "mixed%5B0%5D=string&mixed%5B1%5D=42&mixed%5B2%5D=true&mixed%5B3%5D=false", - }, - { - description: "should handle arrays with objects", - input: { users: [{ name: "John" }, { name: "Jane" }] }, - expected: "users%5B0%5D%5Bname%5D=John&users%5B1%5D%5Bname%5D=Jane", - }, - { - description: "should handle arrays with objects in repeat format", - input: { users: [{ name: "John" }, { name: "Jane" }] }, - options: { arrayFormat: "repeat" }, - expected: "users%5Bname%5D=John&users%5Bname%5D=Jane", - }, - ]; - - arrayTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); - - describe("Nested objects", () => { - const nestedTests: BasicTestCase[] = [ - { - description: "should handle nested objects", - input: { user: { name: "John", age: 30 } }, - expected: "user%5Bname%5D=John&user%5Bage%5D=30", - }, - { - description: "should handle deeply nested objects", - input: { user: { profile: { name: "John", settings: { theme: "dark" } } } }, - expected: "user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", - }, - { - description: "should handle empty nested objects", - input: { user: {} }, - expected: "", - }, - ]; - - nestedTests.forEach(({ description, input, expected }) => { - it(description, () => { - expect(toQueryString(input)).toBe(expected); - }); - }); - }); - - describe("Encoding", () => { - interface EncodingTestCase { - description: string; - input: any; - options?: { encode?: boolean }; - expected: string; - } - - const encodingTests: EncodingTestCase[] = [ - { - description: "should encode by default", - input: { name: "John Doe", email: "john@example.com" }, - expected: "name=John%20Doe&email=john%40example.com", - }, - { - description: "should not encode when encode is false", - input: { name: "John Doe", email: "john@example.com" }, - options: { encode: false }, - expected: "name=John Doe&email=john@example.com", - }, - { - description: "should encode special characters in keys", - input: { "user name": "John", "email[primary]": "john@example.com" }, - expected: "user%20name=John&email%5Bprimary%5D=john%40example.com", - }, - { - description: "should not encode special characters in keys when encode is false", - input: { "user name": "John", "email[primary]": "john@example.com" }, - options: { encode: false }, - expected: "user name=John&email[primary]=john@example.com", - }, - ]; - - encodingTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); - - describe("Mixed scenarios", () => { - interface MixedTestCase { - description: string; - input: any; - options?: { arrayFormat?: "repeat" | "indices" }; - expected: string; - } - - const mixedTests: MixedTestCase[] = [ - { - description: "should handle complex nested structures", - input: { - filters: { - status: ["active", "pending"], - category: { - type: "electronics", - subcategories: ["phones", "laptops"], - }, - }, - sort: { field: "name", direction: "asc" }, - }, - expected: - "filters%5Bstatus%5D%5B0%5D=active&filters%5Bstatus%5D%5B1%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D%5B0%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D%5B1%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", - }, - { - description: "should handle complex nested structures with repeat format", - input: { - filters: { - status: ["active", "pending"], - category: { - type: "electronics", - subcategories: ["phones", "laptops"], - }, - }, - sort: { field: "name", direction: "asc" }, - }, - options: { arrayFormat: "repeat" }, - expected: - "filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", - }, - { - description: "should handle arrays with null/undefined values", - input: { items: ["a", null, "c", undefined, "e"] }, - expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e", - }, - { - description: "should handle objects with null/undefined values", - input: { name: "John", age: null, email: undefined, active: true }, - expected: "name=John&age=&active=true", - }, - ]; - - mixedTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); - - describe("Edge cases", () => { - const edgeCaseTests: BasicTestCase[] = [ - { - description: "should handle numeric keys", - input: { "0": "zero", "1": "one" }, - expected: "0=zero&1=one", - }, - { - description: "should handle boolean values in objects", - input: { enabled: true, disabled: false }, - expected: "enabled=true&disabled=false", - }, - { - description: "should handle empty strings", - input: { name: "", description: "test" }, - expected: "name=&description=test", - }, - { - description: "should handle zero values", - input: { count: 0, price: 0.0 }, - expected: "count=0&price=0", - }, - { - description: "should handle arrays with empty strings", - input: { items: ["a", "", "c"] }, - expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c", - }, - ]; - - edgeCaseTests.forEach(({ description, input, expected }) => { - it(description, () => { - expect(toQueryString(input)).toBe(expected); - }); - }); - }); - - describe("Options combinations", () => { - interface OptionsTestCase { - description: string; - input: any; - options?: { arrayFormat?: "repeat" | "indices"; encode?: boolean }; - expected: string; - } - - const optionsTests: OptionsTestCase[] = [ - { - description: "should respect both arrayFormat and encode options", - input: { items: ["a & b", "c & d"] }, - options: { arrayFormat: "repeat", encode: false }, - expected: "items=a & b&items=c & d", - }, - { - description: "should use default options when none provided", - input: { items: ["a", "b"] }, - expected: "items%5B0%5D=a&items%5B1%5D=b", - }, - { - description: "should merge provided options with defaults", - input: { items: ["a", "b"], name: "John Doe" }, - options: { encode: false }, - expected: "items[0]=a&items[1]=b&name=John Doe", - }, - ]; - - optionsTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tests/wire/.gitkeep b/seed/ts-sdk/allof-inline/tests/wire/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/ts-sdk/allof-inline/tests/wire/main.test.ts b/seed/ts-sdk/allof-inline/tests/wire/main.test.ts deleted file mode 100644 index 322f94682c9e..000000000000 --- a/seed/ts-sdk/allof-inline/tests/wire/main.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import { SeedApiClient } from "../../src/Client"; -import { mockServerPool } from "../mock-server/MockServerPool"; - -describe("SeedApiClient", () => { - test("searchRuleTypes", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { - paging: { next: "next", previous: "previous" }, - results: [{ id: "id", name: "name", description: "description" }], - }; - - server.mockEndpoint().get("/rule-types").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.searchRuleTypes(); - expect(response).toEqual(rawResponseBody); - }); - - test("createRule", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - const rawRequestBody = { name: "name", executionContext: "prod" }; - const rawResponseBody = { - createdBy: "createdBy", - createdDateTime: "2024-01-15T09:30:00Z", - modifiedBy: "modifiedBy", - modifiedDateTime: "2024-01-15T09:30:00Z", - id: "id", - name: "name", - status: "active", - executionContext: "prod", - }; - - server - .mockEndpoint() - .post("/rules") - .jsonBody(rawRequestBody) - .respondWith() - .statusCode(200) - .jsonBody(rawResponseBody) - .build(); - - const response = await client.createRule({ - name: "name", - executionContext: "prod", - }); - expect(response).toEqual(rawResponseBody); - }); - - test("listUsers", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { - paging: { next: "next", previous: "previous" }, - results: [{ id: "id", email: "email" }], - }; - - server.mockEndpoint().get("/users").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.listUsers(); - expect(response).toEqual(rawResponseBody); - }); - - test("getEntity", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { id: "id", name: "name", summary: "summary", status: "active" }; - - server.mockEndpoint().get("/entities").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.getEntity(); - expect(response).toEqual(rawResponseBody); - }); - - test("getOrganization", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { id: "id", metadata: { region: "region", domain: "domain" }, name: "name" }; - - server.mockEndpoint().get("/organizations").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.getOrganization(); - expect(response).toEqual(rawResponseBody); - }); -}); diff --git a/seed/ts-sdk/allof-inline/tsconfig.base.json b/seed/ts-sdk/allof-inline/tsconfig.base.json deleted file mode 100644 index 93a92c0630b5..000000000000 --- a/seed/ts-sdk/allof-inline/tsconfig.base.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "extendedDiagnostics": true, - "strict": true, - "target": "ES6", - "moduleResolution": "node", - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "outDir": "dist", - "rootDir": "src", - "isolatedModules": true, - "isolatedDeclarations": true - }, - "include": ["src"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof-inline/tsconfig.cjs.json b/seed/ts-sdk/allof-inline/tsconfig.cjs.json deleted file mode 100644 index 5c11446f5984..000000000000 --- a/seed/ts-sdk/allof-inline/tsconfig.cjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "CommonJS", - "outDir": "dist/cjs" - }, - "include": ["src"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof-inline/tsconfig.esm.json b/seed/ts-sdk/allof-inline/tsconfig.esm.json deleted file mode 100644 index 6ce909748b2c..000000000000 --- a/seed/ts-sdk/allof-inline/tsconfig.esm.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "outDir": "dist/esm", - "verbatimModuleSyntax": true - }, - "include": ["src"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof-inline/tsconfig.json b/seed/ts-sdk/allof-inline/tsconfig.json deleted file mode 100644 index d77fdf00d259..000000000000 --- a/seed/ts-sdk/allof-inline/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./tsconfig.cjs.json" -} diff --git a/seed/ts-sdk/allof-inline/vitest.config.mts b/seed/ts-sdk/allof-inline/vitest.config.mts deleted file mode 100644 index 0dee5a752d39..000000000000 --- a/seed/ts-sdk/allof-inline/vitest.config.mts +++ /dev/null @@ -1,32 +0,0 @@ -import { defineConfig } from "vitest/config"; -export default defineConfig({ - test: { - typecheck: { - enabled: true, - tsconfig: "./tests/tsconfig.json", - }, - projects: [ - { - test: { - globals: true, - name: "unit", - environment: "node", - root: "./tests", - include: ["**/*.test.{js,ts,jsx,tsx}"], - exclude: ["wire/**"], - setupFiles: ["./setup.ts"], - }, - }, - { - test: { - globals: true, - name: "wire", - environment: "node", - root: "./tests/wire", - setupFiles: ["../setup.ts", "../mock-server/setup.ts"], - }, - }, - ], - passWithNoTests: true, - }, -}); diff --git a/seed/ts-sdk/allof/.fern/metadata.json b/seed/ts-sdk/allof/.fern/metadata.json deleted file mode 100644 index 7b69b32c1f15..000000000000 --- a/seed/ts-sdk/allof/.fern/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "local", - "originGitCommit": "DUMMY", - "sdkVersion": "0.0.1" -} diff --git a/seed/ts-sdk/allof/.github/workflows/ci.yml b/seed/ts-sdk/allof/.github/workflows/ci.yml deleted file mode 100644 index 93fba226cb67..000000000000 --- a/seed/ts-sdk/allof/.github/workflows/ci.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: ci - -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - compile: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Set up node - uses: actions/setup-node@v6 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Compile - run: pnpm build - - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Set up node - uses: actions/setup-node@v6 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Test - run: pnpm test diff --git a/seed/ts-sdk/allof/.gitignore b/seed/ts-sdk/allof/.gitignore deleted file mode 100644 index 72271e049c02..000000000000 --- a/seed/ts-sdk/allof/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -.DS_Store -/dist \ No newline at end of file diff --git a/seed/ts-sdk/allof/CONTRIBUTING.md b/seed/ts-sdk/allof/CONTRIBUTING.md deleted file mode 100644 index fe5bc2f77e0b..000000000000 --- a/seed/ts-sdk/allof/CONTRIBUTING.md +++ /dev/null @@ -1,133 +0,0 @@ -# Contributing - -Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project. - -## Getting Started - -### Prerequisites - -- Node.js 20 or higher -- pnpm package manager - -### Installation - -Install the project dependencies: - -```bash -pnpm install -``` - -### Building - -Build the project: - -```bash -pnpm build -``` - -### Testing - -Run the test suite: - -```bash -pnpm test -``` - -Run specific test types: -- `pnpm test:unit` - Run unit tests -- `pnpm test:wire` - Run wire/integration tests - -### Linting and Formatting - -Check code style: - -```bash -pnpm run lint -pnpm run format:check -``` - -Fix code style issues: - -```bash -pnpm run lint:fix -pnpm run format:fix -``` - -Or use the combined check command: - -```bash -pnpm run check:fix -``` - -## About Generated Code - -**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated. - -### Generated Files - -The following directories contain generated code: -- `src/api/` - API client classes and types -- `src/serialization/` - Serialization/deserialization logic -- Most TypeScript files in `src/` - -### How to Customize - -If you need to customize the SDK, you have two options: - -#### Option 1: Use `.fernignore` - -For custom code that should persist across SDK regenerations: - -1. Create a `.fernignore` file in the project root -2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax) -3. Add your custom code to those files - -Files listed in `.fernignore` will not be overwritten when the SDK is regenerated. - -For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code). - -#### Option 2: Contribute to the Generator - -If you want to change how code is generated for all users of this SDK: - -1. The TypeScript SDK generator lives in the [Fern repository](https://github.com/fern-api/fern) -2. Generator code is located at `generators/typescript/sdk/` -3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md) -4. Submit a pull request with your changes to the generator - -This approach is best for: -- Bug fixes in generated code -- New features that would benefit all users -- Improvements to code generation patterns - -## Making Changes - -### Workflow - -1. Create a new branch for your changes -2. Make your modifications -3. Run tests to ensure nothing breaks: `pnpm test` -4. Run linting and formatting: `pnpm run check:fix` -5. Build the project: `pnpm build` -6. Commit your changes with a clear commit message -7. Push your branch and create a pull request - -### Commit Messages - -Write clear, descriptive commit messages that explain what changed and why. - -### Code Style - -This project uses automated code formatting and linting. Run `pnpm run check:fix` before committing to ensure your code meets the project's style guidelines. - -## Questions or Issues? - -If you have questions or run into issues: - -1. Check the [Fern documentation](https://buildwithfern.com) -2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues) -3. Open a new issue if your question hasn't been addressed - -## License - -By contributing to this project, you agree that your contributions will be licensed under the same license as the project. diff --git a/seed/ts-sdk/allof/README.md b/seed/ts-sdk/allof/README.md deleted file mode 100644 index e8c268db5356..000000000000 --- a/seed/ts-sdk/allof/README.md +++ /dev/null @@ -1,292 +0,0 @@ -# Seed TypeScript Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) -[![npm shield](https://img.shields.io/npm/v/@fern/allof)](https://www.npmjs.com/package/@fern/allof) - -The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Request and Response Types](#request-and-response-types) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Aborting Requests](#aborting-requests) - - [Access Raw Response Data](#access-raw-response-data) - - [Logging](#logging) - - [Custom Fetch](#custom-fetch) - - [Runtime Compatibility](#runtime-compatibility) -- [Contributing](#contributing) - -## Installation - -```sh -npm i -s @fern/allof -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```typescript -import { SeedApiClient } from "@fern/allof"; - -const client = new SeedApiClient; -await client.createRule({ - name: "name", - executionContext: "prod" -}); -``` - -## Environments - -This SDK allows you to configure different environments for API requests. - -```typescript -import { SeedApiClient, SeedApiEnvironment } from "@fern/allof"; - -const client = new SeedApiClient({ - environment: SeedApiEnvironment.Default, -}); -``` - -## Request and Response Types - -The SDK exports all request and response types as TypeScript interfaces. Simply import them with the -following namespace: - -```typescript -import { SeedApi } from "@fern/allof"; - -const request: SeedApi.SearchRuleTypesRequest = { - ... -}; -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```typescript -import { SeedApiError } from "@fern/allof"; - -try { - await client.createRule(...); -} catch (err) { - if (err instanceof SeedApiError) { - console.log(err.statusCode); - console.log(err.message); - console.log(err.body); - console.log(err.rawResponse); - } -} -``` - -## Advanced - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `headers` request option. - -```typescript -import { SeedApiClient } from "@fern/allof"; - -const client = new SeedApiClient({ - ... - headers: { - 'X-Custom-Header': 'custom value' - } -}); - -const response = await client.createRule(..., { - headers: { - 'X-Custom-Header': 'custom value' - } -}); -``` - -### Additional Query String Parameters - -If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. - -```typescript -const response = await client.createRule(..., { - queryParams: { - 'customQueryParamKey': 'custom query param value' - } -}); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` request option to configure this behavior. - -```typescript -const response = await client.createRule(..., { - maxRetries: 0 // override maxRetries at the request level -}); -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. - -```typescript -const response = await client.createRule(..., { - timeoutInSeconds: 30 // override timeout to 30s -}); -``` - -### Aborting Requests - -The SDK allows users to abort requests at any point by passing in an abort signal. - -```typescript -const controller = new AbortController(); -const response = await client.createRule(..., { - abortSignal: controller.signal -}); -controller.abort(); // aborts the request -``` - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. -The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. - -```typescript -const { data, rawResponse } = await client.createRule(...).withRawResponse(); - -console.log(data); -console.log(rawResponse.headers['X-My-Header']); -``` - -### Logging - -The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. - -```typescript -import { SeedApiClient, logging } from "@fern/allof"; - -const client = new SeedApiClient({ - ... - logging: { - level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info - logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger - silent: false, // defaults to true, set to false to enable logging - } -}); -``` -The `logging` object can have the following properties: -- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. -- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. -- `silent`: Whether to silence the logger. Defaults to `true`. - -The `level` property can be one of the following values: -- `logging.LogLevel.Debug` -- `logging.LogLevel.Info` -- `logging.LogLevel.Warn` -- `logging.LogLevel.Error` - -To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. - -
-Custom logger examples - -Here's an example using the popular `winston` logging library. -```ts -import winston from 'winston'; - -const winstonLogger = winston.createLogger({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => winstonLogger.debug(msg, ...args), - info: (msg, ...args) => winstonLogger.info(msg, ...args), - warn: (msg, ...args) => winstonLogger.warn(msg, ...args), - error: (msg, ...args) => winstonLogger.error(msg, ...args), -}; -``` - -Here's an example using the popular `pino` logging library. - -```ts -import pino from 'pino'; - -const pinoLogger = pino({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => pinoLogger.debug(args, msg), - info: (msg, ...args) => pinoLogger.info(args, msg), - warn: (msg, ...args) => pinoLogger.warn(args, msg), - error: (msg, ...args) => pinoLogger.error(args, msg), -}; -``` -
- - -### Custom Fetch - -The SDK provides a low-level `fetch` method for making custom HTTP requests while still -benefiting from SDK-level configuration like authentication, retries, timeouts, and logging. -This is useful for calling API endpoints not yet supported in the SDK. - -```typescript -const response = await client.fetch("/v1/custom/endpoint", { - method: "GET", -}, { - timeoutInSeconds: 30, - maxRetries: 3, - headers: { - "X-Custom-Header": "custom-value", - }, -}); - -const data = await response.json(); -``` - -### Runtime Compatibility - - -The SDK works in the following runtimes: - - - -- Node.js 18+ -- Vercel -- Cloudflare Workers -- Deno v1.25+ -- Bun 1.0+ -- React Native - - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/allof/biome.json b/seed/ts-sdk/allof/biome.json deleted file mode 100644 index 6b89164f9f99..000000000000 --- a/seed/ts-sdk/allof/biome.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", - "root": true, - "vcs": { - "enabled": false - }, - "files": { - "ignoreUnknown": true, - "includes": [ - "**", - "!!dist", - "!!**/dist", - "!!lib", - "!!**/lib", - "!!_tmp_*", - "!!**/_tmp_*", - "!!*.tmp", - "!!**/*.tmp", - "!!.tmp/", - "!!**/.tmp/", - "!!*.log", - "!!**/*.log", - "!!**/.DS_Store", - "!!**/Thumbs.db" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 4, - "lineWidth": 120 - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - }, - "linter": { - "rules": { - "style": { - "useNodejsImportProtocol": "off" - }, - "suspicious": { - "noAssignInExpressions": "warn", - "noUselessEscapeInString": { - "level": "warn", - "fix": "none", - "options": {} - }, - "noThenProperty": "warn", - "useIterableCallbackReturn": "warn", - "noShadowRestrictedNames": "warn", - "noTsIgnore": { - "level": "warn", - "fix": "none", - "options": {} - }, - "noConfusingVoidType": { - "level": "warn", - "fix": "none", - "options": {} - } - } - } - } -} diff --git a/seed/ts-sdk/allof/package.json b/seed/ts-sdk/allof/package.json deleted file mode 100644 index b0d091c7b2b1..000000000000 --- a/seed/ts-sdk/allof/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@fern/allof", - "version": "0.0.1", - "private": false, - "repository": { - "type": "git", - "url": "git+https://github.com/allof/fern.git" - }, - "type": "commonjs", - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.mjs", - "types": "./dist/cjs/index.d.ts", - "exports": { - ".": { - "import": { - "types": "./dist/esm/index.d.mts", - "default": "./dist/esm/index.mjs" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - }, - "default": "./dist/cjs/index.js" - }, - "./package.json": "./package.json" - }, - "files": [ - "dist", - "reference.md", - "README.md", - "LICENSE" - ], - "scripts": { - "format": "biome format --write --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "format:check": "biome format --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "lint": "biome lint --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "lint:fix": "biome lint --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "check": "biome check --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "check:fix": "biome check --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none", - "build": "pnpm build:cjs && pnpm build:esm", - "build:cjs": "tsc --project ./tsconfig.cjs.json", - "build:esm": "tsc --project ./tsconfig.esm.json && node scripts/rename-to-esm-files.js dist/esm", - "test": "vitest", - "test:unit": "vitest --project unit", - "test:wire": "vitest --project wire" - }, - "dependencies": {}, - "devDependencies": { - "webpack": "^5.105.4", - "ts-loader": "^9.5.4", - "vitest": "^4.1.1", - "msw": "2.11.2", - "@types/node": "^18.19.70", - "typescript": "~5.9.3", - "@biomejs/biome": "2.4.10" - }, - "browser": { - "fs": false, - "os": false, - "path": false, - "stream": false, - "crypto": false - }, - "packageManager": "pnpm@10.33.0", - "engines": { - "node": ">=18.0.0" - }, - "sideEffects": false -} diff --git a/seed/ts-sdk/allof/pnpm-workspace.yaml b/seed/ts-sdk/allof/pnpm-workspace.yaml deleted file mode 100644 index 6e4c395107df..000000000000 --- a/seed/ts-sdk/allof/pnpm-workspace.yaml +++ /dev/null @@ -1 +0,0 @@ -packages: ['.'] \ No newline at end of file diff --git a/seed/ts-sdk/allof/reference.md b/seed/ts-sdk/allof/reference.md deleted file mode 100644 index 6d75591df79d..000000000000 --- a/seed/ts-sdk/allof/reference.md +++ /dev/null @@ -1,225 +0,0 @@ -# Reference -
client.searchRuleTypes({ ...params }) -> SeedApi.RuleTypeSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.searchRuleTypes(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `SeedApi.SearchRuleTypesRequest` - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.createRule({ ...params }) -> SeedApi.RuleResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.createRule({ - name: "name", - executionContext: "prod" -}); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `SeedApi.RuleCreateRequest` - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.listUsers() -> SeedApi.UserSearchResponse -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.listUsers(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.getEntity() -> SeedApi.CombinedEntity -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.getEntity(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- -
client.getOrganization() -> SeedApi.Organization -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.getOrganization(); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**requestOptions:** `SeedApiClient.RequestOptions` - -
-
-
-
- - -
-
-
- diff --git a/seed/ts-sdk/allof/scripts/rename-to-esm-files.js b/seed/ts-sdk/allof/scripts/rename-to-esm-files.js deleted file mode 100644 index dc1df1cbbacb..000000000000 --- a/seed/ts-sdk/allof/scripts/rename-to-esm-files.js +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env node - -const fs = require("fs").promises; -const path = require("path"); - -const extensionMap = { - ".js": ".mjs", - ".d.ts": ".d.mts", -}; -const oldExtensions = Object.keys(extensionMap); - -async function findFiles(rootPath) { - const files = []; - - async function scan(directory) { - const entries = await fs.readdir(directory, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(directory, entry.name); - - if (entry.isDirectory()) { - if (entry.name !== "node_modules" && !entry.name.startsWith(".")) { - await scan(fullPath); - } - } else if (entry.isFile()) { - if (oldExtensions.some((ext) => entry.name.endsWith(ext))) { - files.push(fullPath); - } - } - } - } - - await scan(rootPath); - return files; -} - -async function updateFiles(files) { - const updatedFiles = []; - for (const file of files) { - const updated = await updateFileContents(file); - updatedFiles.push(updated); - } - - console.log(`Updated imports in ${updatedFiles.length} files.`); -} - -async function updateFileContents(file) { - const content = await fs.readFile(file, "utf8"); - - let newContent = content; - // Update each extension type defined in the map - for (const [oldExt, newExt] of Object.entries(extensionMap)) { - // Handle static imports/exports - const staticRegex = new RegExp(`(import|export)(.+from\\s+['"])(\\.\\.?\\/[^'"]+)(\\${oldExt})(['"])`, "g"); - newContent = newContent.replace(staticRegex, `$1$2$3${newExt}$5`); - - // Handle dynamic imports (yield import, await import, regular import()) - const dynamicRegex = new RegExp( - `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, - "g", - ); - newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); - } - - if (content !== newContent) { - await fs.writeFile(file, newContent, "utf8"); - return true; - } - return false; -} - -async function renameFiles(files) { - let counter = 0; - for (const file of files) { - const ext = oldExtensions.find((ext) => file.endsWith(ext)); - const newExt = extensionMap[ext]; - - if (newExt) { - const newPath = file.slice(0, -ext.length) + newExt; - await fs.rename(file, newPath); - counter++; - } - } - - console.log(`Renamed ${counter} files.`); -} - -async function main() { - try { - const targetDir = process.argv[2]; - if (!targetDir) { - console.error("Please provide a target directory"); - process.exit(1); - } - - const targetPath = path.resolve(targetDir); - const targetStats = await fs.stat(targetPath); - - if (!targetStats.isDirectory()) { - console.error("The provided path is not a directory"); - process.exit(1); - } - - console.log(`Scanning directory: ${targetDir}`); - - const files = await findFiles(targetDir); - - if (files.length === 0) { - console.log("No matching files found."); - process.exit(0); - } - - console.log(`Found ${files.length} files.`); - await updateFiles(files); - await renameFiles(files); - console.log("\nDone!"); - } catch (error) { - console.error("An error occurred:", error.message); - process.exit(1); - } -} - -main(); diff --git a/seed/ts-sdk/allof/snippet.json b/seed/ts-sdk/allof/snippet.json deleted file mode 100644 index 77c90740f6d5..000000000000 --- a/seed/ts-sdk/allof/snippet.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "endpoints": [ - { - "id": { - "path": "/rule-types", - "method": "GET", - "identifier_override": "endpoint_.searchRuleTypes" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.searchRuleTypes();\n" - } - }, - { - "id": { - "path": "/rules", - "method": "POST", - "identifier_override": "endpoint_.createRule" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.createRule({\n name: \"name\",\n executionContext: \"prod\"\n});\n" - } - }, - { - "id": { - "path": "/users", - "method": "GET", - "identifier_override": "endpoint_.listUsers" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.listUsers();\n" - } - }, - { - "id": { - "path": "/entities", - "method": "GET", - "identifier_override": "endpoint_.getEntity" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.getEntity();\n" - } - }, - { - "id": { - "path": "/organizations", - "method": "GET", - "identifier_override": "endpoint_.getOrganization" - }, - "snippet": { - "type": "typescript", - "client": "import { SeedApiClient } from \"@fern/allof\";\n\nconst client = new SeedApiClient;\nawait client.getOrganization();\n" - } - } - ], - "types": {} -} \ No newline at end of file diff --git a/seed/ts-sdk/allof/src/BaseClient.ts b/seed/ts-sdk/allof/src/BaseClient.ts deleted file mode 100644 index ad5cd965175e..000000000000 --- a/seed/ts-sdk/allof/src/BaseClient.ts +++ /dev/null @@ -1,60 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import { mergeHeaders } from "./core/headers.js"; -import * as core from "./core/index.js"; -import type * as environments from "./environments.js"; - -export interface BaseClientOptions { - environment?: core.Supplier; - /** Specify a custom URL to connect the client to. */ - baseUrl?: core.Supplier; - /** Additional headers to include in requests. */ - headers?: Record | null | undefined>; - /** The default maximum time to wait for a response in seconds. */ - timeoutInSeconds?: number; - /** The default number of times to retry the request. Defaults to 2. */ - maxRetries?: number; - /** Provide a custom fetch implementation. Useful for platforms that don't have a built-in fetch or need a custom implementation. */ - fetch?: typeof fetch; - /** Configure logging for the client. */ - logging?: core.logging.LogConfig | core.logging.Logger; -} - -export interface BaseRequestOptions { - /** The maximum time to wait for a response in seconds. */ - timeoutInSeconds?: number; - /** The number of times to retry the request. Defaults to 2. */ - maxRetries?: number; - /** A hook to abort the request. */ - abortSignal?: AbortSignal; - /** Additional query string parameters to include in the request. */ - queryParams?: Record; - /** Additional headers to include in the request. */ - headers?: Record | null | undefined>; -} - -export type NormalizedClientOptions = T & { - logging: core.logging.Logger; -}; - -export function normalizeClientOptions( - options: T, -): NormalizedClientOptions { - const headers = mergeHeaders( - { - "X-Fern-Language": "JavaScript", - "X-Fern-SDK-Name": "@fern/allof", - "X-Fern-SDK-Version": "0.0.1", - "User-Agent": "@fern/allof/0.0.1", - "X-Fern-Runtime": core.RUNTIME.type, - "X-Fern-Runtime-Version": core.RUNTIME.version, - }, - options?.headers, - ); - - return { - ...options, - logging: core.logging.createLogger(options?.logging), - headers, - } as NormalizedClientOptions; -} diff --git a/seed/ts-sdk/allof/src/Client.ts b/seed/ts-sdk/allof/src/Client.ts deleted file mode 100644 index 01e5b4de5533..000000000000 --- a/seed/ts-sdk/allof/src/Client.ts +++ /dev/null @@ -1,303 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "./api/index.js"; -import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; -import { type NormalizedClientOptions, normalizeClientOptions } from "./BaseClient.js"; -import { mergeHeaders } from "./core/headers.js"; -import * as core from "./core/index.js"; -import * as environments from "./environments.js"; -import { handleNonStatusCodeError } from "./errors/handleNonStatusCodeError.js"; -import * as errors from "./errors/index.js"; - -export declare namespace SeedApiClient { - export type Options = BaseClientOptions; - - export interface RequestOptions extends BaseRequestOptions {} -} - -export class SeedApiClient { - protected readonly _options: NormalizedClientOptions; - - constructor(options: SeedApiClient.Options = {}) { - this._options = normalizeClientOptions(options); - } - - /** - * @param {SeedApi.SearchRuleTypesRequest} request - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.searchRuleTypes() - */ - public searchRuleTypes( - request: SeedApi.SearchRuleTypesRequest = {}, - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__searchRuleTypes(request, requestOptions)); - } - - private async __searchRuleTypes( - request: SeedApi.SearchRuleTypesRequest = {}, - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const { query } = request; - const _queryParams: Record = { - query, - }; - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "rule-types", - ), - method: "GET", - headers: _headers, - queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.RuleTypeSearchResponse, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/rule-types"); - } - - /** - * @param {SeedApi.RuleCreateRequest} request - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.createRule({ - * name: "name", - * executionContext: "prod" - * }) - */ - public createRule( - request: SeedApi.RuleCreateRequest, - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__createRule(request, requestOptions)); - } - - private async __createRule( - request: SeedApi.RuleCreateRequest, - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "rules", - ), - method: "POST", - headers: _headers, - contentType: "application/json", - queryParameters: requestOptions?.queryParams, - requestType: "json", - body: request, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.RuleResponse, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/rules"); - } - - /** - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.listUsers() - */ - public listUsers( - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__listUsers(requestOptions)); - } - - private async __listUsers( - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "users", - ), - method: "GET", - headers: _headers, - queryParameters: requestOptions?.queryParams, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.UserSearchResponse, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/users"); - } - - /** - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.getEntity() - */ - public getEntity(requestOptions?: SeedApiClient.RequestOptions): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__getEntity(requestOptions)); - } - - private async __getEntity( - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "entities", - ), - method: "GET", - headers: _headers, - queryParameters: requestOptions?.queryParams, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.CombinedEntity, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/entities"); - } - - /** - * @param {SeedApiClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.getOrganization() - */ - public getOrganization( - requestOptions?: SeedApiClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__getOrganization(requestOptions)); - } - - private async __getOrganization( - requestOptions?: SeedApiClient.RequestOptions, - ): Promise> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)) ?? - environments.SeedApiEnvironment.Default, - "organizations", - ), - method: "GET", - headers: _headers, - queryParameters: requestOptions?.queryParams, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { data: _response.body as SeedApi.Organization, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - throw new errors.SeedApiError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/organizations"); - } - - /** - * Make a passthrough request using the SDK's configured auth, retry, logging, etc. - * This is useful for making requests to endpoints not yet supported in the SDK. - * The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL. - * - * @param {Request | string | URL} input - The URL, path, or Request object. - * @param {RequestInit} init - Standard fetch RequestInit options. - * @param {core.PassthroughRequest.RequestOptions} requestOptions - Per-request overrides (timeout, retries, headers, abort signal). - * @returns {Promise} A standard Response object. - */ - public async fetch( - input: Request | string | URL, - init?: RequestInit, - requestOptions?: core.PassthroughRequest.RequestOptions, - ): Promise { - return core.makePassthroughRequest( - input, - init, - { - baseUrl: this._options.baseUrl ?? this._options.environment, - headers: this._options.headers, - timeoutInSeconds: this._options.timeoutInSeconds, - maxRetries: this._options.maxRetries, - fetch: this._options.fetch, - logging: this._options.logging, - }, - requestOptions, - ); - } -} diff --git a/seed/ts-sdk/allof/src/api/client/index.ts b/seed/ts-sdk/allof/src/api/client/index.ts deleted file mode 100644 index 195f9aa8a846..000000000000 --- a/seed/ts-sdk/allof/src/api/client/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./requests/index.js"; diff --git a/seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts b/seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts deleted file mode 100644 index bb42c4d24ac9..000000000000 --- a/seed/ts-sdk/allof/src/api/client/requests/RuleCreateRequest.ts +++ /dev/null @@ -1,15 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../../index.js"; - -/** - * @example - * { - * name: "name", - * executionContext: "prod" - * } - */ -export interface RuleCreateRequest { - name: string; - executionContext: SeedApi.RuleExecutionContext; -} diff --git a/seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts b/seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts deleted file mode 100644 index 502888d9c4e3..000000000000 --- a/seed/ts-sdk/allof/src/api/client/requests/SearchRuleTypesRequest.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** - * @example - * {} - */ -export interface SearchRuleTypesRequest { - query?: string; -} diff --git a/seed/ts-sdk/allof/src/api/client/requests/index.ts b/seed/ts-sdk/allof/src/api/client/requests/index.ts deleted file mode 100644 index 07aecd81dd45..000000000000 --- a/seed/ts-sdk/allof/src/api/client/requests/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { RuleCreateRequest } from "./RuleCreateRequest.js"; -export type { SearchRuleTypesRequest } from "./SearchRuleTypesRequest.js"; diff --git a/seed/ts-sdk/allof/src/api/index.ts b/seed/ts-sdk/allof/src/api/index.ts deleted file mode 100644 index d9adb1af9a93..000000000000 --- a/seed/ts-sdk/allof/src/api/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./client/index.js"; -export * from "./types/index.js"; diff --git a/seed/ts-sdk/allof/src/api/types/AuditInfo.ts b/seed/ts-sdk/allof/src/api/types/AuditInfo.ts deleted file mode 100644 index 535489319123..000000000000 --- a/seed/ts-sdk/allof/src/api/types/AuditInfo.ts +++ /dev/null @@ -1,15 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** - * Common audit metadata. - */ -export interface AuditInfo { - /** The user who created this resource. */ - createdBy?: string | undefined; - /** When this resource was created. */ - createdDateTime?: string | undefined; - /** The user who last modified this resource. */ - modifiedBy?: string | undefined; - /** When this resource was last modified. */ - modifiedDateTime?: string | undefined; -} diff --git a/seed/ts-sdk/allof/src/api/types/BaseOrg.ts b/seed/ts-sdk/allof/src/api/types/BaseOrg.ts deleted file mode 100644 index eec0dea9a386..000000000000 --- a/seed/ts-sdk/allof/src/api/types/BaseOrg.ts +++ /dev/null @@ -1,15 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface BaseOrg { - id: string; - metadata?: BaseOrg.Metadata | undefined; -} - -export namespace BaseOrg { - export interface Metadata { - /** Deployment region from BaseOrg. */ - region: string; - /** Subscription tier. */ - tier?: string | undefined; - } -} diff --git a/seed/ts-sdk/allof/src/api/types/CombinedEntity.ts b/seed/ts-sdk/allof/src/api/types/CombinedEntity.ts deleted file mode 100644 index 7cb0214b4706..000000000000 --- a/seed/ts-sdk/allof/src/api/types/CombinedEntity.ts +++ /dev/null @@ -1,19 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface CombinedEntity { - status: CombinedEntity.Status; - /** Unique identifier. */ - id: string; - /** Display name from Identifiable. */ - name?: string | undefined; - /** A short summary. */ - summary?: string | undefined; -} - -export namespace CombinedEntity { - export const Status = { - Active: "active", - Archived: "archived", - } as const; - export type Status = (typeof Status)[keyof typeof Status]; -} diff --git a/seed/ts-sdk/allof/src/api/types/Describable.ts b/seed/ts-sdk/allof/src/api/types/Describable.ts deleted file mode 100644 index b5c82cac5a67..000000000000 --- a/seed/ts-sdk/allof/src/api/types/Describable.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface Describable { - /** Display name from Describable. */ - name?: string | undefined; - /** A short summary. */ - summary?: string | undefined; -} diff --git a/seed/ts-sdk/allof/src/api/types/DetailedOrg.ts b/seed/ts-sdk/allof/src/api/types/DetailedOrg.ts deleted file mode 100644 index 84e0ef063cab..000000000000 --- a/seed/ts-sdk/allof/src/api/types/DetailedOrg.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface DetailedOrg { - metadata?: DetailedOrg.Metadata | undefined; -} - -export namespace DetailedOrg { - export interface Metadata { - /** Deployment region from DetailedOrg. */ - region: string; - /** Custom domain name. */ - domain?: string | undefined; - } -} diff --git a/seed/ts-sdk/allof/src/api/types/Identifiable.ts b/seed/ts-sdk/allof/src/api/types/Identifiable.ts deleted file mode 100644 index 65d2053f6cb8..000000000000 --- a/seed/ts-sdk/allof/src/api/types/Identifiable.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface Identifiable { - /** Unique identifier. */ - id: string; - /** Display name from Identifiable. */ - name?: string | undefined; -} diff --git a/seed/ts-sdk/allof/src/api/types/Organization.ts b/seed/ts-sdk/allof/src/api/types/Organization.ts deleted file mode 100644 index 21138b82840f..000000000000 --- a/seed/ts-sdk/allof/src/api/types/Organization.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface Organization { - name: string; - id: string; - metadata?: Organization.Metadata | undefined; -} - -export namespace Organization { - export interface Metadata { - /** Deployment region from BaseOrg. */ - region: string; - /** Subscription tier. */ - tier?: string | undefined; - } -} diff --git a/seed/ts-sdk/allof/src/api/types/PaginatedResult.ts b/seed/ts-sdk/allof/src/api/types/PaginatedResult.ts deleted file mode 100644 index ed373200a8e1..000000000000 --- a/seed/ts-sdk/allof/src/api/types/PaginatedResult.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface PaginatedResult { - paging: SeedApi.PagingCursors; - /** Current page of results from the requested resource. */ - results: unknown[]; -} diff --git a/seed/ts-sdk/allof/src/api/types/PagingCursors.ts b/seed/ts-sdk/allof/src/api/types/PagingCursors.ts deleted file mode 100644 index 2ff3fa532101..000000000000 --- a/seed/ts-sdk/allof/src/api/types/PagingCursors.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface PagingCursors { - /** Cursor for the next page of results. */ - next: string; - /** Cursor for the previous page of results. */ - previous?: string | undefined; -} diff --git a/seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts b/seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts deleted file mode 100644 index fe794a8856da..000000000000 --- a/seed/ts-sdk/allof/src/api/types/RuleExecutionContext.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** Execution environment for a rule. */ -export const RuleExecutionContext = { - Prod: "prod", - Staging: "staging", - Dev: "dev", -} as const; -export type RuleExecutionContext = (typeof RuleExecutionContext)[keyof typeof RuleExecutionContext]; diff --git a/seed/ts-sdk/allof/src/api/types/RuleResponse.ts b/seed/ts-sdk/allof/src/api/types/RuleResponse.ts deleted file mode 100644 index 79865f6c0808..000000000000 --- a/seed/ts-sdk/allof/src/api/types/RuleResponse.ts +++ /dev/null @@ -1,19 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface RuleResponse extends SeedApi.AuditInfo { - id: string; - name: string; - status: RuleResponse.Status; - executionContext?: SeedApi.RuleExecutionContext | undefined; -} - -export namespace RuleResponse { - export const Status = { - Active: "active", - Inactive: "inactive", - Draft: "draft", - } as const; - export type Status = (typeof Status)[keyof typeof Status]; -} diff --git a/seed/ts-sdk/allof/src/api/types/RuleType.ts b/seed/ts-sdk/allof/src/api/types/RuleType.ts deleted file mode 100644 index ac2bde7133b2..000000000000 --- a/seed/ts-sdk/allof/src/api/types/RuleType.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface RuleType { - id: string; - name: string; - description?: string | undefined; -} diff --git a/seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts b/seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts deleted file mode 100644 index fef8e24a64cc..000000000000 --- a/seed/ts-sdk/allof/src/api/types/RuleTypeSearchResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface RuleTypeSearchResponse { - /** Current page of results from the requested resource. */ - results?: SeedApi.RuleType[] | undefined; - paging: SeedApi.PagingCursors; -} diff --git a/seed/ts-sdk/allof/src/api/types/User.ts b/seed/ts-sdk/allof/src/api/types/User.ts deleted file mode 100644 index 7d0e30eaf136..000000000000 --- a/seed/ts-sdk/allof/src/api/types/User.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export interface User { - id: string; - email: string; -} diff --git a/seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts b/seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts deleted file mode 100644 index d9c018237c2c..000000000000 --- a/seed/ts-sdk/allof/src/api/types/UserSearchResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as SeedApi from "../index.js"; - -export interface UserSearchResponse { - /** Current page of results from the requested resource. */ - results?: SeedApi.User[] | undefined; - paging: SeedApi.PagingCursors; -} diff --git a/seed/ts-sdk/allof/src/api/types/index.ts b/seed/ts-sdk/allof/src/api/types/index.ts deleted file mode 100644 index ae8a133ce81f..000000000000 --- a/seed/ts-sdk/allof/src/api/types/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export * from "./AuditInfo.js"; -export * from "./BaseOrg.js"; -export * from "./CombinedEntity.js"; -export * from "./Describable.js"; -export * from "./DetailedOrg.js"; -export * from "./Identifiable.js"; -export * from "./Organization.js"; -export * from "./PaginatedResult.js"; -export * from "./PagingCursors.js"; -export * from "./RuleExecutionContext.js"; -export * from "./RuleResponse.js"; -export * from "./RuleType.js"; -export * from "./RuleTypeSearchResponse.js"; -export * from "./User.js"; -export * from "./UserSearchResponse.js"; diff --git a/seed/ts-sdk/allof/src/core/exports.ts b/seed/ts-sdk/allof/src/core/exports.ts deleted file mode 100644 index 69296d7100d6..000000000000 --- a/seed/ts-sdk/allof/src/core/exports.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./logging/exports.js"; diff --git a/seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts b/seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts deleted file mode 100644 index 97ab83c2b195..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/APIResponse.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { RawResponse } from "./RawResponse.js"; - -/** - * The response of an API call. - * It is a successful response or a failed response. - */ -export type APIResponse = SuccessfulResponse | FailedResponse; - -export interface SuccessfulResponse { - ok: true; - body: T; - /** - * @deprecated Use `rawResponse` instead - */ - headers?: Record; - rawResponse: RawResponse; -} - -export interface FailedResponse { - ok: false; - error: T; - rawResponse: RawResponse; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts b/seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts deleted file mode 100644 index b9e40fb62cc4..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/BinaryResponse.ts +++ /dev/null @@ -1,34 +0,0 @@ -export type BinaryResponse = { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ - bodyUsed: Response["bodyUsed"]; - /** - * Returns a ReadableStream of the response body. - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) - */ - stream: () => Response["body"]; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ - arrayBuffer: () => ReturnType; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ - blob: () => ReturnType; - /** - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) - * Some versions of the Fetch API may not support this method. - */ - bytes?(): Promise; -}; - -export function getBinaryResponse(response: Response): BinaryResponse { - const binaryResponse: BinaryResponse = { - get bodyUsed() { - return response.bodyUsed; - }, - stream: () => response.body, - arrayBuffer: response.arrayBuffer.bind(response), - blob: response.blob.bind(response), - }; - if ("bytes" in response && typeof response.bytes === "function") { - binaryResponse.bytes = response.bytes.bind(response); - } - - return binaryResponse; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts b/seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts deleted file mode 100644 index 998d68f5c20c..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/EndpointMetadata.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type SecuritySchemeKey = string; -/** - * A collection of security schemes, where the key is the name of the security scheme and the value is the list of scopes required for that scheme. - * All schemes in the collection must be satisfied for authentication to be successful. - */ -export type SecuritySchemeCollection = Record; -export type AuthScope = string; -export type EndpointMetadata = { - /** - * An array of security scheme collections. Each collection represents an alternative way to authenticate. - */ - security?: SecuritySchemeCollection[]; -}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts b/seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts deleted file mode 100644 index aad81f0d9040..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/EndpointSupplier.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { EndpointMetadata } from "./EndpointMetadata.js"; -import type { Supplier } from "./Supplier.js"; - -type EndpointSupplierFn = (arg: { endpointMetadata?: EndpointMetadata }) => T | Promise; -export type EndpointSupplier = Supplier | EndpointSupplierFn; -export const EndpointSupplier = { - get: async (supplier: EndpointSupplier, arg: { endpointMetadata?: EndpointMetadata }): Promise => { - if (typeof supplier === "function") { - return (supplier as EndpointSupplierFn)(arg); - } else { - return supplier; - } - }, -}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts deleted file mode 100644 index 928dfeaabae6..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts +++ /dev/null @@ -1,404 +0,0 @@ -import { toJson } from "../json.js"; -import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; -import type { APIResponse } from "./APIResponse.js"; -import { createRequestUrl } from "./createRequestUrl.js"; -import type { EndpointMetadata } from "./EndpointMetadata.js"; -import { EndpointSupplier } from "./EndpointSupplier.js"; -import { getErrorResponseBody } from "./getErrorResponseBody.js"; -import { getFetchFn } from "./getFetchFn.js"; -import { getRequestBody } from "./getRequestBody.js"; -import { getResponseBody } from "./getResponseBody.js"; -import { Headers } from "./Headers.js"; -import { makeRequest } from "./makeRequest.js"; -import { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; -import { requestWithRetries } from "./requestWithRetries.js"; - -export type FetchFunction = (args: Fetcher.Args) => Promise>; - -export declare namespace Fetcher { - export interface Args { - url: string; - method: string; - contentType?: string; - headers?: Record; - queryParameters?: Record; - body?: unknown; - timeoutMs?: number; - maxRetries?: number; - withCredentials?: boolean; - abortSignal?: AbortSignal; - requestType?: "json" | "file" | "bytes" | "form" | "other"; - responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer" | "binary-response"; - duplex?: "half"; - endpointMetadata?: EndpointMetadata; - fetchFn?: typeof fetch; - logging?: LogConfig | Logger; - } - - export type Error = FailedStatusCodeError | NonJsonError | BodyIsNullError | TimeoutError | UnknownError; - - export interface FailedStatusCodeError { - reason: "status-code"; - statusCode: number; - body: unknown; - } - - export interface NonJsonError { - reason: "non-json"; - statusCode: number; - rawBody: string; - } - - export interface BodyIsNullError { - reason: "body-is-null"; - statusCode: number; - } - - export interface TimeoutError { - reason: "timeout"; - cause?: unknown; - } - - export interface UnknownError { - reason: "unknown"; - errorMessage: string; - cause?: unknown; - } -} - -const SENSITIVE_HEADERS = new Set([ - "authorization", - "www-authenticate", - "x-api-key", - "api-key", - "apikey", - "x-api-token", - "x-auth-token", - "auth-token", - "cookie", - "set-cookie", - "proxy-authorization", - "proxy-authenticate", - "x-csrf-token", - "x-xsrf-token", - "x-session-token", - "x-access-token", -]); - -function redactHeaders(headers: Headers | Record): Record { - const filtered: Record = {}; - for (const [key, value] of headers instanceof Headers ? headers.entries() : Object.entries(headers)) { - if (SENSITIVE_HEADERS.has(key.toLowerCase())) { - filtered[key] = "[REDACTED]"; - } else { - filtered[key] = value; - } - } - return filtered; -} - -const SENSITIVE_QUERY_PARAMS = new Set([ - "api_key", - "api-key", - "apikey", - "token", - "access_token", - "access-token", - "auth_token", - "auth-token", - "password", - "passwd", - "secret", - "api_secret", - "api-secret", - "apisecret", - "key", - "session", - "session_id", - "session-id", -]); - -function redactQueryParameters(queryParameters?: Record): Record | undefined { - if (queryParameters == null) { - return queryParameters; - } - const redacted: Record = {}; - for (const [key, value] of Object.entries(queryParameters)) { - if (SENSITIVE_QUERY_PARAMS.has(key.toLowerCase())) { - redacted[key] = "[REDACTED]"; - } else { - redacted[key] = value; - } - } - return redacted; -} - -function redactUrl(url: string): string { - const protocolIndex = url.indexOf("://"); - if (protocolIndex === -1) return url; - - const afterProtocol = protocolIndex + 3; - - // Find the first delimiter that marks the end of the authority section - const pathStart = url.indexOf("/", afterProtocol); - let queryStart = url.indexOf("?", afterProtocol); - let fragmentStart = url.indexOf("#", afterProtocol); - - const firstDelimiter = Math.min( - pathStart === -1 ? url.length : pathStart, - queryStart === -1 ? url.length : queryStart, - fragmentStart === -1 ? url.length : fragmentStart, - ); - - // Find the LAST @ before the delimiter (handles multiple @ in credentials) - let atIndex = -1; - for (let i = afterProtocol; i < firstDelimiter; i++) { - if (url[i] === "@") { - atIndex = i; - } - } - - if (atIndex !== -1) { - url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`; - } - - // Recalculate queryStart since url might have changed - queryStart = url.indexOf("?"); - if (queryStart === -1) return url; - - fragmentStart = url.indexOf("#", queryStart); - const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length; - const queryString = url.slice(queryStart + 1, queryEnd); - - if (queryString.length === 0) return url; - - // FAST PATH: Quick check if any sensitive keywords present - // Using indexOf is faster than regex for simple substring matching - const lower = queryString.toLowerCase(); - const hasSensitive = - lower.includes("token") || - lower.includes("key") || - lower.includes("password") || - lower.includes("passwd") || - lower.includes("secret") || - lower.includes("session") || - lower.includes("auth"); - - if (!hasSensitive) { - return url; - } - - // SLOW PATH: Parse and redact - const redactedParams: string[] = []; - const params = queryString.split("&"); - - for (const param of params) { - const equalIndex = param.indexOf("="); - if (equalIndex === -1) { - redactedParams.push(param); - continue; - } - - const key = param.slice(0, equalIndex); - let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()); - - if (!shouldRedact && key.includes("%")) { - try { - const decodedKey = decodeURIComponent(key); - shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase()); - } catch {} - } - - redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param); - } - - return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd); -} - -async function getHeaders(args: Fetcher.Args): Promise { - const newHeaders: Headers = new Headers(); - - newHeaders.set( - "Accept", - args.responseType === "json" - ? "application/json" - : args.responseType === "text" - ? "text/plain" - : args.responseType === "sse" - ? "text/event-stream" - : "*/*", - ); - if (args.body !== undefined && args.contentType != null) { - newHeaders.set("Content-Type", args.contentType); - } - - if (args.headers == null) { - return newHeaders; - } - - for (const [key, value] of Object.entries(args.headers)) { - const result = await EndpointSupplier.get(value, { endpointMetadata: args.endpointMetadata ?? {} }); - if (typeof result === "string") { - newHeaders.set(key, result); - continue; - } - if (result == null) { - continue; - } - newHeaders.set(key, `${result}`); - } - return newHeaders; -} - -export async function fetcherImpl(args: Fetcher.Args): Promise> { - const url = createRequestUrl(args.url, args.queryParameters); - const requestBody: BodyInit | undefined = await getRequestBody({ - body: args.body, - type: args.requestType ?? "other", - }); - const fetchFn = args.fetchFn ?? (await getFetchFn()); - const headers = await getHeaders(args); - const logger = createLogger(args.logging); - - if (logger.isDebug()) { - const metadata = { - method: args.method, - url: redactUrl(url), - headers: redactHeaders(headers), - queryParameters: redactQueryParameters(args.queryParameters), - hasBody: requestBody != null, - }; - logger.debug("Making HTTP request", metadata); - } - - try { - const response = await requestWithRetries( - async () => - makeRequest( - fetchFn, - url, - args.method, - headers, - requestBody, - args.timeoutMs, - args.abortSignal, - args.withCredentials, - args.duplex, - args.responseType === "streaming" || args.responseType === "sse", - ), - args.maxRetries, - ); - - if (response.status >= 200 && response.status < 400) { - if (logger.isDebug()) { - const metadata = { - method: args.method, - url: redactUrl(url), - statusCode: response.status, - responseHeaders: redactHeaders(response.headers), - }; - logger.debug("HTTP request succeeded", metadata); - } - const body = await getResponseBody(response, args.responseType); - return { - ok: true, - body: body as R, - headers: response.headers, - rawResponse: toRawResponse(response), - }; - } else { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - statusCode: response.status, - responseHeaders: redactHeaders(Object.fromEntries(response.headers.entries())), - }; - logger.error("HTTP request failed with error status", metadata); - } - return { - ok: false, - error: { - reason: "status-code", - statusCode: response.status, - body: await getErrorResponseBody(response), - }, - rawResponse: toRawResponse(response), - }; - } - } catch (error) { - if (args.abortSignal?.aborted) { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - }; - logger.error("HTTP request was aborted", metadata); - } - return { - ok: false, - error: { - reason: "unknown", - errorMessage: "The user aborted a request", - cause: error, - }, - rawResponse: abortRawResponse, - }; - } else if (error instanceof Error && error.name === "AbortError") { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - timeoutMs: args.timeoutMs, - }; - logger.error("HTTP request timed out", metadata); - } - return { - ok: false, - error: { - reason: "timeout", - cause: error, - }, - rawResponse: abortRawResponse, - }; - } else if (error instanceof Error) { - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - errorMessage: error.message, - }; - logger.error("HTTP request failed with error", metadata); - } - return { - ok: false, - error: { - reason: "unknown", - errorMessage: error.message, - cause: error, - }, - rawResponse: unknownRawResponse, - }; - } - - if (logger.isError()) { - const metadata = { - method: args.method, - url: redactUrl(url), - error: toJson(error), - }; - logger.error("HTTP request failed with unknown error", metadata); - } - return { - ok: false, - error: { - reason: "unknown", - errorMessage: toJson(error), - cause: error, - }, - rawResponse: unknownRawResponse, - }; - } -} - -export const fetcher: FetchFunction = fetcherImpl; diff --git a/seed/ts-sdk/allof/src/core/fetcher/Headers.ts b/seed/ts-sdk/allof/src/core/fetcher/Headers.ts deleted file mode 100644 index af841aa24f55..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/Headers.ts +++ /dev/null @@ -1,93 +0,0 @@ -let Headers: typeof globalThis.Headers; - -if (typeof globalThis.Headers !== "undefined") { - Headers = globalThis.Headers; -} else { - Headers = class Headers implements Headers { - private headers: Map; - - constructor(init?: HeadersInit) { - this.headers = new Map(); - - if (init) { - if (init instanceof Headers) { - init.forEach((value, key) => this.append(key, value)); - } else if (Array.isArray(init)) { - for (const [key, value] of init) { - if (typeof key === "string" && typeof value === "string") { - this.append(key, value); - } else { - throw new TypeError("Each header entry must be a [string, string] tuple"); - } - } - } else { - for (const [key, value] of Object.entries(init)) { - if (typeof value === "string") { - this.append(key, value); - } else { - throw new TypeError("Header values must be strings"); - } - } - } - } - } - - append(name: string, value: string): void { - const key = name.toLowerCase(); - const existing = this.headers.get(key) || []; - this.headers.set(key, [...existing, value]); - } - - delete(name: string): void { - const key = name.toLowerCase(); - this.headers.delete(key); - } - - get(name: string): string | null { - const key = name.toLowerCase(); - const values = this.headers.get(key); - return values ? values.join(", ") : null; - } - - has(name: string): boolean { - const key = name.toLowerCase(); - return this.headers.has(key); - } - - set(name: string, value: string): void { - const key = name.toLowerCase(); - this.headers.set(key, [value]); - } - - forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: unknown): void { - const boundCallback = thisArg ? callbackfn.bind(thisArg) : callbackfn; - this.headers.forEach((values, key) => boundCallback(values.join(", "), key, this)); - } - - getSetCookie(): string[] { - return this.headers.get("set-cookie") || []; - } - - *entries(): HeadersIterator<[string, string]> { - for (const [key, values] of this.headers.entries()) { - yield [key, values.join(", ")]; - } - } - - *keys(): HeadersIterator { - yield* this.headers.keys(); - } - - *values(): HeadersIterator { - for (const values of this.headers.values()) { - yield values.join(", "); - } - } - - [Symbol.iterator](): HeadersIterator<[string, string]> { - return this.entries(); - } - }; -} - -export { Headers }; diff --git a/seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts b/seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts deleted file mode 100644 index 692ca7d795f0..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/HttpResponsePromise.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { WithRawResponse } from "./RawResponse.js"; - -/** - * A promise that returns the parsed response and lets you retrieve the raw response too. - */ -export class HttpResponsePromise extends Promise { - private innerPromise: Promise>; - private unwrappedPromise: Promise | undefined; - - private constructor(promise: Promise>) { - // Initialize with a no-op to avoid premature parsing - super((resolve) => { - resolve(undefined as unknown as T); - }); - this.innerPromise = promise; - } - - /** - * Creates an `HttpResponsePromise` from a function that returns a promise. - * - * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. - * @param args - Arguments to pass to the function. - * @returns An `HttpResponsePromise` instance. - */ - public static fromFunction Promise>, T>( - fn: F, - ...args: Parameters - ): HttpResponsePromise { - return new HttpResponsePromise(fn(...args)); - } - - /** - * Creates a function that returns an `HttpResponsePromise` from a function that returns a promise. - * - * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. - * @returns A function that returns an `HttpResponsePromise` instance. - */ - public static interceptFunction< - F extends (...args: never[]) => Promise>, - T = Awaited>["data"], - >(fn: F): (...args: Parameters) => HttpResponsePromise { - return (...args: Parameters): HttpResponsePromise => { - return HttpResponsePromise.fromPromise(fn(...args)); - }; - } - - /** - * Creates an `HttpResponsePromise` from an existing promise. - * - * @param promise - A promise resolving to a `WithRawResponse` object. - * @returns An `HttpResponsePromise` instance. - */ - public static fromPromise(promise: Promise>): HttpResponsePromise { - return new HttpResponsePromise(promise); - } - - /** - * Creates an `HttpResponsePromise` from an executor function. - * - * @param executor - A function that takes resolve and reject callbacks to create a promise. - * @returns An `HttpResponsePromise` instance. - */ - public static fromExecutor( - executor: (resolve: (value: WithRawResponse) => void, reject: (reason?: unknown) => void) => void, - ): HttpResponsePromise { - const promise = new Promise>(executor); - return new HttpResponsePromise(promise); - } - - /** - * Creates an `HttpResponsePromise` from a resolved result. - * - * @param result - A `WithRawResponse` object to resolve immediately. - * @returns An `HttpResponsePromise` instance. - */ - public static fromResult(result: WithRawResponse): HttpResponsePromise { - const promise = Promise.resolve(result); - return new HttpResponsePromise(promise); - } - - private unwrap(): Promise { - if (!this.unwrappedPromise) { - this.unwrappedPromise = this.innerPromise.then(({ data }) => data); - } - return this.unwrappedPromise; - } - - /** @inheritdoc */ - public override then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, - ): Promise { - return this.unwrap().then(onfulfilled, onrejected); - } - - /** @inheritdoc */ - public override catch( - onrejected?: ((reason: unknown) => TResult | PromiseLike) | null, - ): Promise { - return this.unwrap().catch(onrejected); - } - - /** @inheritdoc */ - public override finally(onfinally?: (() => void) | null): Promise { - return this.unwrap().finally(onfinally); - } - - /** - * Retrieves the data and raw response. - * - * @returns A promise resolving to a `WithRawResponse` object. - */ - public async withRawResponse(): Promise> { - return await this.innerPromise; - } -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts b/seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts deleted file mode 100644 index 37fb44e2aa99..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/RawResponse.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Headers } from "./Headers.js"; - -/** - * The raw response from the fetch call excluding the body. - */ -export type RawResponse = Omit< - { - [K in keyof Response as Response[K] extends Function ? never : K]: Response[K]; // strips out functions - }, - "ok" | "body" | "bodyUsed" ->; // strips out body and bodyUsed - -/** - * A raw response indicating that the request was aborted. - */ -export const abortRawResponse: RawResponse = { - headers: new Headers(), - redirected: false, - status: 499, - statusText: "Client Closed Request", - type: "error", - url: "", -} as const; - -/** - * A raw response indicating an unknown error. - */ -export const unknownRawResponse: RawResponse = { - headers: new Headers(), - redirected: false, - status: 0, - statusText: "Unknown Error", - type: "error", - url: "", -} as const; - -/** - * Converts a `RawResponse` object into a `RawResponse` by extracting its properties, - * excluding the `body` and `bodyUsed` fields. - * - * @param response - The `RawResponse` object to convert. - * @returns A `RawResponse` object containing the extracted properties of the input response. - */ -export function toRawResponse(response: Response): RawResponse { - return { - headers: response.headers, - redirected: response.redirected, - status: response.status, - statusText: response.statusText, - type: response.type, - url: response.url, - }; -} - -/** - * Creates a `RawResponse` from a standard `Response` object. - */ -export interface WithRawResponse { - readonly data: T; - readonly rawResponse: RawResponse; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/Supplier.ts b/seed/ts-sdk/allof/src/core/fetcher/Supplier.ts deleted file mode 100644 index 867c931c02f4..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/Supplier.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type Supplier = T | Promise | (() => T | Promise); - -export const Supplier = { - get: async (supplier: Supplier): Promise => { - if (typeof supplier === "function") { - return (supplier as () => T)(); - } else { - return supplier; - } - }, -}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts b/seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts deleted file mode 100644 index 88e13265e112..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/createRequestUrl.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { toQueryString } from "../url/qs.js"; - -export function createRequestUrl(baseUrl: string, queryParameters?: Record): string { - const queryString = toQueryString(queryParameters, { arrayFormat: "repeat" }); - return queryString ? `${baseUrl}?${queryString}` : baseUrl; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts b/seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts deleted file mode 100644 index 7cf4e623c2f5..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/getErrorResponseBody.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { fromJson } from "../json.js"; -import { getResponseBody } from "./getResponseBody.js"; - -export async function getErrorResponseBody(response: Response): Promise { - let contentType = response.headers.get("Content-Type")?.toLowerCase(); - if (contentType == null || contentType.length === 0) { - return getResponseBody(response); - } - - if (contentType.indexOf(";") !== -1) { - contentType = contentType.split(";")[0]?.trim() ?? ""; - } - switch (contentType) { - case "application/hal+json": - case "application/json": - case "application/ld+json": - case "application/problem+json": - case "application/vnd.api+json": - case "text/json": { - const text = await response.text(); - return text.length > 0 ? fromJson(text) : undefined; - } - default: - if (contentType.startsWith("application/vnd.") && contentType.endsWith("+json")) { - const text = await response.text(); - return text.length > 0 ? fromJson(text) : undefined; - } - - // Fallback to plain text if content type is not recognized - // Even if no body is present, the response will be an empty string - return await response.text(); - } -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts deleted file mode 100644 index 9f845b956392..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/getFetchFn.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function getFetchFn(): Promise { - return fetch; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getHeader.ts b/seed/ts-sdk/allof/src/core/fetcher/getHeader.ts deleted file mode 100644 index 50f922b0e87f..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/getHeader.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function getHeader(headers: Record, header: string): string | undefined { - for (const [headerKey, headerValue] of Object.entries(headers)) { - if (headerKey.toLowerCase() === header.toLowerCase()) { - return headerValue; - } - } - return undefined; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts b/seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts deleted file mode 100644 index 91d9d81f50e5..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/getRequestBody.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { toJson } from "../json.js"; -import { toQueryString } from "../url/qs.js"; - -export declare namespace GetRequestBody { - interface Args { - body: unknown; - type: "json" | "file" | "bytes" | "form" | "other"; - } -} - -export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { - if (type === "form") { - return toQueryString(body, { arrayFormat: "repeat", encode: true }); - } - if (type.includes("json")) { - return toJson(body); - } else { - return body as BodyInit; - } -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts deleted file mode 100644 index 708d55728f2b..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/getResponseBody.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { fromJson } from "../json.js"; -import { getBinaryResponse } from "./BinaryResponse.js"; - -export async function getResponseBody(response: Response, responseType?: string): Promise { - switch (responseType) { - case "binary-response": - return getBinaryResponse(response); - case "blob": - return await response.blob(); - case "arrayBuffer": - return await response.arrayBuffer(); - case "sse": - if (response.body == null) { - return { - ok: false, - error: { - reason: "body-is-null", - statusCode: response.status, - }, - }; - } - return response.body; - case "streaming": - if (response.body == null) { - return { - ok: false, - error: { - reason: "body-is-null", - statusCode: response.status, - }, - }; - } - - return response.body; - - case "text": - return await response.text(); - } - - // if responseType is "json" or not specified, try to parse as JSON - const text = await response.text(); - if (text.length > 0) { - try { - const responseBody = fromJson(text); - return responseBody; - } catch (_err) { - return { - ok: false, - error: { - reason: "non-json", - statusCode: response.status, - rawBody: text, - }, - }; - } - } - return undefined; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/index.ts b/seed/ts-sdk/allof/src/core/fetcher/index.ts deleted file mode 100644 index bd5db362c778..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type { APIResponse } from "./APIResponse.js"; -export type { BinaryResponse } from "./BinaryResponse.js"; -export type { EndpointMetadata } from "./EndpointMetadata.js"; -export { EndpointSupplier } from "./EndpointSupplier.js"; -export type { Fetcher, FetchFunction } from "./Fetcher.js"; -export { fetcher } from "./Fetcher.js"; -export { getHeader } from "./getHeader.js"; -export { HttpResponsePromise } from "./HttpResponsePromise.js"; -export type { PassthroughRequest } from "./makePassthroughRequest.js"; -export { makePassthroughRequest } from "./makePassthroughRequest.js"; -export type { RawResponse, WithRawResponse } from "./RawResponse.js"; -export { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; -export { Supplier } from "./Supplier.js"; diff --git a/seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts b/seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts deleted file mode 100644 index f5ba761400f8..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/makePassthroughRequest.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; -import { join } from "../url/join.js"; -import { EndpointSupplier } from "./EndpointSupplier.js"; -import { getFetchFn } from "./getFetchFn.js"; -import { makeRequest } from "./makeRequest.js"; -import { requestWithRetries } from "./requestWithRetries.js"; -import { Supplier } from "./Supplier.js"; - -export declare namespace PassthroughRequest { - /** - * Per-request options that can override the SDK client defaults. - */ - export interface RequestOptions { - /** Override the default timeout for this request (in seconds). */ - timeoutInSeconds?: number; - /** Override the default number of retries for this request. */ - maxRetries?: number; - /** Additional headers to include in this request. */ - headers?: Record; - /** Abort signal for this request. */ - abortSignal?: AbortSignal; - } - - /** - * SDK client configuration used by the passthrough fetch method. - */ - export interface ClientOptions { - /** The base URL or environment for the client. */ - environment?: Supplier; - /** Override the base URL. */ - baseUrl?: Supplier; - /** Default headers to include in requests. */ - headers?: Record; - /** Default maximum time to wait for a response in seconds. */ - timeoutInSeconds?: number; - /** Default number of times to retry the request. Defaults to 2. */ - maxRetries?: number; - /** A custom fetch function. */ - fetch?: typeof fetch; - /** Logging configuration. */ - logging?: LogConfig | Logger; - /** A function that returns auth headers. */ - getAuthHeaders?: () => Promise>; - } -} - -/** - * Makes a passthrough HTTP request using the SDK's configuration (auth, retry, logging, etc.) - * while mimicking the standard `fetch` API. - * - * @param input - The URL, path, or Request object. If a relative path, it will be resolved against the configured base URL. - * @param init - Standard RequestInit options (method, headers, body, signal, etc.) - * @param clientOptions - SDK client options (auth, default headers, logging, etc.) - * @param requestOptions - Per-request overrides (timeout, retries, extra headers, abort signal). - * @returns A standard Response object. - */ -export async function makePassthroughRequest( - input: Request | string | URL, - init: RequestInit | undefined, - clientOptions: PassthroughRequest.ClientOptions, - requestOptions?: PassthroughRequest.RequestOptions, -): Promise { - const logger = createLogger(clientOptions.logging); - - // Extract URL and default init properties from Request object if provided - let url: string; - let effectiveInit: RequestInit | undefined = init; - if (input instanceof Request) { - url = input.url; - // If no explicit init provided, extract properties from the Request object - if (init == null) { - effectiveInit = { - method: input.method, - headers: Object.fromEntries(input.headers.entries()), - body: input.body, - signal: input.signal, - credentials: input.credentials, - cache: input.cache as RequestCache, - redirect: input.redirect, - referrer: input.referrer, - integrity: input.integrity, - mode: input.mode, - }; - } - } else { - url = input instanceof URL ? input.toString() : input; - } - - // Resolve the base URL - const baseUrl = - (clientOptions.baseUrl != null ? await Supplier.get(clientOptions.baseUrl) : undefined) ?? - (clientOptions.environment != null ? await Supplier.get(clientOptions.environment) : undefined); - - // Determine the full URL - let fullUrl: string; - if (url.startsWith("http://") || url.startsWith("https://")) { - fullUrl = url; - } else if (baseUrl != null) { - fullUrl = join(baseUrl, url); - } else { - fullUrl = url; - } - - // Merge headers: SDK default headers -> auth headers -> user-provided headers - const mergedHeaders: Record = {}; - - // Apply SDK default headers (resolve suppliers) - if (clientOptions.headers != null) { - for (const [key, value] of Object.entries(clientOptions.headers)) { - const resolved = await EndpointSupplier.get(value, { endpointMetadata: {} }); - if (resolved != null) { - mergedHeaders[key.toLowerCase()] = `${resolved}`; - } - } - } - - // Apply auth headers - if (clientOptions.getAuthHeaders != null) { - const authHeaders = await clientOptions.getAuthHeaders(); - for (const [key, value] of Object.entries(authHeaders)) { - mergedHeaders[key.toLowerCase()] = value; - } - } - - // Apply user-provided headers from init - if (effectiveInit?.headers != null) { - const initHeaders = - effectiveInit.headers instanceof Headers - ? Object.fromEntries(effectiveInit.headers.entries()) - : Array.isArray(effectiveInit.headers) - ? Object.fromEntries(effectiveInit.headers) - : effectiveInit.headers; - for (const [key, value] of Object.entries(initHeaders)) { - if (value != null) { - mergedHeaders[key.toLowerCase()] = value; - } - } - } - - // Apply per-request option headers (highest priority) - if (requestOptions?.headers != null) { - for (const [key, value] of Object.entries(requestOptions.headers)) { - mergedHeaders[key.toLowerCase()] = value; - } - } - - const method = effectiveInit?.method ?? "GET"; - const body = effectiveInit?.body; - const timeoutInSeconds = requestOptions?.timeoutInSeconds ?? clientOptions.timeoutInSeconds; - const timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : undefined; - const maxRetries = requestOptions?.maxRetries ?? clientOptions.maxRetries; - const abortSignal = requestOptions?.abortSignal ?? effectiveInit?.signal ?? undefined; - const fetchFn = clientOptions.fetch ?? (await getFetchFn()); - - if (logger.isDebug()) { - logger.debug("Making passthrough HTTP request", { - method, - url: fullUrl, - hasBody: body != null, - }); - } - - const response = await requestWithRetries( - async () => - makeRequest( - fetchFn, - fullUrl, - method, - mergedHeaders, - body ?? undefined, - timeoutMs, - abortSignal, - effectiveInit?.credentials === "include", - undefined, // duplex - false, // disableCache - ), - maxRetries, - ); - - if (logger.isDebug()) { - logger.debug("Passthrough HTTP request completed", { - method, - url: fullUrl, - statusCode: response.status, - }); - } - - return response; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts b/seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts deleted file mode 100644 index 360a86df40ad..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/makeRequest.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { anySignal, getTimeoutSignal } from "./signals.js"; - -/** - * Cached result of checking whether the current runtime supports - * the `cache` option in `Request`. Some runtimes (e.g. Cloudflare Workers) - * throw a TypeError when this option is used. - */ -let _cacheNoStoreSupported: boolean | undefined; -export function isCacheNoStoreSupported(): boolean { - if (_cacheNoStoreSupported != null) { - return _cacheNoStoreSupported; - } - try { - new Request("http://localhost", { cache: "no-store" }); - _cacheNoStoreSupported = true; - } catch { - _cacheNoStoreSupported = false; - } - return _cacheNoStoreSupported; -} - -/** - * Reset the cached result of `isCacheNoStoreSupported`. Exposed for testing only. - */ -export function resetCacheNoStoreSupported(): void { - _cacheNoStoreSupported = undefined; -} - -export const makeRequest = async ( - fetchFn: (url: string, init: RequestInit) => Promise, - url: string, - method: string, - headers: Headers | Record, - requestBody: BodyInit | undefined, - timeoutMs?: number, - abortSignal?: AbortSignal, - withCredentials?: boolean, - duplex?: "half", - disableCache?: boolean, -): Promise => { - const signals: AbortSignal[] = []; - - let timeoutAbortId: ReturnType | undefined; - if (timeoutMs != null) { - const { signal, abortId } = getTimeoutSignal(timeoutMs); - timeoutAbortId = abortId; - signals.push(signal); - } - - if (abortSignal != null) { - signals.push(abortSignal); - } - const newSignals = anySignal(signals); - const response = await fetchFn(url, { - method: method, - headers, - body: requestBody, - signal: newSignals, - credentials: withCredentials ? "include" : undefined, - // @ts-ignore - duplex, - ...(disableCache && isCacheNoStoreSupported() ? { cache: "no-store" as RequestCache } : {}), - }); - - if (timeoutAbortId != null) { - clearTimeout(timeoutAbortId); - } - - return response; -}; diff --git a/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts deleted file mode 100644 index 1f689688c4b2..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts +++ /dev/null @@ -1,64 +0,0 @@ -const INITIAL_RETRY_DELAY = 1000; // in milliseconds -const MAX_RETRY_DELAY = 60000; // in milliseconds -const DEFAULT_MAX_RETRIES = 2; -const JITTER_FACTOR = 0.2; // 20% random jitter - -function addPositiveJitter(delay: number): number { - const jitterMultiplier = 1 + Math.random() * JITTER_FACTOR; - return delay * jitterMultiplier; -} - -function addSymmetricJitter(delay: number): number { - const jitterMultiplier = 1 + (Math.random() - 0.5) * JITTER_FACTOR; - return delay * jitterMultiplier; -} - -function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { - const retryAfter = response.headers.get("Retry-After"); - if (retryAfter) { - const retryAfterSeconds = parseInt(retryAfter, 10); - if (!Number.isNaN(retryAfterSeconds) && retryAfterSeconds > 0) { - return Math.min(retryAfterSeconds * 1000, MAX_RETRY_DELAY); - } - - const retryAfterDate = new Date(retryAfter); - if (!Number.isNaN(retryAfterDate.getTime())) { - const delay = retryAfterDate.getTime() - Date.now(); - if (delay > 0) { - return Math.min(Math.max(delay, 0), MAX_RETRY_DELAY); - } - } - } - - const rateLimitReset = response.headers.get("X-RateLimit-Reset"); - if (rateLimitReset) { - const resetTime = parseInt(rateLimitReset, 10); - if (!Number.isNaN(resetTime)) { - const delay = resetTime * 1000 - Date.now(); - if (delay > 0) { - return addPositiveJitter(Math.min(delay, MAX_RETRY_DELAY)); - } - } - } - - return addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** retryAttempt, MAX_RETRY_DELAY)); -} - -export async function requestWithRetries( - requestFn: () => Promise, - maxRetries: number = DEFAULT_MAX_RETRIES, -): Promise { - let response: Response = await requestFn(); - - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { - const delay = getRetryDelayFromHeaders(response, i); - - await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; - } - } - return response!; -} diff --git a/seed/ts-sdk/allof/src/core/fetcher/signals.ts b/seed/ts-sdk/allof/src/core/fetcher/signals.ts deleted file mode 100644 index 7bd3757ec3a7..000000000000 --- a/seed/ts-sdk/allof/src/core/fetcher/signals.ts +++ /dev/null @@ -1,26 +0,0 @@ -const TIMEOUT = "timeout"; - -export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: ReturnType } { - const controller = new AbortController(); - const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); - return { signal: controller.signal, abortId }; -} - -export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { - const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args) as AbortSignal[]; - - const controller = new AbortController(); - - for (const signal of signals) { - if (signal.aborted) { - controller.abort((signal as any)?.reason); - break; - } - - signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { - signal: controller.signal, - }); - } - - return controller.signal; -} diff --git a/seed/ts-sdk/allof/src/core/headers.ts b/seed/ts-sdk/allof/src/core/headers.ts deleted file mode 100644 index be45c4552a35..000000000000 --- a/seed/ts-sdk/allof/src/core/headers.ts +++ /dev/null @@ -1,33 +0,0 @@ -export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { - const result: Record = {}; - - for (const [key, value] of headersArray - .filter((headers) => headers != null) - .flatMap((headers) => Object.entries(headers))) { - const insensitiveKey = key.toLowerCase(); - if (value != null) { - result[insensitiveKey] = value; - } else if (insensitiveKey in result) { - delete result[insensitiveKey]; - } - } - - return result; -} - -export function mergeOnlyDefinedHeaders( - ...headersArray: (Record | null | undefined)[] -): Record { - const result: Record = {}; - - for (const [key, value] of headersArray - .filter((headers) => headers != null) - .flatMap((headers) => Object.entries(headers))) { - const insensitiveKey = key.toLowerCase(); - if (value != null) { - result[insensitiveKey] = value; - } - } - - return result; -} diff --git a/seed/ts-sdk/allof/src/core/index.ts b/seed/ts-sdk/allof/src/core/index.ts deleted file mode 100644 index afa8351fcf85..000000000000 --- a/seed/ts-sdk/allof/src/core/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./fetcher/index.js"; -export * as logging from "./logging/index.js"; -export * from "./runtime/index.js"; -export * as url from "./url/index.js"; diff --git a/seed/ts-sdk/allof/src/core/json.ts b/seed/ts-sdk/allof/src/core/json.ts deleted file mode 100644 index c052f3249f4f..000000000000 --- a/seed/ts-sdk/allof/src/core/json.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Serialize a value to JSON - * @param value A JavaScript value, usually an object or array, to be converted. - * @param replacer A function that transforms the results. - * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. - * @returns JSON string - */ -export const toJson = ( - value: unknown, - replacer?: (this: unknown, key: string, value: unknown) => unknown, - space?: string | number, -): string => { - return JSON.stringify(value, replacer, space); -}; - -/** - * Parse JSON string to object, array, or other type - * @param text A valid JSON string. - * @param reviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is. - * @returns Parsed object, array, or other type - */ -export function fromJson( - text: string, - reviver?: (this: unknown, key: string, value: unknown) => unknown, -): T { - return JSON.parse(text, reviver); -} diff --git a/seed/ts-sdk/allof/src/core/logging/exports.ts b/seed/ts-sdk/allof/src/core/logging/exports.ts deleted file mode 100644 index 88f6c00db0cf..000000000000 --- a/seed/ts-sdk/allof/src/core/logging/exports.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as logger from "./logger.js"; - -export namespace logging { - /** - * Configuration for logger instances. - */ - export type LogConfig = logger.LogConfig; - export type LogLevel = logger.LogLevel; - export const LogLevel: typeof logger.LogLevel = logger.LogLevel; - export type ILogger = logger.ILogger; - /** - * Console logger implementation that outputs to the console. - */ - export type ConsoleLogger = logger.ConsoleLogger; - /** - * Console logger implementation that outputs to the console. - */ - export const ConsoleLogger: typeof logger.ConsoleLogger = logger.ConsoleLogger; -} diff --git a/seed/ts-sdk/allof/src/core/logging/index.ts b/seed/ts-sdk/allof/src/core/logging/index.ts deleted file mode 100644 index d81cc32c40f9..000000000000 --- a/seed/ts-sdk/allof/src/core/logging/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./logger.js"; diff --git a/seed/ts-sdk/allof/src/core/logging/logger.ts b/seed/ts-sdk/allof/src/core/logging/logger.ts deleted file mode 100644 index a3f3673cda93..000000000000 --- a/seed/ts-sdk/allof/src/core/logging/logger.ts +++ /dev/null @@ -1,203 +0,0 @@ -export const LogLevel = { - Debug: "debug", - Info: "info", - Warn: "warn", - Error: "error", -} as const; -export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; -const logLevelMap: Record = { - [LogLevel.Debug]: 1, - [LogLevel.Info]: 2, - [LogLevel.Warn]: 3, - [LogLevel.Error]: 4, -}; - -export interface ILogger { - /** - * Logs a debug message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - debug(message: string, ...args: unknown[]): void; - /** - * Logs an info message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - info(message: string, ...args: unknown[]): void; - /** - * Logs a warning message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - warn(message: string, ...args: unknown[]): void; - /** - * Logs an error message. - * @param message - The message to log - * @param args - Additional arguments to log - */ - error(message: string, ...args: unknown[]): void; -} - -/** - * Configuration for logger initialization. - */ -export interface LogConfig { - /** - * Minimum log level to output. - * @default LogLevel.Info - */ - level?: LogLevel; - /** - * Logger implementation to use. - * @default new ConsoleLogger() - */ - logger?: ILogger; - /** - * Whether logging should be silenced. - * @default true - */ - silent?: boolean; -} - -/** - * Default console-based logger implementation. - */ -export class ConsoleLogger implements ILogger { - debug(message: string, ...args: unknown[]): void { - console.debug(message, ...args); - } - info(message: string, ...args: unknown[]): void { - console.info(message, ...args); - } - warn(message: string, ...args: unknown[]): void { - console.warn(message, ...args); - } - error(message: string, ...args: unknown[]): void { - console.error(message, ...args); - } -} - -/** - * Logger class that provides level-based logging functionality. - */ -export class Logger { - private readonly level: number; - private readonly logger: ILogger; - private readonly silent: boolean; - - /** - * Creates a new logger instance. - * @param config - Logger configuration - */ - constructor(config: Required) { - this.level = logLevelMap[config.level]; - this.logger = config.logger; - this.silent = config.silent; - } - - /** - * Checks if a log level should be output based on configuration. - * @param level - The log level to check - * @returns True if the level should be logged - */ - public shouldLog(level: LogLevel): boolean { - return !this.silent && this.level <= logLevelMap[level]; - } - - /** - * Checks if debug logging is enabled. - * @returns True if debug logs should be output - */ - public isDebug(): boolean { - return this.shouldLog(LogLevel.Debug); - } - - /** - * Logs a debug message if debug logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public debug(message: string, ...args: unknown[]): void { - if (this.isDebug()) { - this.logger.debug(message, ...args); - } - } - - /** - * Checks if info logging is enabled. - * @returns True if info logs should be output - */ - public isInfo(): boolean { - return this.shouldLog(LogLevel.Info); - } - - /** - * Logs an info message if info logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public info(message: string, ...args: unknown[]): void { - if (this.isInfo()) { - this.logger.info(message, ...args); - } - } - - /** - * Checks if warning logging is enabled. - * @returns True if warning logs should be output - */ - public isWarn(): boolean { - return this.shouldLog(LogLevel.Warn); - } - - /** - * Logs a warning message if warning logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public warn(message: string, ...args: unknown[]): void { - if (this.isWarn()) { - this.logger.warn(message, ...args); - } - } - - /** - * Checks if error logging is enabled. - * @returns True if error logs should be output - */ - public isError(): boolean { - return this.shouldLog(LogLevel.Error); - } - - /** - * Logs an error message if error logging is enabled. - * @param message - The message to log - * @param args - Additional arguments to log - */ - public error(message: string, ...args: unknown[]): void { - if (this.isError()) { - this.logger.error(message, ...args); - } - } -} - -export function createLogger(config?: LogConfig | Logger): Logger { - if (config == null) { - return defaultLogger; - } - if (config instanceof Logger) { - return config; - } - config = config ?? {}; - config.level ??= LogLevel.Info; - config.logger ??= new ConsoleLogger(); - config.silent ??= true; - return new Logger(config as Required); -} - -const defaultLogger: Logger = new Logger({ - level: LogLevel.Info, - logger: new ConsoleLogger(), - silent: true, -}); diff --git a/seed/ts-sdk/allof/src/core/runtime/index.ts b/seed/ts-sdk/allof/src/core/runtime/index.ts deleted file mode 100644 index cfab23f9a834..000000000000 --- a/seed/ts-sdk/allof/src/core/runtime/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RUNTIME } from "./runtime.js"; diff --git a/seed/ts-sdk/allof/src/core/runtime/runtime.ts b/seed/ts-sdk/allof/src/core/runtime/runtime.ts deleted file mode 100644 index e6e66b2a7bce..000000000000 --- a/seed/ts-sdk/allof/src/core/runtime/runtime.ts +++ /dev/null @@ -1,134 +0,0 @@ -interface DenoGlobal { - version: { - deno: string; - }; -} - -interface BunGlobal { - version: string; -} - -declare const Deno: DenoGlobal | undefined; -declare const Bun: BunGlobal | undefined; -declare const EdgeRuntime: string | undefined; -declare const self: typeof globalThis.self & { - importScripts?: unknown; -}; - -/** - * A constant that indicates which environment and version the SDK is running in. - */ -export const RUNTIME: Runtime = evaluateRuntime(); - -export interface Runtime { - type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd" | "edge-runtime"; - version?: string; - parsedVersion?: number; -} - -function evaluateRuntime(): Runtime { - /** - * A constant that indicates whether the environment the code is running is a Web Browser. - */ - const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; - if (isBrowser) { - return { - type: "browser", - version: window.navigator.userAgent, - }; - } - - /** - * A constant that indicates whether the environment the code is running is Cloudflare. - * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent - */ - const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; - if (isCloudflare) { - return { - type: "workerd", - }; - } - - /** - * A constant that indicates whether the environment the code is running is Edge Runtime. - * https://vercel.com/docs/functions/runtimes/edge-runtime#check-if-you're-running-on-the-edge-runtime - */ - const isEdgeRuntime = typeof EdgeRuntime === "string"; - if (isEdgeRuntime) { - return { - type: "edge-runtime", - }; - } - - /** - * A constant that indicates whether the environment the code is running is a Web Worker. - */ - const isWebWorker = - typeof self === "object" && - typeof self?.importScripts === "function" && - (self.constructor?.name === "DedicatedWorkerGlobalScope" || - self.constructor?.name === "ServiceWorkerGlobalScope" || - self.constructor?.name === "SharedWorkerGlobalScope"); - if (isWebWorker) { - return { - type: "web-worker", - }; - } - - /** - * A constant that indicates whether the environment the code is running is Deno. - * FYI Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions - */ - const isDeno = - typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; - if (isDeno) { - return { - type: "deno", - version: Deno.version.deno, - }; - } - - /** - * A constant that indicates whether the environment the code is running is Bun.sh. - */ - const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; - if (isBun) { - return { - type: "bun", - version: Bun.version, - }; - } - - /** - * A constant that indicates whether the environment the code is running is in React-Native. - * This check should come before Node.js detection since React Native may have a process polyfill. - * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js - */ - const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; - if (isReactNative) { - return { - type: "react-native", - }; - } - - /** - * A constant that indicates whether the environment the code is running is Node.JS. - * - * We assign `process` to a local variable first to avoid being flagged by - * bundlers that perform static analysis on `process.versions` (e.g. Next.js - * Edge Runtime warns about Node.js APIs even when they are guarded). - */ - const _process = typeof process !== "undefined" ? process : undefined; - const isNode = typeof _process !== "undefined" && typeof _process.versions?.node === "string"; - if (isNode) { - return { - type: "node", - version: _process.versions.node, - parsedVersion: Number(_process.versions.node.split(".")[0]), - }; - } - - return { - type: "unknown", - }; -} diff --git a/seed/ts-sdk/allof/src/core/url/encodePathParam.ts b/seed/ts-sdk/allof/src/core/url/encodePathParam.ts deleted file mode 100644 index 19b901244218..000000000000 --- a/seed/ts-sdk/allof/src/core/url/encodePathParam.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function encodePathParam(param: unknown): string { - if (param === null) { - return "null"; - } - const typeofParam = typeof param; - switch (typeofParam) { - case "undefined": - return "undefined"; - case "string": - case "number": - case "boolean": - break; - default: - param = String(param); - break; - } - return encodeURIComponent(param as string | number | boolean); -} diff --git a/seed/ts-sdk/allof/src/core/url/index.ts b/seed/ts-sdk/allof/src/core/url/index.ts deleted file mode 100644 index f2e0fa2d2221..000000000000 --- a/seed/ts-sdk/allof/src/core/url/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { encodePathParam } from "./encodePathParam.js"; -export { join } from "./join.js"; -export { toQueryString } from "./qs.js"; diff --git a/seed/ts-sdk/allof/src/core/url/join.ts b/seed/ts-sdk/allof/src/core/url/join.ts deleted file mode 100644 index 7ca7daef094d..000000000000 --- a/seed/ts-sdk/allof/src/core/url/join.ts +++ /dev/null @@ -1,79 +0,0 @@ -export function join(base: string, ...segments: string[]): string { - if (!base) { - return ""; - } - - if (segments.length === 0) { - return base; - } - - if (base.includes("://")) { - let url: URL; - try { - url = new URL(base); - } catch { - return joinPath(base, ...segments); - } - - const lastSegment = segments[segments.length - 1]; - const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); - - for (const segment of segments) { - const cleanSegment = trimSlashes(segment); - if (cleanSegment) { - url.pathname = joinPathSegments(url.pathname, cleanSegment); - } - } - - if (shouldPreserveTrailingSlash && !url.pathname.endsWith("/")) { - url.pathname += "/"; - } - - return url.toString(); - } - - return joinPath(base, ...segments); -} - -function joinPath(base: string, ...segments: string[]): string { - if (segments.length === 0) { - return base; - } - - let result = base; - - const lastSegment = segments[segments.length - 1]; - const shouldPreserveTrailingSlash = lastSegment?.endsWith("/"); - - for (const segment of segments) { - const cleanSegment = trimSlashes(segment); - if (cleanSegment) { - result = joinPathSegments(result, cleanSegment); - } - } - - if (shouldPreserveTrailingSlash && !result.endsWith("/")) { - result += "/"; - } - - return result; -} - -function joinPathSegments(left: string, right: string): string { - if (left.endsWith("/")) { - return left + right; - } - return `${left}/${right}`; -} - -function trimSlashes(str: string): string { - if (!str) return str; - - let start = 0; - let end = str.length; - - if (str.startsWith("/")) start = 1; - if (str.endsWith("/")) end = str.length - 1; - - return start === 0 && end === str.length ? str : str.slice(start, end); -} diff --git a/seed/ts-sdk/allof/src/core/url/qs.ts b/seed/ts-sdk/allof/src/core/url/qs.ts deleted file mode 100644 index 13e89be9d9a6..000000000000 --- a/seed/ts-sdk/allof/src/core/url/qs.ts +++ /dev/null @@ -1,74 +0,0 @@ -interface QueryStringOptions { - arrayFormat?: "indices" | "repeat"; - encode?: boolean; -} - -const defaultQsOptions: Required = { - arrayFormat: "indices", - encode: true, -} as const; - -function encodeValue(value: unknown, shouldEncode: boolean): string { - if (value === undefined) { - return ""; - } - if (value === null) { - return ""; - } - const stringValue = String(value); - return shouldEncode ? encodeURIComponent(stringValue) : stringValue; -} - -function stringifyObject(obj: Record, prefix = "", options: Required): string[] { - const parts: string[] = []; - - for (const [key, value] of Object.entries(obj)) { - const fullKey = prefix ? `${prefix}[${key}]` : key; - - if (value === undefined) { - continue; - } - - if (Array.isArray(value)) { - if (value.length === 0) { - continue; - } - for (let i = 0; i < value.length; i++) { - const item = value[i]; - if (item === undefined) { - continue; - } - if (typeof item === "object" && !Array.isArray(item) && item !== null) { - const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; - parts.push(...stringifyObject(item as Record, arrayKey, options)); - } else { - const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; - const encodedKey = options.encode ? encodeURIComponent(arrayKey) : arrayKey; - parts.push(`${encodedKey}=${encodeValue(item, options.encode)}`); - } - } - } else if (typeof value === "object" && value !== null) { - if (Object.keys(value as Record).length === 0) { - continue; - } - parts.push(...stringifyObject(value as Record, fullKey, options)); - } else { - const encodedKey = options.encode ? encodeURIComponent(fullKey) : fullKey; - parts.push(`${encodedKey}=${encodeValue(value, options.encode)}`); - } - } - - return parts; -} - -export function toQueryString(obj: unknown, options?: QueryStringOptions): string { - if (obj == null || typeof obj !== "object") { - return ""; - } - - const parts = stringifyObject(obj as Record, "", { - ...defaultQsOptions, - ...options, - }); - return parts.join("&"); -} diff --git a/seed/ts-sdk/allof/src/environments.ts b/seed/ts-sdk/allof/src/environments.ts deleted file mode 100644 index 92d0fc94dd2c..000000000000 --- a/seed/ts-sdk/allof/src/environments.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export const SeedApiEnvironment = { - Default: "https://api.example.com", -} as const; - -export type SeedApiEnvironment = typeof SeedApiEnvironment.Default; diff --git a/seed/ts-sdk/allof/src/errors/SeedApiError.ts b/seed/ts-sdk/allof/src/errors/SeedApiError.ts deleted file mode 100644 index ec2bc570e2a7..000000000000 --- a/seed/ts-sdk/allof/src/errors/SeedApiError.ts +++ /dev/null @@ -1,64 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as core from "../core/index.js"; -import { toJson } from "../core/json.js"; - -export class SeedApiError extends Error { - public readonly statusCode?: number; - public readonly body?: unknown; - public readonly rawResponse?: core.RawResponse; - public readonly cause?: unknown; - - constructor({ - message, - statusCode, - body, - rawResponse, - cause, - }: { - message?: string; - statusCode?: number; - body?: unknown; - rawResponse?: core.RawResponse; - cause?: unknown; - }) { - super(buildMessage({ message, statusCode, body })); - Object.setPrototypeOf(this, new.target.prototype); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - - this.name = this.constructor.name; - this.statusCode = statusCode; - this.body = body; - this.rawResponse = rawResponse; - if (cause != null) { - this.cause = cause; - } - } -} - -function buildMessage({ - message, - statusCode, - body, -}: { - message: string | undefined; - statusCode: number | undefined; - body: unknown | undefined; -}): string { - const lines: string[] = []; - if (message != null) { - lines.push(message); - } - - if (statusCode != null) { - lines.push(`Status code: ${statusCode.toString()}`); - } - - if (body != null) { - lines.push(`Body: ${toJson(body, undefined, 2)}`); - } - - return lines.join("\n"); -} diff --git a/seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts b/seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts deleted file mode 100644 index f8f6a5f95430..000000000000 --- a/seed/ts-sdk/allof/src/errors/SeedApiTimeoutError.ts +++ /dev/null @@ -1,18 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export class SeedApiTimeoutError extends Error { - public readonly cause?: unknown; - - constructor(message: string, opts?: { cause?: unknown }) { - super(message); - Object.setPrototypeOf(this, new.target.prototype); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - - this.name = this.constructor.name; - if (opts?.cause != null) { - this.cause = opts.cause; - } - } -} diff --git a/seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts deleted file mode 100644 index 27d1ebec132d..000000000000 --- a/seed/ts-sdk/allof/src/errors/handleNonStatusCodeError.ts +++ /dev/null @@ -1,40 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as core from "../core/index.js"; -import * as errors from "./index.js"; - -export function handleNonStatusCodeError( - error: core.Fetcher.Error, - rawResponse: core.RawResponse, - method: string, - path: string, -): never { - switch (error.reason) { - case "non-json": - throw new errors.SeedApiError({ - statusCode: error.statusCode, - body: error.rawBody, - rawResponse: rawResponse, - }); - case "body-is-null": - throw new errors.SeedApiError({ - statusCode: error.statusCode, - rawResponse: rawResponse, - }); - case "timeout": - throw new errors.SeedApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`, { - cause: error.cause, - }); - case "unknown": - throw new errors.SeedApiError({ - message: error.errorMessage, - rawResponse: rawResponse, - cause: error.cause, - }); - default: - throw new errors.SeedApiError({ - message: "Unknown error", - rawResponse: rawResponse, - }); - } -} diff --git a/seed/ts-sdk/allof/src/errors/index.ts b/seed/ts-sdk/allof/src/errors/index.ts deleted file mode 100644 index 09e82b954c26..000000000000 --- a/seed/ts-sdk/allof/src/errors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { SeedApiError } from "./SeedApiError.js"; -export { SeedApiTimeoutError } from "./SeedApiTimeoutError.js"; diff --git a/seed/ts-sdk/allof/src/exports.ts b/seed/ts-sdk/allof/src/exports.ts deleted file mode 100644 index 7b70ee14fc02..000000000000 --- a/seed/ts-sdk/allof/src/exports.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./core/exports.js"; diff --git a/seed/ts-sdk/allof/src/index.ts b/seed/ts-sdk/allof/src/index.ts deleted file mode 100644 index a11386c163bd..000000000000 --- a/seed/ts-sdk/allof/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * as SeedApi from "./api/index.js"; -export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; -export { SeedApiClient } from "./Client.js"; -export { SeedApiEnvironment } from "./environments.js"; -export { SeedApiError, SeedApiTimeoutError } from "./errors/index.js"; -export * from "./exports.js"; diff --git a/seed/ts-sdk/allof/src/version.ts b/seed/ts-sdk/allof/src/version.ts deleted file mode 100644 index b643a3e3ea27..000000000000 --- a/seed/ts-sdk/allof/src/version.ts +++ /dev/null @@ -1 +0,0 @@ -export const SDK_VERSION = "0.0.1"; diff --git a/seed/ts-sdk/allof/tests/custom.test.ts b/seed/ts-sdk/allof/tests/custom.test.ts deleted file mode 100644 index 7f5e031c8396..000000000000 --- a/seed/ts-sdk/allof/tests/custom.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This is a custom test file, if you wish to add more tests - * to your SDK. - * Be sure to mark this file in `.fernignore`. - * - * If you include example requests/responses in your fern definition, - * you will have tests automatically generated for you. - */ -describe("test", () => { - it("default", () => { - expect(true).toBe(true); - }); -}); diff --git a/seed/ts-sdk/allof/tests/mock-server/MockServer.ts b/seed/ts-sdk/allof/tests/mock-server/MockServer.ts deleted file mode 100644 index 954872157d52..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/MockServer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { RequestHandlerOptions } from "msw"; -import type { SetupServer } from "msw/node"; - -import { mockEndpointBuilder } from "./mockEndpointBuilder"; - -export interface MockServerOptions { - baseUrl: string; - server: SetupServer; -} - -export class MockServer { - private readonly server: SetupServer; - public readonly baseUrl: string; - - constructor({ baseUrl, server }: MockServerOptions) { - this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl; - this.server = server; - } - - public mockEndpoint(options?: RequestHandlerOptions): ReturnType { - const builder = mockEndpointBuilder({ - once: options?.once ?? true, - onBuild: (handler) => { - this.server.use(handler); - }, - }).baseUrl(this.baseUrl); - return builder; - } -} diff --git a/seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts b/seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts deleted file mode 100644 index d7d891a2d80b..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/MockServerPool.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { setupServer } from "msw/node"; - -import { fromJson, toJson } from "../../src/core/json"; -import { MockServer } from "./MockServer"; -import { randomBaseUrl } from "./randomBaseUrl"; - -const mswServer = setupServer(); -interface MockServerOptions { - baseUrl?: string; -} - -async function formatHttpRequest(request: Request, id?: string): Promise { - try { - const clone = request.clone(); - const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); - - let body = ""; - try { - const contentType = clone.headers.get("content-type"); - if (contentType?.includes("application/json")) { - body = toJson(fromJson(await clone.text()), undefined, 2); - } else if (clone.body) { - body = await clone.text(); - } - } catch (_e) { - body = "(unable to parse body)"; - } - - const title = id ? `### Request ${id} ###\n` : ""; - const firstLine = `${title}${request.method} ${request.url.toString()} HTTP/1.1`; - - return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; - } catch (e) { - return `Error formatting request: ${e}`; - } -} - -async function formatHttpResponse(response: Response, id?: string): Promise { - try { - const clone = response.clone(); - const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); - - let body = ""; - try { - const contentType = clone.headers.get("content-type"); - if (contentType?.includes("application/json")) { - body = toJson(fromJson(await clone.text()), undefined, 2); - } else if (clone.body) { - body = await clone.text(); - } - } catch (_e) { - body = "(unable to parse body)"; - } - - const title = id ? `### Response for ${id} ###\n` : ""; - const firstLine = `${title}HTTP/1.1 ${response.status} ${response.statusText}`; - - return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; - } catch (e) { - return `Error formatting response: ${e}`; - } -} - -class MockServerPool { - private servers: MockServer[] = []; - - public createServer(options?: Partial): MockServer { - const baseUrl = options?.baseUrl || randomBaseUrl(); - const server = new MockServer({ baseUrl, server: mswServer }); - this.servers.push(server); - return server; - } - - public getServers(): MockServer[] { - return [...this.servers]; - } - - public listen(): void { - const onUnhandledRequest = process.env.LOG_LEVEL === "debug" ? "warn" : "bypass"; - mswServer.listen({ onUnhandledRequest }); - - if (process.env.LOG_LEVEL === "debug") { - mswServer.events.on("request:start", async ({ request, requestId }) => { - const formattedRequest = await formatHttpRequest(request, requestId); - console.debug(`request:start\n${formattedRequest}`); - }); - - mswServer.events.on("request:unhandled", async ({ request, requestId }) => { - const formattedRequest = await formatHttpRequest(request, requestId); - console.debug(`request:unhandled\n${formattedRequest}`); - }); - - mswServer.events.on("response:mocked", async ({ request, response, requestId }) => { - const formattedResponse = await formatHttpResponse(response, requestId); - console.debug(`response:mocked\n${formattedResponse}`); - }); - } - } - - public close(): void { - this.servers = []; - mswServer.close(); - } -} - -export const mockServerPool: MockServerPool = new MockServerPool(); diff --git a/seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts b/seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts deleted file mode 100644 index 3e8540a3ba5a..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/mockEndpointBuilder.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { type DefaultBodyType, type HttpHandler, HttpResponse, type HttpResponseResolver, http } from "msw"; - -import { url } from "../../src/core"; -import { toJson } from "../../src/core/json"; -import { type WithFormUrlEncodedOptions, withFormUrlEncoded } from "./withFormUrlEncoded"; -import { withHeaders } from "./withHeaders"; -import { type WithJsonOptions, withJson } from "./withJson"; - -type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; - -interface MethodStage { - baseUrl(baseUrl: string): MethodStage; - all(path: string): RequestHeadersStage; - get(path: string): RequestHeadersStage; - post(path: string): RequestHeadersStage; - put(path: string): RequestHeadersStage; - delete(path: string): RequestHeadersStage; - patch(path: string): RequestHeadersStage; - options(path: string): RequestHeadersStage; - head(path: string): RequestHeadersStage; -} - -interface RequestHeadersStage extends RequestBodyStage, ResponseStage { - header(name: string, value: string): RequestHeadersStage; - headers(headers: Record): RequestBodyStage; -} - -interface RequestBodyStage extends ResponseStage { - jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage; - formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage; -} - -interface ResponseStage { - respondWith(): ResponseStatusStage; -} -interface ResponseStatusStage { - statusCode(statusCode: number): ResponseHeaderStage; -} - -interface ResponseHeaderStage extends ResponseBodyStage, BuildStage { - header(name: string, value: string): ResponseHeaderStage; - headers(headers: Record): ResponseHeaderStage; -} - -interface ResponseBodyStage { - jsonBody(body: unknown): BuildStage; - sseBody(body: string): BuildStage; -} - -interface BuildStage { - build(): HttpHandler; -} - -export interface HttpHandlerBuilderOptions { - onBuild?: (handler: HttpHandler) => void; - once?: boolean; -} - -class RequestBuilder implements MethodStage, RequestHeadersStage, RequestBodyStage, ResponseStage { - private method: HttpMethod = "get"; - private _baseUrl: string = ""; - private path: string = "/"; - private readonly predicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[] = []; - private readonly handlerOptions?: HttpHandlerBuilderOptions; - - constructor(options?: HttpHandlerBuilderOptions) { - this.handlerOptions = options; - } - - baseUrl(baseUrl: string): MethodStage { - this._baseUrl = baseUrl; - return this; - } - - all(path: string): RequestHeadersStage { - this.method = "all"; - this.path = path; - return this; - } - - get(path: string): RequestHeadersStage { - this.method = "get"; - this.path = path; - return this; - } - - post(path: string): RequestHeadersStage { - this.method = "post"; - this.path = path; - return this; - } - - put(path: string): RequestHeadersStage { - this.method = "put"; - this.path = path; - return this; - } - - delete(path: string): RequestHeadersStage { - this.method = "delete"; - this.path = path; - return this; - } - - patch(path: string): RequestHeadersStage { - this.method = "patch"; - this.path = path; - return this; - } - - options(path: string): RequestHeadersStage { - this.method = "options"; - this.path = path; - return this; - } - - head(path: string): RequestHeadersStage { - this.method = "head"; - this.path = path; - return this; - } - - header(name: string, value: string): RequestHeadersStage { - this.predicates.push((resolver) => withHeaders({ [name]: value }, resolver)); - return this; - } - - headers(headers: Record): RequestBodyStage { - this.predicates.push((resolver) => withHeaders(headers, resolver)); - return this; - } - - jsonBody(body: unknown, options?: WithJsonOptions): ResponseStage { - if (body === undefined) { - throw new Error("Undefined is not valid JSON. Do not call jsonBody if you want an empty body."); - } - this.predicates.push((resolver) => withJson(body, resolver, options)); - return this; - } - - formUrlEncodedBody(body: unknown, options?: WithFormUrlEncodedOptions): ResponseStage { - if (body === undefined) { - throw new Error( - "Undefined is not valid for form-urlencoded. Do not call formUrlEncodedBody if you want an empty body.", - ); - } - this.predicates.push((resolver) => withFormUrlEncoded(body, resolver, options)); - return this; - } - - respondWith(): ResponseStatusStage { - return new ResponseBuilder(this.method, this.buildUrl(), this.predicates, this.handlerOptions); - } - - private buildUrl(): string { - return url.join(this._baseUrl, this.path); - } -} - -class ResponseBuilder implements ResponseStatusStage, ResponseHeaderStage, ResponseBodyStage, BuildStage { - private readonly method: HttpMethod; - private readonly url: string; - private readonly requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[]; - private readonly handlerOptions?: HttpHandlerBuilderOptions; - - private responseStatusCode: number = 200; - private responseHeaders: Record = {}; - private responseBody: DefaultBodyType = undefined; - - constructor( - method: HttpMethod, - url: string, - requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[], - options?: HttpHandlerBuilderOptions, - ) { - this.method = method; - this.url = url; - this.requestPredicates = requestPredicates; - this.handlerOptions = options; - } - - public statusCode(code: number): ResponseHeaderStage { - this.responseStatusCode = code; - return this; - } - - public header(name: string, value: string): ResponseHeaderStage { - this.responseHeaders[name] = value; - return this; - } - - public headers(headers: Record): ResponseHeaderStage { - this.responseHeaders = { ...this.responseHeaders, ...headers }; - return this; - } - - public jsonBody(body: unknown): BuildStage { - if (body === undefined) { - throw new Error("Undefined is not valid JSON. Do not call jsonBody if you expect an empty body."); - } - this.responseBody = toJson(body); - return this; - } - - public sseBody(body: string): BuildStage { - this.responseHeaders["Content-Type"] = "text/event-stream"; - this.responseBody = body; - return this; - } - - public build(): HttpHandler { - const responseResolver: HttpResponseResolver = () => { - const response = new HttpResponse(this.responseBody, { - status: this.responseStatusCode, - headers: this.responseHeaders, - }); - // if no Content-Type header is set, delete the default text content type that is set - if (Object.keys(this.responseHeaders).some((key) => key.toLowerCase() === "content-type") === false) { - response.headers.delete("Content-Type"); - } - return response; - }; - - const finalResolver = this.requestPredicates.reduceRight((acc, predicate) => predicate(acc), responseResolver); - - const handler = http[this.method](this.url, finalResolver, this.handlerOptions); - this.handlerOptions?.onBuild?.(handler); - return handler; - } -} - -export function mockEndpointBuilder(options?: HttpHandlerBuilderOptions): MethodStage { - return new RequestBuilder(options); -} diff --git a/seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts b/seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts deleted file mode 100644 index 031aa6408aca..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/randomBaseUrl.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function randomBaseUrl(): string { - const randomString = Math.random().toString(36).substring(2, 15); - return `http://${randomString}.localhost`; -} diff --git a/seed/ts-sdk/allof/tests/mock-server/setup.ts b/seed/ts-sdk/allof/tests/mock-server/setup.ts deleted file mode 100644 index aeb3a95af7dc..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/setup.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { afterAll, beforeAll } from "vitest"; - -import { mockServerPool } from "./MockServerPool"; - -beforeAll(() => { - mockServerPool.listen(); -}); -afterAll(() => { - mockServerPool.close(); -}); diff --git a/seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts b/seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts deleted file mode 100644 index 2b23448e3102..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/withFormUrlEncoded.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { type HttpResponseResolver, passthrough } from "msw"; - -import { toJson } from "../../src/core/json"; - -export interface WithFormUrlEncodedOptions { - /** - * List of field names to ignore when comparing request bodies. - * This is useful for pagination cursor fields that change between requests. - */ - ignoredFields?: string[]; -} - -/** - * Creates a request matcher that validates if the request form-urlencoded body exactly matches the expected object - * @param expectedBody - The exact body object to match against - * @param resolver - Response resolver to execute if body matches - * @param options - Optional configuration including fields to ignore - */ -export function withFormUrlEncoded( - expectedBody: unknown, - resolver: HttpResponseResolver, - options?: WithFormUrlEncodedOptions, -): HttpResponseResolver { - const ignoredFields = options?.ignoredFields ?? []; - return async (args) => { - const { request } = args; - - let clonedRequest: Request; - let bodyText: string | undefined; - let actualBody: Record; - try { - clonedRequest = request.clone(); - bodyText = await clonedRequest.text(); - if (bodyText === "") { - // Empty body is valid if expected body is also empty - const isExpectedEmpty = - expectedBody != null && - typeof expectedBody === "object" && - Object.keys(expectedBody as Record).length === 0; - if (!isExpectedEmpty) { - console.error("Request body is empty, expected a form-urlencoded body."); - return passthrough(); - } - actualBody = {}; - } else { - const params = new URLSearchParams(bodyText); - actualBody = {}; - for (const [key, value] of params.entries()) { - actualBody[key] = value; - } - } - } catch (error) { - console.error(`Error processing form-urlencoded request body:\n\tError: ${error}\n\tBody: ${bodyText}`); - return passthrough(); - } - - const mismatches = findMismatches(actualBody, expectedBody); - const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); - if (filteredMismatches.length > 0) { - console.error("Form-urlencoded body mismatch:", toJson(mismatches, undefined, 2)); - return passthrough(); - } - - return resolver(args); - }; -} - -function findMismatches(actual: any, expected: any): Record { - const mismatches: Record = {}; - - if (typeof actual !== typeof expected) { - return { value: { actual, expected } }; - } - - if (typeof actual !== "object" || actual === null || expected === null) { - if (actual !== expected) { - return { value: { actual, expected } }; - } - return {}; - } - - const actualKeys = Object.keys(actual); - const expectedKeys = Object.keys(expected); - - const allKeys = new Set([...actualKeys, ...expectedKeys]); - - for (const key of allKeys) { - if (!expectedKeys.includes(key)) { - if (actual[key] === undefined) { - continue; - } - mismatches[key] = { actual: actual[key], expected: undefined }; - } else if (!actualKeys.includes(key)) { - if (expected[key] === undefined) { - continue; - } - mismatches[key] = { actual: undefined, expected: expected[key] }; - } else if (actual[key] !== expected[key]) { - mismatches[key] = { actual: actual[key], expected: expected[key] }; - } - } - - return mismatches; -} diff --git a/seed/ts-sdk/allof/tests/mock-server/withHeaders.ts b/seed/ts-sdk/allof/tests/mock-server/withHeaders.ts deleted file mode 100644 index 6599d2b4a92d..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/withHeaders.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { type HttpResponseResolver, passthrough } from "msw"; - -/** - * Creates a request matcher that validates if request headers match specified criteria - * @param expectedHeaders - Headers to match against - * @param resolver - Response resolver to execute if headers match - */ -export function withHeaders( - expectedHeaders: Record boolean)>, - resolver: HttpResponseResolver, -): HttpResponseResolver { - return (args) => { - const { request } = args; - const { headers } = request; - - const mismatches: Record< - string, - { actual: string | null; expected: string | RegExp | ((value: string) => boolean) } - > = {}; - - for (const [key, expectedValue] of Object.entries(expectedHeaders)) { - const actualValue = headers.get(key); - - if (actualValue === null) { - mismatches[key] = { actual: null, expected: expectedValue }; - continue; - } - - if (typeof expectedValue === "function") { - if (!expectedValue(actualValue)) { - mismatches[key] = { actual: actualValue, expected: expectedValue }; - } - } else if (expectedValue instanceof RegExp) { - if (!expectedValue.test(actualValue)) { - mismatches[key] = { actual: actualValue, expected: expectedValue }; - } - } else if (expectedValue !== actualValue) { - mismatches[key] = { actual: actualValue, expected: expectedValue }; - } - } - - if (Object.keys(mismatches).length > 0) { - const formattedMismatches = formatHeaderMismatches(mismatches); - console.error("Header mismatch:", formattedMismatches); - return passthrough(); - } - - return resolver(args); - }; -} - -function formatHeaderMismatches( - mismatches: Record boolean) }>, -): Record { - const formatted: Record = {}; - - for (const [key, { actual, expected }] of Object.entries(mismatches)) { - formatted[key] = { - actual, - expected: - expected instanceof RegExp - ? expected.toString() - : typeof expected === "function" - ? "[Function]" - : expected, - }; - } - - return formatted; -} diff --git a/seed/ts-sdk/allof/tests/mock-server/withJson.ts b/seed/ts-sdk/allof/tests/mock-server/withJson.ts deleted file mode 100644 index 3e8800a0c374..000000000000 --- a/seed/ts-sdk/allof/tests/mock-server/withJson.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { type HttpResponseResolver, passthrough } from "msw"; - -import { fromJson, toJson } from "../../src/core/json"; - -export interface WithJsonOptions { - /** - * List of field names to ignore when comparing request bodies. - * This is useful for pagination cursor fields that change between requests. - */ - ignoredFields?: string[]; -} - -/** - * Creates a request matcher that validates if the request JSON body exactly matches the expected object - * @param expectedBody - The exact body object to match against - * @param resolver - Response resolver to execute if body matches - * @param options - Optional configuration including fields to ignore - */ -export function withJson( - expectedBody: unknown, - resolver: HttpResponseResolver, - options?: WithJsonOptions, -): HttpResponseResolver { - const ignoredFields = options?.ignoredFields ?? []; - return async (args) => { - const { request } = args; - - let clonedRequest: Request; - let bodyText: string | undefined; - let actualBody: unknown; - try { - clonedRequest = request.clone(); - bodyText = await clonedRequest.text(); - if (bodyText === "") { - console.error("Request body is empty, expected a JSON object."); - return passthrough(); - } - actualBody = fromJson(bodyText); - } catch (error) { - console.error(`Error processing request body:\n\tError: ${error}\n\tBody: ${bodyText}`); - return passthrough(); - } - - const mismatches = findMismatches(actualBody, expectedBody); - const filteredMismatches = Object.keys(mismatches).filter((key) => !ignoredFields.includes(key)); - if (filteredMismatches.length > 0) { - console.error("JSON body mismatch:", toJson(mismatches, undefined, 2)); - return passthrough(); - } - - return resolver(args); - }; -} - -function findMismatches(actual: any, expected: any): Record { - const mismatches: Record = {}; - - if (typeof actual !== typeof expected) { - if (areEquivalent(actual, expected)) { - return {}; - } - return { value: { actual, expected } }; - } - - if (typeof actual !== "object" || actual === null || expected === null) { - if (actual !== expected) { - if (areEquivalent(actual, expected)) { - return {}; - } - return { value: { actual, expected } }; - } - return {}; - } - - if (Array.isArray(actual) && Array.isArray(expected)) { - if (actual.length !== expected.length) { - return { length: { actual: actual.length, expected: expected.length } }; - } - - const arrayMismatches: Record = {}; - for (let i = 0; i < actual.length; i++) { - const itemMismatches = findMismatches(actual[i], expected[i]); - if (Object.keys(itemMismatches).length > 0) { - for (const [mismatchKey, mismatchValue] of Object.entries(itemMismatches)) { - arrayMismatches[`[${i}]${mismatchKey === "value" ? "" : `.${mismatchKey}`}`] = mismatchValue; - } - } - } - return arrayMismatches; - } - - const actualKeys = Object.keys(actual); - const expectedKeys = Object.keys(expected); - - const allKeys = new Set([...actualKeys, ...expectedKeys]); - - for (const key of allKeys) { - if (!expectedKeys.includes(key)) { - if (actual[key] === undefined) { - continue; // Skip undefined values in actual - } - mismatches[key] = { actual: actual[key], expected: undefined }; - } else if (!actualKeys.includes(key)) { - if (expected[key] === undefined) { - continue; // Skip undefined values in expected - } - mismatches[key] = { actual: undefined, expected: expected[key] }; - } else if ( - typeof actual[key] === "object" && - actual[key] !== null && - typeof expected[key] === "object" && - expected[key] !== null - ) { - const nestedMismatches = findMismatches(actual[key], expected[key]); - if (Object.keys(nestedMismatches).length > 0) { - for (const [nestedKey, nestedValue] of Object.entries(nestedMismatches)) { - mismatches[`${key}${nestedKey === "value" ? "" : `.${nestedKey}`}`] = nestedValue; - } - } - } else if (actual[key] !== expected[key]) { - if (areEquivalent(actual[key], expected[key])) { - continue; - } - mismatches[key] = { actual: actual[key], expected: expected[key] }; - } - } - - return mismatches; -} - -function areEquivalent(actual: unknown, expected: unknown): boolean { - if (actual === expected) { - return true; - } - if (isEquivalentBigInt(actual, expected)) { - return true; - } - if (isEquivalentDatetime(actual, expected)) { - return true; - } - return false; -} - -function isEquivalentBigInt(actual: unknown, expected: unknown) { - if (typeof actual === "number") { - actual = BigInt(actual); - } - if (typeof expected === "number") { - expected = BigInt(expected); - } - if (typeof actual === "bigint" && typeof expected === "bigint") { - return actual === expected; - } - return false; -} - -function isEquivalentDatetime(str1: unknown, str2: unknown): boolean { - if (typeof str1 !== "string" || typeof str2 !== "string") { - return false; - } - const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/; - if (!isoDatePattern.test(str1) || !isoDatePattern.test(str2)) { - return false; - } - - try { - const date1 = new Date(str1).getTime(); - const date2 = new Date(str2).getTime(); - return date1 === date2; - } catch { - return false; - } -} diff --git a/seed/ts-sdk/allof/tests/setup.ts b/seed/ts-sdk/allof/tests/setup.ts deleted file mode 100644 index a5651f81ba10..000000000000 --- a/seed/ts-sdk/allof/tests/setup.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { expect } from "vitest"; - -interface CustomMatchers { - toContainHeaders(expectedHeaders: Record): R; -} - -declare module "vitest" { - interface Assertion extends CustomMatchers {} - interface AsymmetricMatchersContaining extends CustomMatchers {} -} - -expect.extend({ - toContainHeaders(actual: unknown, expectedHeaders: Record) { - const isHeaders = actual instanceof Headers; - const isPlainObject = typeof actual === "object" && actual !== null && !Array.isArray(actual); - - if (!isHeaders && !isPlainObject) { - throw new TypeError("Received value must be an instance of Headers or a plain object!"); - } - - if (typeof expectedHeaders !== "object" || expectedHeaders === null || Array.isArray(expectedHeaders)) { - throw new TypeError("Expected headers must be a plain object!"); - } - - const missingHeaders: string[] = []; - const mismatchedHeaders: Array<{ key: string; expected: string; actual: string | null }> = []; - - for (const [key, value] of Object.entries(expectedHeaders)) { - let actualValue: string | null = null; - - if (isHeaders) { - // Headers.get() is already case-insensitive - actualValue = (actual as Headers).get(key); - } else { - // For plain objects, do case-insensitive lookup - const actualObj = actual as Record; - const lowerKey = key.toLowerCase(); - const foundKey = Object.keys(actualObj).find((k) => k.toLowerCase() === lowerKey); - actualValue = foundKey ? actualObj[foundKey] : null; - } - - if (actualValue === null || actualValue === undefined) { - missingHeaders.push(key); - } else if (actualValue !== value) { - mismatchedHeaders.push({ key, expected: value, actual: actualValue }); - } - } - - const pass = missingHeaders.length === 0 && mismatchedHeaders.length === 0; - - const actualType = isHeaders ? "Headers" : "object"; - - if (pass) { - return { - message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, - pass: true, - }; - } else { - const messages: string[] = []; - - if (missingHeaders.length > 0) { - messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); - } - - if (mismatchedHeaders.length > 0) { - const mismatches = mismatchedHeaders.map( - ({ key, expected, actual }) => - `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, - ); - messages.push(mismatches.join("\n")); - } - - return { - message: () => - `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, - pass: false, - }; - } - }, -}); diff --git a/seed/ts-sdk/allof/tests/tsconfig.json b/seed/ts-sdk/allof/tests/tsconfig.json deleted file mode 100644 index ac39744de7b2..000000000000 --- a/seed/ts-sdk/allof/tests/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": null, - "rootDir": "..", - "types": ["vitest/globals"] - }, - "include": ["../src", "../tests"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts deleted file mode 100644 index 6c17624228bb..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/Fetcher.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import fs from "fs"; -import { join } from "path"; -import stream from "stream"; -import type { BinaryResponse } from "../../../src/core"; -import { type Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; - -describe("Test fetcherImpl", () => { - it("should handle successful request", async () => { - const mockArgs: Fetcher.Args = { - url: "https://httpbin.org/post", - method: "POST", - headers: { "X-Test": "x-test-header" }, - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - maxRetries: 0, - responseType: "json", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - }), - ); - - const result = await fetcherImpl(mockArgs); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.body).toEqual({ data: "test" }); - } - - expect(global.fetch).toHaveBeenCalledWith( - "https://httpbin.org/post", - expect.objectContaining({ - method: "POST", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - body: JSON.stringify({ data: "test" }), - }), - ); - }); - - it("should send octet stream", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "POST", - headers: { "X-Test": "x-test-header" }, - contentType: "application/octet-stream", - requestType: "bytes", - maxRetries: 0, - responseType: "json", - body: fs.createReadStream(join(__dirname, "test-file.txt")), - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - }), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "POST", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - body: expect.any(fs.ReadStream), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.body).toEqual({ data: "test" }); - } - }); - - it("should receive file as stream", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.stream).toBe("function"); - const stream = body.stream(); - expect(stream).toBeInstanceOf(ReadableStream); - const readableStream = stream as ReadableStream; - const reader = readableStream.getReader(); - const { value } = await reader.read(); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(value); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); - - it("should receive file as blob", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.blob).toBe("function"); - const blob = await body.blob(); - expect(blob).toBeInstanceOf(Blob); - const reader = blob.stream().getReader(); - const { value } = await reader.read(); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(value); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); - - it("should receive file as arraybuffer", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.arrayBuffer).toBe("function"); - const arrayBuffer = await body.arrayBuffer(); - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(new Uint8Array(arrayBuffer)); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); - - it("should receive file as bytes", async () => { - const url = "https://httpbin.org/post/file"; - const mockArgs: Fetcher.Args = { - url, - method: "GET", - headers: { "X-Test": "x-test-header" }, - maxRetries: 0, - responseType: "binary-response", - }; - - global.fetch = vi.fn().mockResolvedValue( - new Response( - stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, - { - status: 200, - statusText: "OK", - }, - ), - ); - - const result = await fetcherImpl(mockArgs); - - expect(global.fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: "GET", - headers: expect.toContainHeaders({ "X-Test": "x-test-header" }), - }), - ); - expect(result.ok).toBe(true); - if (result.ok) { - const body = result.body as BinaryResponse; - expect(body).toBeDefined(); - expect(body.bodyUsed).toBe(false); - expect(typeof body.bytes).toBe("function"); - if (!body.bytes) { - return; - } - const bytes = await body.bytes(); - expect(bytes).toBeInstanceOf(Uint8Array); - const decoder = new TextDecoder(); - const streamContent = decoder.decode(bytes); - expect(streamContent.trim()).toBe("This is a test file!"); - expect(body.bodyUsed).toBe(true); - } - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts deleted file mode 100644 index 2ec008e581d8..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/HttpResponsePromise.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -import { HttpResponsePromise } from "../../../src/core/fetcher/HttpResponsePromise"; -import type { RawResponse, WithRawResponse } from "../../../src/core/fetcher/RawResponse"; - -describe("HttpResponsePromise", () => { - const mockRawResponse: RawResponse = { - headers: new Headers(), - redirected: false, - status: 200, - statusText: "OK", - type: "basic" as ResponseType, - url: "https://example.com", - }; - const mockData = { id: "123", name: "test" }; - const mockWithRawResponse: WithRawResponse = { - data: mockData, - rawResponse: mockRawResponse, - }; - - describe("fromFunction", () => { - it("should create an HttpResponsePromise from a function", async () => { - const mockFn = vi - .fn<(arg1: string, arg2: string) => Promise>>() - .mockResolvedValue(mockWithRawResponse); - - const responsePromise = HttpResponsePromise.fromFunction(mockFn, "arg1", "arg2"); - - const result = await responsePromise; - expect(result).toEqual(mockData); - expect(mockFn).toHaveBeenCalledWith("arg1", "arg2"); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("fromPromise", () => { - it("should create an HttpResponsePromise from a promise", async () => { - const promise = Promise.resolve(mockWithRawResponse); - - const responsePromise = HttpResponsePromise.fromPromise(promise); - - const result = await responsePromise; - expect(result).toEqual(mockData); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("fromExecutor", () => { - it("should create an HttpResponsePromise from an executor function", async () => { - const responsePromise = HttpResponsePromise.fromExecutor((resolve) => { - resolve(mockWithRawResponse); - }); - - const result = await responsePromise; - expect(result).toEqual(mockData); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("fromResult", () => { - it("should create an HttpResponsePromise from a result", async () => { - const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); - - const result = await responsePromise; - expect(result).toEqual(mockData); - - const resultWithRawResponse = await responsePromise.withRawResponse(); - expect(resultWithRawResponse).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); - - describe("Promise methods", () => { - let responsePromise: HttpResponsePromise; - - beforeEach(() => { - responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); - }); - - it("should support then() method", async () => { - const result = await responsePromise.then((data) => ({ - ...data, - modified: true, - })); - - expect(result).toEqual({ - ...mockData, - modified: true, - }); - }); - - it("should support catch() method", async () => { - const errorResponsePromise = HttpResponsePromise.fromExecutor((_, reject) => { - reject(new Error("Test error")); - }); - - const catchSpy = vi.fn(); - await errorResponsePromise.catch(catchSpy); - - expect(catchSpy).toHaveBeenCalled(); - const error = catchSpy.mock.calls[0]?.[0]; - expect(error).toBeInstanceOf(Error); - expect((error as Error).message).toBe("Test error"); - }); - - it("should support finally() method", async () => { - const finallySpy = vi.fn(); - await responsePromise.finally(finallySpy); - - expect(finallySpy).toHaveBeenCalled(); - }); - }); - - describe("withRawResponse", () => { - it("should return both data and raw response", async () => { - const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); - - const result = await responsePromise.withRawResponse(); - - expect(result).toEqual({ - data: mockData, - rawResponse: mockRawResponse, - }); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts deleted file mode 100644 index 375ee3f38064..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/RawResponse.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { toRawResponse } from "../../../src/core/fetcher/RawResponse"; - -describe("RawResponse", () => { - describe("toRawResponse", () => { - it("should convert Response to RawResponse by removing body, bodyUsed, and ok properties", () => { - const mockHeaders = new Headers({ "content-type": "application/json" }); - const mockResponse = { - body: "test body", - bodyUsed: false, - ok: true, - headers: mockHeaders, - redirected: false, - status: 200, - statusText: "OK", - type: "basic" as ResponseType, - url: "https://example.com", - }; - - const result = toRawResponse(mockResponse as unknown as Response); - - expect("body" in result).toBe(false); - expect("bodyUsed" in result).toBe(false); - expect("ok" in result).toBe(false); - expect(result.headers).toBe(mockHeaders); - expect(result.redirected).toBe(false); - expect(result.status).toBe(200); - expect(result.statusText).toBe("OK"); - expect(result.type).toBe("basic"); - expect(result.url).toBe("https://example.com"); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts deleted file mode 100644 index a92f1b5e81d1..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/createRequestUrl.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; - -describe("Test createRequestUrl", () => { - const BASE_URL = "https://api.example.com"; - - interface TestCase { - description: string; - baseUrl: string; - queryParams?: Record; - expected: string; - } - - const testCases: TestCase[] = [ - { - description: "should return the base URL when no query parameters are provided", - baseUrl: BASE_URL, - expected: BASE_URL, - }, - { - description: "should append simple query parameters", - baseUrl: BASE_URL, - queryParams: { key: "value", another: "param" }, - expected: "https://api.example.com?key=value&another=param", - }, - { - description: "should handle array query parameters", - baseUrl: BASE_URL, - queryParams: { items: ["a", "b", "c"] }, - expected: "https://api.example.com?items=a&items=b&items=c", - }, - { - description: "should handle object query parameters", - baseUrl: BASE_URL, - queryParams: { filter: { name: "John", age: 30 } }, - expected: "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30", - }, - { - description: "should handle mixed types of query parameters", - baseUrl: BASE_URL, - queryParams: { - simple: "value", - array: ["x", "y"], - object: { key: "value" }, - }, - expected: "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value", - }, - { - description: "should handle empty query parameters object", - baseUrl: BASE_URL, - queryParams: {}, - expected: BASE_URL, - }, - { - description: "should encode special characters in query parameters", - baseUrl: BASE_URL, - queryParams: { special: "a&b=c d" }, - expected: "https://api.example.com?special=a%26b%3Dc%20d", - }, - { - description: "should handle numeric values", - baseUrl: BASE_URL, - queryParams: { count: 42, price: 19.99, active: 1, inactive: 0 }, - expected: "https://api.example.com?count=42&price=19.99&active=1&inactive=0", - }, - { - description: "should handle boolean values", - baseUrl: BASE_URL, - queryParams: { enabled: true, disabled: false }, - expected: "https://api.example.com?enabled=true&disabled=false", - }, - { - description: "should handle null and undefined values", - baseUrl: BASE_URL, - queryParams: { - valid: "value", - nullValue: null, - undefinedValue: undefined, - emptyString: "", - }, - expected: "https://api.example.com?valid=value&nullValue=&emptyString=", - }, - { - description: "should handle deeply nested objects", - baseUrl: BASE_URL, - queryParams: { - user: { - profile: { - name: "John", - settings: { theme: "dark" }, - }, - }, - }, - expected: - "https://api.example.com?user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", - }, - { - description: "should handle arrays of objects", - baseUrl: BASE_URL, - queryParams: { - users: [ - { name: "John", age: 30 }, - { name: "Jane", age: 25 }, - ], - }, - expected: - "https://api.example.com?users%5Bname%5D=John&users%5Bage%5D=30&users%5Bname%5D=Jane&users%5Bage%5D=25", - }, - { - description: "should handle mixed arrays", - baseUrl: BASE_URL, - queryParams: { - mixed: ["string", 42, true, { key: "value" }], - }, - expected: "https://api.example.com?mixed=string&mixed=42&mixed=true&mixed%5Bkey%5D=value", - }, - { - description: "should handle empty arrays", - baseUrl: BASE_URL, - queryParams: { emptyArray: [] }, - expected: BASE_URL, - }, - { - description: "should handle empty objects", - baseUrl: BASE_URL, - queryParams: { emptyObject: {} }, - expected: BASE_URL, - }, - { - description: "should handle special characters in keys", - baseUrl: BASE_URL, - queryParams: { "key with spaces": "value", "key[with]brackets": "value" }, - expected: "https://api.example.com?key%20with%20spaces=value&key%5Bwith%5Dbrackets=value", - }, - { - description: "should handle URL with existing query parameters", - baseUrl: "https://api.example.com?existing=param", - queryParams: { new: "value" }, - expected: "https://api.example.com?existing=param?new=value", - }, - { - description: "should handle complex nested structures", - baseUrl: BASE_URL, - queryParams: { - filters: { - status: ["active", "pending"], - category: { - type: "electronics", - subcategories: ["phones", "laptops"], - }, - }, - sort: { field: "name", direction: "asc" }, - }, - expected: - "https://api.example.com?filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", - }, - ]; - - testCases.forEach(({ description, baseUrl, queryParams, expected }) => { - it(description, () => { - expect(createRequestUrl(baseUrl, queryParams)).toBe(expected); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts deleted file mode 100644 index 8a6c3a57e211..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/getRequestBody.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; -import { RUNTIME } from "../../../src/core/runtime"; - -describe("Test getRequestBody", () => { - interface TestCase { - description: string; - input: any; - type: "json" | "form" | "file" | "bytes" | "other"; - expected: any; - skipCondition?: () => boolean; - } - - const testCases: TestCase[] = [ - { - description: "should stringify body if not FormData in Node environment", - input: { key: "value" }, - type: "json", - expected: '{"key":"value"}', - skipCondition: () => RUNTIME.type !== "node", - }, - { - description: "should stringify body if not FormData in browser environment", - input: { key: "value" }, - type: "json", - expected: '{"key":"value"}', - skipCondition: () => RUNTIME.type !== "browser", - }, - { - description: "should return the Uint8Array", - input: new Uint8Array([1, 2, 3]), - type: "bytes", - expected: new Uint8Array([1, 2, 3]), - }, - { - description: "should serialize objects for form-urlencoded content type", - input: { username: "johndoe", email: "john@example.com" }, - type: "form", - expected: "username=johndoe&email=john%40example.com", - }, - { - description: "should serialize complex nested objects and arrays for form-urlencoded content type", - input: { - user: { - profile: { - name: "John Doe", - settings: { - theme: "dark", - notifications: true, - }, - }, - tags: ["admin", "user"], - contacts: [ - { type: "email", value: "john@example.com" }, - { type: "phone", value: "+1234567890" }, - ], - }, - filters: { - status: ["active", "pending"], - metadata: { - created: "2024-01-01", - categories: ["electronics", "books"], - }, - }, - preferences: ["notifications", "updates"], - }, - type: "form", - expected: - "user%5Bprofile%5D%5Bname%5D=John%20Doe&" + - "user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark&" + - "user%5Bprofile%5D%5Bsettings%5D%5Bnotifications%5D=true&" + - "user%5Btags%5D=admin&" + - "user%5Btags%5D=user&" + - "user%5Bcontacts%5D%5Btype%5D=email&" + - "user%5Bcontacts%5D%5Bvalue%5D=john%40example.com&" + - "user%5Bcontacts%5D%5Btype%5D=phone&" + - "user%5Bcontacts%5D%5Bvalue%5D=%2B1234567890&" + - "filters%5Bstatus%5D=active&" + - "filters%5Bstatus%5D=pending&" + - "filters%5Bmetadata%5D%5Bcreated%5D=2024-01-01&" + - "filters%5Bmetadata%5D%5Bcategories%5D=electronics&" + - "filters%5Bmetadata%5D%5Bcategories%5D=books&" + - "preferences=notifications&" + - "preferences=updates", - }, - { - description: "should return the input for pre-serialized form-urlencoded strings", - input: "key=value&another=param", - type: "other", - expected: "key=value&another=param", - }, - { - description: "should JSON stringify objects", - input: { key: "value" }, - type: "json", - expected: '{"key":"value"}', - }, - ]; - - testCases.forEach(({ description, input, type, expected, skipCondition }) => { - it(description, async () => { - if (skipCondition?.()) { - return; - } - - const result = await getRequestBody({ - body: input, - type, - }); - - if (input instanceof Uint8Array) { - expect(result).toBe(input); - } else { - expect(result).toBe(expected); - } - }); - }); - - it("should return FormData in browser environment", async () => { - if (RUNTIME.type === "browser") { - const formData = new FormData(); - formData.append("key", "value"); - const result = await getRequestBody({ - body: formData, - type: "file", - }); - expect(result).toBe(formData); - } - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts deleted file mode 100644 index ad6be7fc2c9b..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/getResponseBody.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; - -import { RUNTIME } from "../../../src/core/runtime"; - -describe("Test getResponseBody", () => { - interface SimpleTestCase { - description: string; - responseData: string | Record; - responseType?: "blob" | "sse" | "streaming" | "text"; - expected: any; - skipCondition?: () => boolean; - } - - const simpleTestCases: SimpleTestCase[] = [ - { - description: "should handle text response type", - responseData: "test text", - responseType: "text", - expected: "test text", - }, - { - description: "should handle JSON response", - responseData: { key: "value" }, - expected: { key: "value" }, - }, - { - description: "should handle empty response", - responseData: "", - expected: undefined, - }, - { - description: "should handle non-JSON response", - responseData: "invalid json", - expected: { - ok: false, - error: { - reason: "non-json", - statusCode: 200, - rawBody: "invalid json", - }, - }, - }, - ]; - - simpleTestCases.forEach(({ description, responseData, responseType, expected, skipCondition }) => { - it(description, async () => { - if (skipCondition?.()) { - return; - } - - const mockResponse = new Response( - typeof responseData === "string" ? responseData : JSON.stringify(responseData), - ); - const result = await getResponseBody(mockResponse, responseType); - expect(result).toEqual(expected); - }); - }); - - it("should handle blob response type", async () => { - const mockBlob = new Blob(["test"], { type: "text/plain" }); - const mockResponse = new Response(mockBlob); - const result = await getResponseBody(mockResponse, "blob"); - // @ts-expect-error - expect(result.constructor.name).toBe("Blob"); - }); - - it("should handle sse response type", async () => { - if (RUNTIME.type === "node") { - const mockStream = new ReadableStream(); - const mockResponse = new Response(mockStream); - const result = await getResponseBody(mockResponse, "sse"); - expect(result).toBe(mockStream); - } - }); - - it("should handle streaming response type", async () => { - const encoder = new TextEncoder(); - const testData = "test stream data"; - const mockStream = new ReadableStream({ - start(controller) { - controller.enqueue(encoder.encode(testData)); - controller.close(); - }, - }); - - const mockResponse = new Response(mockStream); - const result = (await getResponseBody(mockResponse, "streaming")) as ReadableStream; - - expect(result).toBeInstanceOf(ReadableStream); - - const reader = result.getReader(); - const decoder = new TextDecoder(); - const { value } = await reader.read(); - const streamContent = decoder.decode(value); - expect(streamContent).toBe(testData); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts deleted file mode 100644 index 366c9b6ced61..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/logging.test.ts +++ /dev/null @@ -1,517 +0,0 @@ -import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; - -function createMockLogger() { - return { - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }; -} - -function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify(data), { - status, - statusText, - }), - ); -} - -function mockErrorResponse(data: unknown = { error: "Error" }, status = 404, statusText = "Not Found") { - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify(data), { - status, - statusText, - }), - ); -} - -describe("Fetcher Logging Integration", () => { - describe("Request Logging", () => { - it("should log successful request at debug level", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - headers: { "Content-Type": "application/json" }, - body: { test: "data" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "POST", - url: "https://example.com/api", - headers: expect.toContainHeaders({ - "Content-Type": "application/json", - }), - hasBody: true, - }), - ); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - method: "POST", - url: "https://example.com/api", - statusCode: 200, - }), - ); - }); - - it("should not log debug messages at info level for successful requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "info", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - }); - - it("should log request with body flag", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - hasBody: true, - }), - ); - }); - - it("should log request without body flag", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - hasBody: false, - }), - ); - }); - - it("should not log when silent mode is enabled", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: true, - }, - }); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).not.toHaveBeenCalled(); - expect(mockLogger.error).not.toHaveBeenCalled(); - }); - - it("should not log when no logging config is provided", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - }); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - }); - }); - - describe("Error Logging", () => { - it("should log 4xx errors at error level", async () => { - const mockLogger = createMockLogger(); - mockErrorResponse({ error: "Not found" }, 404, "Not Found"); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - statusCode: 404, - }), - ); - }); - - it("should log 5xx errors at error level", async () => { - const mockLogger = createMockLogger(); - mockErrorResponse({ error: "Internal error" }, 500, "Internal Server Error"); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - statusCode: 500, - }), - ); - }); - - it("should log aborted request errors", async () => { - const mockLogger = createMockLogger(); - - const abortController = new AbortController(); - abortController.abort(); - - global.fetch = vi.fn().mockRejectedValue(new Error("Aborted")); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - abortSignal: abortController.signal, - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request was aborted", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - }), - ); - }); - - it("should log timeout errors", async () => { - const mockLogger = createMockLogger(); - - const timeoutError = new Error("Request timeout"); - timeoutError.name = "AbortError"; - - global.fetch = vi.fn().mockRejectedValue(timeoutError); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request timed out", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - timeoutMs: undefined, - }), - ); - }); - - it("should log unknown errors", async () => { - const mockLogger = createMockLogger(); - - const unknownError = new Error("Unknown error"); - - global.fetch = vi.fn().mockRejectedValue(unknownError); - - const result = await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(result.ok).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error", - expect.objectContaining({ - method: "GET", - url: "https://example.com/api", - errorMessage: "Unknown error", - }), - ); - }); - }); - - describe("Logging with Redaction", () => { - it("should redact sensitive data in error logs", async () => { - const mockLogger = createMockLogger(); - mockErrorResponse({ error: "Unauthorized" }, 401, "Unauthorized"); - - await fetcherImpl({ - url: "https://example.com/api?api_key=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - url: "https://example.com/api?api_key=[REDACTED]", - }), - ); - }); - }); - - describe("Different HTTP Methods", () => { - it("should log GET requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "GET", - }), - ); - }); - - it("should log POST requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse({ data: "test" }, 201, "Created"); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "POST", - }), - ); - }); - - it("should log PUT requests", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "PUT", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "PUT", - }), - ); - }); - - it("should log DELETE requests", async () => { - const mockLogger = createMockLogger(); - global.fetch = vi.fn().mockResolvedValue( - new Response(null, { - status: 200, - statusText: "OK", - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "DELETE", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - method: "DELETE", - }), - ); - }); - }); - - describe("Status Code Logging", () => { - it("should log 2xx success status codes", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse({ data: "test" }, 201, "Created"); - - await fetcherImpl({ - url: "https://example.com/api", - method: "POST", - body: { data: "test" }, - contentType: "application/json", - requestType: "json", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - statusCode: 201, - }), - ); - }); - - it("should log 3xx redirect status codes as success", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse({ data: "test" }, 301, "Moved Permanently"); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - statusCode: 301, - }), - ); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts deleted file mode 100644 index 1850d1fda959..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/makePassthroughRequest.test.ts +++ /dev/null @@ -1,398 +0,0 @@ -import type { Mock } from "vitest"; -import { makePassthroughRequest } from "../../../src/core/fetcher/makePassthroughRequest"; - -describe("makePassthroughRequest", () => { - let mockFetch: Mock; - - beforeEach(() => { - mockFetch = vi.fn(); - mockFetch.mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 200 })); - }); - - describe("URL resolution", () => { - it("should use absolute URL directly", async () => { - await makePassthroughRequest("https://api.example.com/v1/users", undefined, { - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/users"); - }); - - it("should resolve relative path against baseUrl", async () => { - await makePassthroughRequest("/v1/users", undefined, { - baseUrl: "https://api.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/users"); - }); - - it("should resolve relative path against environment when baseUrl is not set", async () => { - await makePassthroughRequest("/v1/users", undefined, { - environment: "https://env.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://env.example.com/v1/users"); - }); - - it("should prefer baseUrl over environment", async () => { - await makePassthroughRequest("/v1/users", undefined, { - baseUrl: "https://base.example.com", - environment: "https://env.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://base.example.com/v1/users"); - }); - - it("should pass relative URL through as-is when no baseUrl or environment", async () => { - await makePassthroughRequest("/v1/users", undefined, { - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("/v1/users"); - }); - - it("should resolve baseUrl supplier", async () => { - await makePassthroughRequest("/v1/users", undefined, { - baseUrl: () => "https://dynamic.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://dynamic.example.com/v1/users"); - }); - - it("should ignore absolute URL even when baseUrl is set", async () => { - await makePassthroughRequest("https://other.example.com/path", undefined, { - baseUrl: "https://base.example.com", - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://other.example.com/path"); - }); - - it("should accept a URL object", async () => { - await makePassthroughRequest(new URL("https://api.example.com/v1/users"), undefined, { - fetch: mockFetch, - }); - const [calledUrl] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/users"); - }); - }); - - describe("header merge order", () => { - it("should merge headers in correct priority: SDK defaults < auth < init < requestOptions", async () => { - await makePassthroughRequest( - "https://api.example.com", - { - headers: { "X-Custom": "from-init", Authorization: "from-init" }, - }, - { - headers: { - "X-Custom": "from-sdk", - "X-SDK-Only": "sdk-value", - Authorization: "from-sdk", - }, - getAuthHeaders: async () => ({ - Authorization: "Bearer auth-token", - "X-Auth-Only": "auth-value", - }), - fetch: mockFetch, - }, - { - headers: { Authorization: "from-request-options" }, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - const headers = calledOptions.headers; - - // requestOptions.headers wins for Authorization (highest priority) - expect(headers.authorization).toBe("from-request-options"); - // init.headers wins over SDK defaults for X-Custom - expect(headers["x-custom"]).toBe("from-init"); - // SDK-only header is preserved - expect(headers["x-sdk-only"]).toBe("sdk-value"); - // Auth-only header is preserved - expect(headers["x-auth-only"]).toBe("auth-value"); - }); - - it("should lowercase all header keys", async () => { - await makePassthroughRequest( - "https://api.example.com", - { - headers: { "Content-Type": "application/json" }, - }, - { - headers: { "X-Fern-Language": "JavaScript" }, - fetch: mockFetch, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - const headers = calledOptions.headers; - expect(headers["content-type"]).toBe("application/json"); - expect(headers["x-fern-language"]).toBe("JavaScript"); - expect(headers["Content-Type"]).toBeUndefined(); - expect(headers["X-Fern-Language"]).toBeUndefined(); - }); - - it("should handle Headers object in init", async () => { - const initHeaders = new Headers(); - initHeaders.set("X-From-Headers-Object", "value"); - await makePassthroughRequest("https://api.example.com", { headers: initHeaders }, { fetch: mockFetch }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-from-headers-object"]).toBe("value"); - }); - - it("should handle array-style headers in init", async () => { - await makePassthroughRequest( - "https://api.example.com", - { headers: [["X-Array-Header", "array-value"]] }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-array-header"]).toBe("array-value"); - }); - - it("should skip null SDK default header values", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - headers: { "X-Present": "value", "X-Null": null }, - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-present"]).toBe("value"); - expect(calledOptions.headers["x-null"]).toBeUndefined(); - }); - }); - - describe("auth headers", () => { - it("should include auth headers when getAuthHeaders is provided", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers.authorization).toBe("Bearer my-token"); - }); - - it("should work without auth headers", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers.authorization).toBeUndefined(); - }); - - it("should allow init headers to override auth headers", async () => { - await makePassthroughRequest( - "https://api.example.com", - { headers: { Authorization: "Bearer override" } }, - { - getAuthHeaders: async () => ({ Authorization: "Bearer sdk-auth" }), - fetch: mockFetch, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers.authorization).toBe("Bearer override"); - }); - }); - - describe("method and body", () => { - it("should default to GET when no method specified", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.method).toBe("GET"); - }); - - it("should use the method from init", async () => { - await makePassthroughRequest( - "https://api.example.com", - { method: "POST", body: JSON.stringify({ key: "value" }) }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.method).toBe("POST"); - expect(calledOptions.body).toBe(JSON.stringify({ key: "value" })); - }); - - it("should pass body as undefined when not provided", async () => { - await makePassthroughRequest("https://api.example.com", { method: "GET" }, { fetch: mockFetch }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.body).toBeUndefined(); - }); - }); - - describe("timeout and retries", () => { - it("should use requestOptions timeout over client timeout", async () => { - await makePassthroughRequest( - "https://api.example.com", - undefined, - { timeoutInSeconds: 30, fetch: mockFetch }, - { timeoutInSeconds: 10 }, - ); - // The timeout is passed to makeRequest which converts to ms - // We verify via the signal timing behavior (indirectly tested through makeRequest) - expect(mockFetch).toHaveBeenCalledTimes(1); - }); - - it("should use client timeout when requestOptions timeout is not set", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - timeoutInSeconds: 30, - fetch: mockFetch, - }); - expect(mockFetch).toHaveBeenCalledTimes(1); - }); - - it("should use requestOptions maxRetries over client maxRetries", async () => { - mockFetch.mockResolvedValue(new Response("", { status: 500 })); - vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - await makePassthroughRequest( - "https://api.example.com", - undefined, - { maxRetries: 5, fetch: mockFetch }, - { maxRetries: 1 }, - ); - // 1 initial + 1 retry = 2 calls - expect(mockFetch).toHaveBeenCalledTimes(2); - - vi.restoreAllMocks(); - }); - }); - - describe("abort signal", () => { - it("should use requestOptions.abortSignal over init.signal", async () => { - const initController = new AbortController(); - const requestController = new AbortController(); - - await makePassthroughRequest( - "https://api.example.com", - { signal: initController.signal }, - { fetch: mockFetch }, - { abortSignal: requestController.signal }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - // The signal passed to makeRequest is combined with timeout signal via anySignal, - // but the requestOptions.abortSignal should be the one that's used (not init.signal) - expect(calledOptions.signal).toBeDefined(); - }); - - it("should use init.signal when requestOptions.abortSignal is not set", async () => { - const initController = new AbortController(); - - await makePassthroughRequest( - "https://api.example.com", - { signal: initController.signal }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.signal).toBeDefined(); - }); - }); - - describe("credentials", () => { - it("should pass credentials include when set", async () => { - await makePassthroughRequest("https://api.example.com", { credentials: "include" }, { fetch: mockFetch }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.credentials).toBe("include"); - }); - - it("should not pass credentials when not set to include", async () => { - await makePassthroughRequest( - "https://api.example.com", - { credentials: "same-origin" }, - { - fetch: mockFetch, - }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.credentials).toBeUndefined(); - }); - }); - - describe("response", () => { - it("should return the Response object from fetch", async () => { - const mockResponse = new Response(JSON.stringify({ data: "test" }), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - mockFetch.mockResolvedValue(mockResponse); - - const response = await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - expect(response).toBe(mockResponse); - expect(response.status).toBe(200); - }); - - it("should return error responses without throwing", async () => { - const errorResponse = new Response("Not Found", { status: 404 }); - mockFetch.mockResolvedValue(errorResponse); - - const response = await makePassthroughRequest("https://api.example.com", undefined, { - fetch: mockFetch, - }); - expect(response.status).toBe(404); - }); - }); - - describe("Request object input", () => { - it("should extract URL from Request object", async () => { - const request = new Request("https://api.example.com/v1/resource", { method: "POST" }); - await makePassthroughRequest(request, undefined, { - fetch: mockFetch, - }); - const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe("https://api.example.com/v1/resource"); - expect(calledOptions.method).toBe("POST"); - }); - - it("should extract headers from Request object when no init provided", async () => { - const request = new Request("https://api.example.com", { - headers: { "X-From-Request": "request-value" }, - }); - await makePassthroughRequest(request, undefined, { - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-from-request"]).toBe("request-value"); - }); - - it("should use explicit init over Request object properties", async () => { - const request = new Request("https://api.example.com", { - method: "POST", - headers: { "X-From-Request": "request-value" }, - }); - await makePassthroughRequest( - request, - { method: "PUT", headers: { "X-From-Init": "init-value" } }, - { fetch: mockFetch }, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.method).toBe("PUT"); - expect(calledOptions.headers["x-from-init"]).toBe("init-value"); - // Request headers should NOT be present since explicit init was provided - expect(calledOptions.headers["x-from-request"]).toBeUndefined(); - }); - }); - - describe("SDK default header suppliers", () => { - it("should resolve supplier functions for SDK default headers", async () => { - await makePassthroughRequest("https://api.example.com", undefined, { - headers: { - "X-Static": "static-value", - "X-Dynamic": () => "dynamic-value", - }, - fetch: mockFetch, - }); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.headers["x-static"]).toBe("static-value"); - expect(calledOptions.headers["x-dynamic"]).toBe("dynamic-value"); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts deleted file mode 100644 index bde194554dd8..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/makeRequest.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import type { Mock } from "vitest"; -import { - isCacheNoStoreSupported, - makeRequest, - resetCacheNoStoreSupported, -} from "../../../src/core/fetcher/makeRequest"; - -describe("Test makeRequest", () => { - const mockPostUrl = "https://httpbin.org/post"; - const mockGetUrl = "https://httpbin.org/get"; - const mockHeaders = { "Content-Type": "application/json" }; - const mockBody = JSON.stringify({ key: "value" }); - - let mockFetch: Mock; - - beforeEach(() => { - mockFetch = vi.fn(); - mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); - resetCacheNoStoreSupported(); - }); - - it("should handle POST request correctly", async () => { - const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); - const responseBody = await response.json(); - expect(responseBody).toEqual({ test: "successful" }); - expect(mockFetch).toHaveBeenCalledTimes(1); - const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe(mockPostUrl); - expect(calledOptions).toEqual( - expect.objectContaining({ - method: "POST", - headers: mockHeaders, - body: mockBody, - credentials: undefined, - }), - ); - expect(calledOptions.signal).toBeDefined(); - expect(calledOptions.signal).toBeInstanceOf(AbortSignal); - }); - - it("should handle GET request correctly", async () => { - const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); - const responseBody = await response.json(); - expect(responseBody).toEqual({ test: "successful" }); - expect(mockFetch).toHaveBeenCalledTimes(1); - const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; - expect(calledUrl).toBe(mockGetUrl); - expect(calledOptions).toEqual( - expect.objectContaining({ - method: "GET", - headers: mockHeaders, - body: undefined, - credentials: undefined, - }), - ); - expect(calledOptions.signal).toBeDefined(); - expect(calledOptions.signal).toBeInstanceOf(AbortSignal); - }); - - it("should not include cache option when disableCache is not set", async () => { - await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBeUndefined(); - }); - - it("should not include cache option when disableCache is false", async () => { - await makeRequest( - mockFetch, - mockGetUrl, - "GET", - mockHeaders, - undefined, - undefined, - undefined, - undefined, - undefined, - false, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBeUndefined(); - }); - - it("should include cache: no-store when disableCache is true and runtime supports it", async () => { - // In Node.js test environment, Request supports the cache option - expect(isCacheNoStoreSupported()).toBe(true); - await makeRequest( - mockFetch, - mockGetUrl, - "GET", - mockHeaders, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBe("no-store"); - }); - - it("should cache the result of isCacheNoStoreSupported", () => { - const first = isCacheNoStoreSupported(); - const second = isCacheNoStoreSupported(); - expect(first).toBe(second); - }); - - it("should reset cache detection state with resetCacheNoStoreSupported", () => { - // First call caches the result - const first = isCacheNoStoreSupported(); - expect(first).toBe(true); - - // Reset clears the cache - resetCacheNoStoreSupported(); - - // After reset, it should re-detect (and still return true in Node.js) - const second = isCacheNoStoreSupported(); - expect(second).toBe(true); - }); - - it("should not include cache option when runtime does not support it (e.g. Cloudflare Workers)", async () => { - // Mock Request constructor to throw when cache option is passed, - // simulating runtimes like Cloudflare Workers - const OriginalRequest = globalThis.Request; - globalThis.Request = class MockRequest { - constructor(_url: string, init?: RequestInit) { - if (init?.cache != null) { - throw new TypeError("The 'cache' field on 'RequestInitializerDict' is not implemented."); - } - } - } as unknown as typeof Request; - - try { - // Reset so the detection runs fresh with the mocked Request - resetCacheNoStoreSupported(); - expect(isCacheNoStoreSupported()).toBe(false); - - await makeRequest( - mockFetch, - mockGetUrl, - "GET", - mockHeaders, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - ); - const [, calledOptions] = mockFetch.mock.calls[0]; - expect(calledOptions.cache).toBeUndefined(); - } finally { - // Restore original Request - globalThis.Request = OriginalRequest; - resetCacheNoStoreSupported(); - } - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts deleted file mode 100644 index d599376b9bcf..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/redacting.test.ts +++ /dev/null @@ -1,1115 +0,0 @@ -import { fetcherImpl } from "../../../src/core/fetcher/Fetcher"; - -function createMockLogger() { - return { - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }; -} - -function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") { - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify(data), { - status, - statusText, - }), - ); -} - -describe("Redacting Logic", () => { - describe("Header Redaction", () => { - it("should redact authorization header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { Authorization: "Bearer secret-token-12345" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - Authorization: "[REDACTED]", - }), - }), - ); - }); - - it("should redact api-key header (case-insensitive)", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "X-API-KEY": "secret-api-key" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "X-API-KEY": "[REDACTED]", - }), - }), - ); - }); - - it("should redact cookie header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { Cookie: "session=abc123; token=xyz789" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - Cookie: "[REDACTED]", - }), - }), - ); - }); - - it("should redact x-auth-token header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "x-auth-token": "auth-token-12345" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "x-auth-token": "[REDACTED]", - }), - }), - ); - }); - - it("should redact proxy-authorization header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "Proxy-Authorization": "Basic credentials" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "Proxy-Authorization": "[REDACTED]", - }), - }), - ); - }); - - it("should redact x-csrf-token header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "X-CSRF-Token": "csrf-token-abc" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "X-CSRF-Token": "[REDACTED]", - }), - }), - ); - }); - - it("should redact www-authenticate header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "WWW-Authenticate": "Bearer realm=example" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "WWW-Authenticate": "[REDACTED]", - }), - }), - ); - }); - - it("should redact x-session-token header", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { "X-Session-Token": "session-token-xyz" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "X-Session-Token": "[REDACTED]", - }), - }), - ); - }); - - it("should not redact non-sensitive headers", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { - "Content-Type": "application/json", - "User-Agent": "Test/1.0", - Accept: "application/json", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - "Content-Type": "application/json", - "User-Agent": "Test/1.0", - Accept: "application/json", - }), - }), - ); - }); - - it("should redact multiple sensitive headers at once", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - headers: { - Authorization: "Bearer token", - "X-API-Key": "api-key", - Cookie: "session=123", - "Content-Type": "application/json", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - headers: expect.toContainHeaders({ - Authorization: "[REDACTED]", - "X-API-Key": "[REDACTED]", - Cookie: "[REDACTED]", - "Content-Type": "application/json", - }), - }), - ); - }); - }); - - describe("Response Header Redaction", () => { - it("should redact Set-Cookie in response headers", async () => { - const mockLogger = createMockLogger(); - - const mockHeaders = new Headers(); - mockHeaders.set("Set-Cookie", "session=abc123; HttpOnly; Secure"); - mockHeaders.set("Content-Type", "application/json"); - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - headers: mockHeaders, - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - responseHeaders: expect.toContainHeaders({ - "set-cookie": "[REDACTED]", - "content-type": "application/json", - }), - }), - ); - }); - - it("should redact authorization in response headers", async () => { - const mockLogger = createMockLogger(); - - const mockHeaders = new Headers(); - mockHeaders.set("Authorization", "Bearer token-123"); - mockHeaders.set("Content-Type", "application/json"); - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ data: "test" }), { - status: 200, - statusText: "OK", - headers: mockHeaders, - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "HTTP request succeeded", - expect.objectContaining({ - responseHeaders: expect.toContainHeaders({ - authorization: "[REDACTED]", - "content-type": "application/json", - }), - }), - ); - }); - - it("should redact response headers in error responses", async () => { - const mockLogger = createMockLogger(); - - const mockHeaders = new Headers(); - mockHeaders.set("WWW-Authenticate", "Bearer realm=example"); - mockHeaders.set("Content-Type", "application/json"); - - global.fetch = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ error: "Unauthorized" }), { - status: 401, - statusText: "Unauthorized", - headers: mockHeaders, - }), - ); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "error", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.error).toHaveBeenCalledWith( - "HTTP request failed with error status", - expect.objectContaining({ - responseHeaders: expect.toContainHeaders({ - "www-authenticate": "[REDACTED]", - "content-type": "application/json", - }), - }), - ); - }); - }); - - describe("Query Parameter Redaction", () => { - it("should redact api_key query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { api_key: "secret-key" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - api_key: "[REDACTED]", - }), - }), - ); - }); - - it("should redact token query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { token: "secret-token" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - token: "[REDACTED]", - }), - }), - ); - }); - - it("should redact access_token query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { access_token: "secret-access-token" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - access_token: "[REDACTED]", - }), - }), - ); - }); - - it("should redact password query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { password: "secret-password" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - password: "[REDACTED]", - }), - }), - ); - }); - - it("should redact secret query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { secret: "secret-value" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - secret: "[REDACTED]", - }), - }), - ); - }); - - it("should redact session_id query parameter", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { session_id: "session-123" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - session_id: "[REDACTED]", - }), - }), - ); - }); - - it("should not redact non-sensitive query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { - page: "1", - limit: "10", - sort: "name", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - page: "1", - limit: "10", - sort: "name", - }), - }), - ); - }); - - it("should not redact parameters containing 'auth' substring like 'author'", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { - author: "john", - authenticate: "false", - authorization_level: "user", - }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - author: "john", - authenticate: "false", - authorization_level: "user", - }), - }), - ); - }); - - it("should handle undefined query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: undefined, - }), - ); - }); - - it("should redact case-insensitive query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - queryParameters: { API_KEY: "secret-key", Token: "secret-token" }, - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - queryParameters: expect.objectContaining({ - API_KEY: "[REDACTED]", - Token: "[REDACTED]", - }), - }), - ); - }); - }); - - describe("URL Redaction", () => { - it("should redact credentials in URL", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user:password@example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@example.com/api", - }), - ); - }); - - it("should redact api_key in query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?api_key=secret-key&page=1", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?api_key=[REDACTED]&page=1", - }), - ); - }); - - it("should redact token in query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?token=secret-token", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?token=[REDACTED]", - }), - ); - }); - - it("should redact password in query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?username=user&password=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?username=user&password=[REDACTED]", - }), - ); - }); - - it("should not redact non-sensitive query strings", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?page=1&limit=10&sort=name", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?page=1&limit=10&sort=name", - }), - ); - }); - - it("should not redact URL parameters containing 'auth' substring like 'author'", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?author=john&authenticate=false&page=1", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?author=john&authenticate=false&page=1", - }), - ); - }); - - it("should handle URL with fragment", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?token=secret#section", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?token=[REDACTED]#section", - }), - ); - }); - - it("should redact URL-encoded query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?api%5Fkey=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?api%5Fkey=[REDACTED]", - }), - ); - }); - - it("should handle URL without query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api", - }), - ); - }); - - it("should handle empty query string", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?", - }), - ); - }); - - it("should redact multiple sensitive parameters in URL", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?api_key=secret1&token=secret2&page=1", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?api_key=[REDACTED]&token=[REDACTED]&page=1", - }), - ); - }); - - it("should redact both credentials and query parameters", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user:pass@example.com/api?token=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@example.com/api?token=[REDACTED]", - }), - ); - }); - - it("should use fast path for URLs without sensitive keywords", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?page=1&limit=10&sort=name&filter=value", - }), - ); - }); - - it("should handle query parameter without value", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?flag&token=secret", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?flag&token=[REDACTED]", - }), - ); - }); - - it("should handle URL with multiple @ symbols in credentials", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user@example.com:pass@host.com/api", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@host.com/api", - }), - ); - }); - - it("should handle URL with @ in query parameter but not in credentials", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://example.com/api?email=user@example.com", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://example.com/api?email=user@example.com", - }), - ); - }); - - it("should handle URL with both credentials and @ in path", async () => { - const mockLogger = createMockLogger(); - mockSuccessResponse(); - - await fetcherImpl({ - url: "https://user:pass@example.com/users/@username", - method: "GET", - responseType: "json", - maxRetries: 0, - logging: { - level: "debug", - logger: mockLogger, - silent: false, - }, - }); - - expect(mockLogger.debug).toHaveBeenCalledWith( - "Making HTTP request", - expect.objectContaining({ - url: "https://[REDACTED]@example.com/users/@username", - }), - ); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts deleted file mode 100644 index d22661367f4e..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/requestWithRetries.test.ts +++ /dev/null @@ -1,230 +0,0 @@ -import type { Mock, MockInstance } from "vitest"; -import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; - -describe("requestWithRetries", () => { - let mockFetch: Mock; - let originalMathRandom: typeof Math.random; - let setTimeoutSpy: MockInstance; - - beforeEach(() => { - mockFetch = vi.fn(); - originalMathRandom = Math.random; - - Math.random = vi.fn(() => 0.5); - - vi.useFakeTimers({ - toFake: [ - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "Date", - "performance", - "requestAnimationFrame", - "cancelAnimationFrame", - "requestIdleCallback", - "cancelIdleCallback", - ], - }); - }); - - afterEach(() => { - Math.random = originalMathRandom; - vi.clearAllMocks(); - vi.clearAllTimers(); - }); - - it("should retry on retryable status codes", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const retryableStatuses = [408, 429, 500, 502]; - let callCount = 0; - - mockFetch.mockImplementation(async () => { - if (callCount < retryableStatuses.length) { - return new Response("", { status: retryableStatuses[callCount++] }); - } - return new Response("", { status: 200 }); - }); - - const responsePromise = requestWithRetries(() => mockFetch(), retryableStatuses.length); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(mockFetch).toHaveBeenCalledTimes(retryableStatuses.length + 1); - expect(response.status).toBe(200); - }); - - it("should respect maxRetries limit", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const maxRetries = 2; - mockFetch.mockResolvedValue(new Response("", { status: 500 })); - - const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); - expect(response.status).toBe(500); - }); - - it("should not retry on success status codes", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const successStatuses = [200, 201, 202]; - - for (const status of successStatuses) { - mockFetch.mockReset(); - setTimeoutSpy.mockClear(); - mockFetch.mockResolvedValueOnce(new Response("", { status })); - - const responsePromise = requestWithRetries(() => mockFetch(), 3); - await vi.runAllTimersAsync(); - await responsePromise; - - expect(mockFetch).toHaveBeenCalledTimes(1); - expect(setTimeoutSpy).not.toHaveBeenCalled(); - } - }); - - interface RetryHeaderTestCase { - description: string; - headerName: string; - headerValue: string | (() => string); - expectedDelayMin: number; - expectedDelayMax: number; - } - - const retryHeaderTests: RetryHeaderTestCase[] = [ - { - description: "should respect retry-after header with seconds value", - headerName: "retry-after", - headerValue: "5", - expectedDelayMin: 4000, - expectedDelayMax: 6000, - }, - { - description: "should respect retry-after header with HTTP date value", - headerName: "retry-after", - headerValue: () => new Date(Date.now() + 3000).toUTCString(), - expectedDelayMin: 2000, - expectedDelayMax: 4000, - }, - { - description: "should respect x-ratelimit-reset header", - headerName: "x-ratelimit-reset", - headerValue: () => Math.floor((Date.now() + 4000) / 1000).toString(), - expectedDelayMin: 3000, - expectedDelayMax: 6000, - }, - ]; - - retryHeaderTests.forEach(({ description, headerName, headerValue, expectedDelayMin, expectedDelayMax }) => { - it(description, async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - const value = typeof headerValue === "function" ? headerValue() : headerValue; - mockFetch - .mockResolvedValueOnce( - new Response("", { - status: 429, - headers: new Headers({ [headerName]: value }), - }), - ) - .mockResolvedValueOnce(new Response("", { status: 200 })); - - const responsePromise = requestWithRetries(() => mockFetch(), 1); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), expect.any(Number)); - const actualDelay = setTimeoutSpy.mock.calls[0][1]; - expect(actualDelay).toBeGreaterThan(expectedDelayMin); - expect(actualDelay).toBeLessThan(expectedDelayMax); - expect(response.status).toBe(200); - }); - }); - - it("should apply correct exponential backoff with jitter", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - mockFetch.mockResolvedValue(new Response("", { status: 500 })); - const maxRetries = 3; - const expectedDelays = [1000, 2000, 4000]; - - const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); - await vi.runAllTimersAsync(); - await responsePromise; - - expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length); - - expectedDelays.forEach((delay, index) => { - expect(setTimeoutSpy).toHaveBeenNthCalledWith(index + 1, expect.any(Function), delay); - }); - - expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); - }); - - it("should handle concurrent retries independently", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - mockFetch - .mockResolvedValueOnce(new Response("", { status: 500 })) - .mockResolvedValueOnce(new Response("", { status: 500 })) - .mockResolvedValueOnce(new Response("", { status: 200 })) - .mockResolvedValueOnce(new Response("", { status: 200 })); - - const promise1 = requestWithRetries(() => mockFetch(), 1); - const promise2 = requestWithRetries(() => mockFetch(), 1); - - await vi.runAllTimersAsync(); - const [response1, response2] = await Promise.all([promise1, promise2]); - - expect(response1.status).toBe(200); - expect(response2.status).toBe(200); - }); - - it("should cap delay at MAX_RETRY_DELAY for large header values", async () => { - setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); - - mockFetch - .mockResolvedValueOnce( - new Response("", { - status: 429, - headers: new Headers({ "retry-after": "120" }), // 120 seconds = 120000ms > MAX_RETRY_DELAY (60000ms) - }), - ) - .mockResolvedValueOnce(new Response("", { status: 200 })); - - const responsePromise = requestWithRetries(() => mockFetch(), 1); - await vi.runAllTimersAsync(); - const response = await responsePromise; - - expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 60000); - expect(response.status).toBe(200); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts b/seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts deleted file mode 100644 index d7b6d1e63caa..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/signals.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; - -describe("Test getTimeoutSignal", () => { - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it("should return an object with signal and abortId", () => { - const { signal, abortId } = getTimeoutSignal(1000); - - expect(signal).toBeDefined(); - expect(abortId).toBeDefined(); - expect(signal).toBeInstanceOf(AbortSignal); - expect(signal.aborted).toBe(false); - }); - - it("should create a signal that aborts after the specified timeout", () => { - const timeoutMs = 5000; - const { signal } = getTimeoutSignal(timeoutMs); - - expect(signal.aborted).toBe(false); - - vi.advanceTimersByTime(timeoutMs - 1); - expect(signal.aborted).toBe(false); - - vi.advanceTimersByTime(1); - expect(signal.aborted).toBe(true); - }); -}); - -describe("Test anySignal", () => { - it("should return an AbortSignal", () => { - const signal = anySignal(new AbortController().signal); - expect(signal).toBeInstanceOf(AbortSignal); - }); - - it("should abort when any of the input signals is aborted", () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - const signal = anySignal(controller1.signal, controller2.signal); - - expect(signal.aborted).toBe(false); - controller1.abort(); - expect(signal.aborted).toBe(true); - }); - - it("should handle an array of signals", () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - const signal = anySignal([controller1.signal, controller2.signal]); - - expect(signal.aborted).toBe(false); - controller2.abort(); - expect(signal.aborted).toBe(true); - }); - - it("should abort immediately if one of the input signals is already aborted", () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - controller1.abort(); - - const signal = anySignal(controller1.signal, controller2.signal); - expect(signal.aborted).toBe(true); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt b/seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt deleted file mode 100644 index c66d471e359c..000000000000 --- a/seed/ts-sdk/allof/tests/unit/fetcher/test-file.txt +++ /dev/null @@ -1 +0,0 @@ -This is a test file! diff --git a/seed/ts-sdk/allof/tests/unit/logging/logger.test.ts b/seed/ts-sdk/allof/tests/unit/logging/logger.test.ts deleted file mode 100644 index 2e0b5fe5040c..000000000000 --- a/seed/ts-sdk/allof/tests/unit/logging/logger.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -import { ConsoleLogger, createLogger, Logger, LogLevel } from "../../../src/core/logging/logger"; - -function createMockLogger() { - return { - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }; -} - -describe("Logger", () => { - describe("LogLevel", () => { - it("should have correct log levels", () => { - expect(LogLevel.Debug).toBe("debug"); - expect(LogLevel.Info).toBe("info"); - expect(LogLevel.Warn).toBe("warn"); - expect(LogLevel.Error).toBe("error"); - }); - }); - - describe("ConsoleLogger", () => { - let consoleLogger: ConsoleLogger; - let consoleSpy: { - debug: ReturnType; - info: ReturnType; - warn: ReturnType; - error: ReturnType; - }; - - beforeEach(() => { - consoleLogger = new ConsoleLogger(); - consoleSpy = { - debug: vi.spyOn(console, "debug").mockImplementation(() => {}), - info: vi.spyOn(console, "info").mockImplementation(() => {}), - warn: vi.spyOn(console, "warn").mockImplementation(() => {}), - error: vi.spyOn(console, "error").mockImplementation(() => {}), - }; - }); - - afterEach(() => { - consoleSpy.debug.mockRestore(); - consoleSpy.info.mockRestore(); - consoleSpy.warn.mockRestore(); - consoleSpy.error.mockRestore(); - }); - - it("should log debug messages", () => { - consoleLogger.debug("debug message", { data: "test" }); - expect(consoleSpy.debug).toHaveBeenCalledWith("debug message", { data: "test" }); - }); - - it("should log info messages", () => { - consoleLogger.info("info message", { data: "test" }); - expect(consoleSpy.info).toHaveBeenCalledWith("info message", { data: "test" }); - }); - - it("should log warn messages", () => { - consoleLogger.warn("warn message", { data: "test" }); - expect(consoleSpy.warn).toHaveBeenCalledWith("warn message", { data: "test" }); - }); - - it("should log error messages", () => { - consoleLogger.error("error message", { data: "test" }); - expect(consoleSpy.error).toHaveBeenCalledWith("error message", { data: "test" }); - }); - - it("should handle multiple arguments", () => { - consoleLogger.debug("message", "arg1", "arg2", { key: "value" }); - expect(consoleSpy.debug).toHaveBeenCalledWith("message", "arg1", "arg2", { key: "value" }); - }); - }); - - describe("Logger with level filtering", () => { - let mockLogger: { - debug: ReturnType; - info: ReturnType; - warn: ReturnType; - error: ReturnType; - }; - - beforeEach(() => { - mockLogger = createMockLogger(); - }); - - describe("Debug level", () => { - it("should log all levels when set to debug", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).toHaveBeenCalledWith("debug"); - expect(mockLogger.info).toHaveBeenCalledWith("info"); - expect(mockLogger.warn).toHaveBeenCalledWith("warn"); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(true); - expect(logger.isInfo()).toBe(true); - expect(logger.isWarn()).toBe(true); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Info level", () => { - it("should log info, warn, and error when set to info", () => { - const logger = new Logger({ - level: LogLevel.Info, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).toHaveBeenCalledWith("info"); - expect(mockLogger.warn).toHaveBeenCalledWith("warn"); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Info, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(true); - expect(logger.isWarn()).toBe(true); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Warn level", () => { - it("should log warn and error when set to warn", () => { - const logger = new Logger({ - level: LogLevel.Warn, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).toHaveBeenCalledWith("warn"); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Warn, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(false); - expect(logger.isWarn()).toBe(true); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Error level", () => { - it("should only log error when set to error", () => { - const logger = new Logger({ - level: LogLevel.Error, - logger: mockLogger, - silent: false, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).not.toHaveBeenCalled(); - expect(mockLogger.error).toHaveBeenCalledWith("error"); - }); - - it("should report correct level checks", () => { - const logger = new Logger({ - level: LogLevel.Error, - logger: mockLogger, - silent: false, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(false); - expect(logger.isWarn()).toBe(false); - expect(logger.isError()).toBe(true); - }); - }); - - describe("Silent mode", () => { - it("should not log anything when silent is true", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: true, - }); - - logger.debug("debug"); - logger.info("info"); - logger.warn("warn"); - logger.error("error"); - - expect(mockLogger.debug).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalled(); - expect(mockLogger.warn).not.toHaveBeenCalled(); - expect(mockLogger.error).not.toHaveBeenCalled(); - }); - - it("should report all level checks as false when silent", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: true, - }); - - expect(logger.isDebug()).toBe(false); - expect(logger.isInfo()).toBe(false); - expect(logger.isWarn()).toBe(false); - expect(logger.isError()).toBe(false); - }); - }); - - describe("shouldLog", () => { - it("should correctly determine if level should be logged", () => { - const logger = new Logger({ - level: LogLevel.Info, - logger: mockLogger, - silent: false, - }); - - expect(logger.shouldLog(LogLevel.Debug)).toBe(false); - expect(logger.shouldLog(LogLevel.Info)).toBe(true); - expect(logger.shouldLog(LogLevel.Warn)).toBe(true); - expect(logger.shouldLog(LogLevel.Error)).toBe(true); - }); - - it("should return false for all levels when silent", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: true, - }); - - expect(logger.shouldLog(LogLevel.Debug)).toBe(false); - expect(logger.shouldLog(LogLevel.Info)).toBe(false); - expect(logger.shouldLog(LogLevel.Warn)).toBe(false); - expect(logger.shouldLog(LogLevel.Error)).toBe(false); - }); - }); - - describe("Multiple arguments", () => { - it("should pass multiple arguments to logger", () => { - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("message", "arg1", { key: "value" }, 123); - expect(mockLogger.debug).toHaveBeenCalledWith("message", "arg1", { key: "value" }, 123); - }); - }); - }); - - describe("createLogger", () => { - it("should return default logger when no config provided", () => { - const logger = createLogger(); - expect(logger).toBeInstanceOf(Logger); - }); - - it("should return same logger instance when Logger is passed", () => { - const customLogger = new Logger({ - level: LogLevel.Debug, - logger: new ConsoleLogger(), - silent: false, - }); - - const result = createLogger(customLogger); - expect(result).toBe(customLogger); - }); - - it("should create logger with custom config", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - level: LogLevel.Warn, - logger: mockLogger, - silent: false, - }); - - expect(logger).toBeInstanceOf(Logger); - logger.warn("test"); - expect(mockLogger.warn).toHaveBeenCalledWith("test"); - }); - - it("should use default values for missing config", () => { - const logger = createLogger({}); - expect(logger).toBeInstanceOf(Logger); - }); - - it("should override default level", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("test"); - expect(mockLogger.debug).toHaveBeenCalledWith("test"); - }); - - it("should override default silent mode", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - logger: mockLogger, - silent: false, - }); - - logger.info("test"); - expect(mockLogger.info).toHaveBeenCalledWith("test"); - }); - - it("should use provided logger implementation", () => { - const customLogger = createMockLogger(); - - const logger = createLogger({ - logger: customLogger, - level: LogLevel.Debug, - silent: false, - }); - - logger.debug("test"); - expect(customLogger.debug).toHaveBeenCalledWith("test"); - }); - - it("should default to silent: true", () => { - const mockLogger = createMockLogger(); - - const logger = createLogger({ - logger: mockLogger, - level: LogLevel.Debug, - }); - - logger.debug("test"); - expect(mockLogger.debug).not.toHaveBeenCalled(); - }); - }); - - describe("Default logger", () => { - it("should have silent: true by default", () => { - const logger = createLogger(); - expect(logger.shouldLog(LogLevel.Info)).toBe(false); - }); - - it("should not log when using default logger", () => { - const logger = createLogger(); - - logger.info("test"); - expect(logger.isInfo()).toBe(false); - }); - }); - - describe("Edge cases", () => { - it("should handle empty message", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug(""); - expect(mockLogger.debug).toHaveBeenCalledWith(""); - }); - - it("should handle no arguments", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - logger.debug("message"); - expect(mockLogger.debug).toHaveBeenCalledWith("message"); - }); - - it("should handle complex objects", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Debug, - logger: mockLogger, - silent: false, - }); - - const complexObject = { - nested: { key: "value" }, - array: [1, 2, 3], - fn: () => "test", - }; - - logger.debug("message", complexObject); - expect(mockLogger.debug).toHaveBeenCalledWith("message", complexObject); - }); - - it("should handle errors as arguments", () => { - const mockLogger = createMockLogger(); - - const logger = new Logger({ - level: LogLevel.Error, - logger: mockLogger, - silent: false, - }); - - const error = new Error("Test error"); - logger.error("Error occurred", error); - expect(mockLogger.error).toHaveBeenCalledWith("Error occurred", error); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/url/join.test.ts b/seed/ts-sdk/allof/tests/unit/url/join.test.ts deleted file mode 100644 index 123488f084ea..000000000000 --- a/seed/ts-sdk/allof/tests/unit/url/join.test.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { join } from "../../../src/core/url/index"; - -describe("join", () => { - interface TestCase { - description: string; - base: string; - segments: string[]; - expected: string; - } - - describe("basic functionality", () => { - const basicTests: TestCase[] = [ - { description: "should return empty string for empty base", base: "", segments: [], expected: "" }, - { - description: "should return empty string for empty base with path", - base: "", - segments: ["path"], - expected: "", - }, - { - description: "should handle single segment", - base: "base", - segments: ["segment"], - expected: "base/segment", - }, - { - description: "should handle single segment with trailing slash on base", - base: "base/", - segments: ["segment"], - expected: "base/segment", - }, - { - description: "should handle single segment with leading slash", - base: "base", - segments: ["/segment"], - expected: "base/segment", - }, - { - description: "should handle single segment with both slashes", - base: "base/", - segments: ["/segment"], - expected: "base/segment", - }, - { - description: "should handle multiple segments", - base: "base", - segments: ["path1", "path2", "path3"], - expected: "base/path1/path2/path3", - }, - { - description: "should handle multiple segments with slashes", - base: "base/", - segments: ["/path1/", "/path2/", "/path3/"], - expected: "base/path1/path2/path3/", - }, - ]; - - basicTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("URL handling", () => { - const urlTests: TestCase[] = [ - { - description: "should handle absolute URLs", - base: "https://example.com", - segments: ["api", "v1"], - expected: "https://example.com/api/v1", - }, - { - description: "should handle absolute URLs with slashes", - base: "https://example.com/", - segments: ["/api/", "/v1/"], - expected: "https://example.com/api/v1/", - }, - { - description: "should handle absolute URLs with base path", - base: "https://example.com/base", - segments: ["api", "v1"], - expected: "https://example.com/base/api/v1", - }, - { - description: "should preserve URL query parameters", - base: "https://example.com?query=1", - segments: ["api"], - expected: "https://example.com/api?query=1", - }, - { - description: "should preserve URL fragments", - base: "https://example.com#fragment", - segments: ["api"], - expected: "https://example.com/api#fragment", - }, - { - description: "should preserve URL query and fragments", - base: "https://example.com?query=1#fragment", - segments: ["api"], - expected: "https://example.com/api?query=1#fragment", - }, - { - description: "should handle http protocol", - base: "http://example.com", - segments: ["api"], - expected: "http://example.com/api", - }, - { - description: "should handle ftp protocol", - base: "ftp://example.com", - segments: ["files"], - expected: "ftp://example.com/files", - }, - { - description: "should handle ws protocol", - base: "ws://example.com", - segments: ["socket"], - expected: "ws://example.com/socket", - }, - { - description: "should fallback to path joining for malformed URLs", - base: "not-a-url://", - segments: ["path"], - expected: "not-a-url:///path", - }, - ]; - - urlTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("edge cases", () => { - const edgeCaseTests: TestCase[] = [ - { - description: "should handle empty segments", - base: "base", - segments: ["", "path"], - expected: "base/path", - }, - { - description: "should handle null segments", - base: "base", - segments: [null as any, "path"], - expected: "base/path", - }, - { - description: "should handle undefined segments", - base: "base", - segments: [undefined as any, "path"], - expected: "base/path", - }, - { - description: "should handle segments with only single slash", - base: "base", - segments: ["/", "path"], - expected: "base/path", - }, - { - description: "should handle segments with only double slash", - base: "base", - segments: ["//", "path"], - expected: "base/path", - }, - { - description: "should handle base paths with trailing slashes", - base: "base/", - segments: ["path"], - expected: "base/path", - }, - { - description: "should handle complex nested paths", - base: "api/v1/", - segments: ["/users/", "/123/", "/profile"], - expected: "api/v1/users/123/profile", - }, - ]; - - edgeCaseTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("real-world scenarios", () => { - const realWorldTests: TestCase[] = [ - { - description: "should handle API endpoint construction", - base: "https://api.example.com/v1", - segments: ["users", "123", "posts"], - expected: "https://api.example.com/v1/users/123/posts", - }, - { - description: "should handle file path construction", - base: "/var/www", - segments: ["html", "assets", "images"], - expected: "/var/www/html/assets/images", - }, - { - description: "should handle relative path construction", - base: "../parent", - segments: ["child", "grandchild"], - expected: "../parent/child/grandchild", - }, - { - description: "should handle Windows-style paths", - base: "C:\\Users", - segments: ["Documents", "file.txt"], - expected: "C:\\Users/Documents/file.txt", - }, - ]; - - realWorldTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); - - describe("performance scenarios", () => { - it("should handle many segments efficiently", () => { - const segments = Array(100).fill("segment"); - const result = join("base", ...segments); - expect(result).toBe(`base/${segments.join("/")}`); - }); - - it("should handle long URLs", () => { - const longPath = "a".repeat(1000); - expect(join("https://example.com", longPath)).toBe(`https://example.com/${longPath}`); - }); - }); - - describe("trailing slash preservation", () => { - const trailingSlashTests: TestCase[] = [ - { - description: - "should preserve trailing slash on final result when base has trailing slash and no segments", - base: "https://api.example.com/", - segments: [], - expected: "https://api.example.com/", - }, - { - description: "should preserve trailing slash on v1 path", - base: "https://api.example.com/v1/", - segments: [], - expected: "https://api.example.com/v1/", - }, - { - description: "should preserve trailing slash when last segment has trailing slash", - base: "https://api.example.com", - segments: ["users/"], - expected: "https://api.example.com/users/", - }, - { - description: "should preserve trailing slash with relative path", - base: "api/v1", - segments: ["users/"], - expected: "api/v1/users/", - }, - { - description: "should preserve trailing slash with multiple segments", - base: "https://api.example.com", - segments: ["v1", "collections/"], - expected: "https://api.example.com/v1/collections/", - }, - { - description: "should preserve trailing slash with base path", - base: "base", - segments: ["path1", "path2/"], - expected: "base/path1/path2/", - }, - ]; - - trailingSlashTests.forEach(({ description, base, segments, expected }) => { - it(description, () => { - expect(join(base, ...segments)).toBe(expected); - }); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/unit/url/qs.test.ts b/seed/ts-sdk/allof/tests/unit/url/qs.test.ts deleted file mode 100644 index 42cdffb9e5ea..000000000000 --- a/seed/ts-sdk/allof/tests/unit/url/qs.test.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { toQueryString } from "../../../src/core/url/index"; - -describe("Test qs toQueryString", () => { - interface BasicTestCase { - description: string; - input: any; - expected: string; - } - - describe("Basic functionality", () => { - const basicTests: BasicTestCase[] = [ - { description: "should return empty string for null", input: null, expected: "" }, - { description: "should return empty string for undefined", input: undefined, expected: "" }, - { description: "should return empty string for string primitive", input: "hello", expected: "" }, - { description: "should return empty string for number primitive", input: 42, expected: "" }, - { description: "should return empty string for true boolean", input: true, expected: "" }, - { description: "should return empty string for false boolean", input: false, expected: "" }, - { description: "should handle empty objects", input: {}, expected: "" }, - { - description: "should handle simple key-value pairs", - input: { name: "John", age: 30 }, - expected: "name=John&age=30", - }, - ]; - - basicTests.forEach(({ description, input, expected }) => { - it(description, () => { - expect(toQueryString(input)).toBe(expected); - }); - }); - }); - - describe("Array handling", () => { - interface ArrayTestCase { - description: string; - input: any; - options?: { arrayFormat?: "repeat" | "indices" }; - expected: string; - } - - const arrayTests: ArrayTestCase[] = [ - { - description: "should handle arrays with indices format (default)", - input: { items: ["a", "b", "c"] }, - expected: "items%5B0%5D=a&items%5B1%5D=b&items%5B2%5D=c", - }, - { - description: "should handle arrays with repeat format", - input: { items: ["a", "b", "c"] }, - options: { arrayFormat: "repeat" }, - expected: "items=a&items=b&items=c", - }, - { - description: "should handle empty arrays", - input: { items: [] }, - expected: "", - }, - { - description: "should handle arrays with mixed types", - input: { mixed: ["string", 42, true, false] }, - expected: "mixed%5B0%5D=string&mixed%5B1%5D=42&mixed%5B2%5D=true&mixed%5B3%5D=false", - }, - { - description: "should handle arrays with objects", - input: { users: [{ name: "John" }, { name: "Jane" }] }, - expected: "users%5B0%5D%5Bname%5D=John&users%5B1%5D%5Bname%5D=Jane", - }, - { - description: "should handle arrays with objects in repeat format", - input: { users: [{ name: "John" }, { name: "Jane" }] }, - options: { arrayFormat: "repeat" }, - expected: "users%5Bname%5D=John&users%5Bname%5D=Jane", - }, - ]; - - arrayTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); - - describe("Nested objects", () => { - const nestedTests: BasicTestCase[] = [ - { - description: "should handle nested objects", - input: { user: { name: "John", age: 30 } }, - expected: "user%5Bname%5D=John&user%5Bage%5D=30", - }, - { - description: "should handle deeply nested objects", - input: { user: { profile: { name: "John", settings: { theme: "dark" } } } }, - expected: "user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", - }, - { - description: "should handle empty nested objects", - input: { user: {} }, - expected: "", - }, - ]; - - nestedTests.forEach(({ description, input, expected }) => { - it(description, () => { - expect(toQueryString(input)).toBe(expected); - }); - }); - }); - - describe("Encoding", () => { - interface EncodingTestCase { - description: string; - input: any; - options?: { encode?: boolean }; - expected: string; - } - - const encodingTests: EncodingTestCase[] = [ - { - description: "should encode by default", - input: { name: "John Doe", email: "john@example.com" }, - expected: "name=John%20Doe&email=john%40example.com", - }, - { - description: "should not encode when encode is false", - input: { name: "John Doe", email: "john@example.com" }, - options: { encode: false }, - expected: "name=John Doe&email=john@example.com", - }, - { - description: "should encode special characters in keys", - input: { "user name": "John", "email[primary]": "john@example.com" }, - expected: "user%20name=John&email%5Bprimary%5D=john%40example.com", - }, - { - description: "should not encode special characters in keys when encode is false", - input: { "user name": "John", "email[primary]": "john@example.com" }, - options: { encode: false }, - expected: "user name=John&email[primary]=john@example.com", - }, - ]; - - encodingTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); - - describe("Mixed scenarios", () => { - interface MixedTestCase { - description: string; - input: any; - options?: { arrayFormat?: "repeat" | "indices" }; - expected: string; - } - - const mixedTests: MixedTestCase[] = [ - { - description: "should handle complex nested structures", - input: { - filters: { - status: ["active", "pending"], - category: { - type: "electronics", - subcategories: ["phones", "laptops"], - }, - }, - sort: { field: "name", direction: "asc" }, - }, - expected: - "filters%5Bstatus%5D%5B0%5D=active&filters%5Bstatus%5D%5B1%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D%5B0%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D%5B1%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", - }, - { - description: "should handle complex nested structures with repeat format", - input: { - filters: { - status: ["active", "pending"], - category: { - type: "electronics", - subcategories: ["phones", "laptops"], - }, - }, - sort: { field: "name", direction: "asc" }, - }, - options: { arrayFormat: "repeat" }, - expected: - "filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", - }, - { - description: "should handle arrays with null/undefined values", - input: { items: ["a", null, "c", undefined, "e"] }, - expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e", - }, - { - description: "should handle objects with null/undefined values", - input: { name: "John", age: null, email: undefined, active: true }, - expected: "name=John&age=&active=true", - }, - ]; - - mixedTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); - - describe("Edge cases", () => { - const edgeCaseTests: BasicTestCase[] = [ - { - description: "should handle numeric keys", - input: { "0": "zero", "1": "one" }, - expected: "0=zero&1=one", - }, - { - description: "should handle boolean values in objects", - input: { enabled: true, disabled: false }, - expected: "enabled=true&disabled=false", - }, - { - description: "should handle empty strings", - input: { name: "", description: "test" }, - expected: "name=&description=test", - }, - { - description: "should handle zero values", - input: { count: 0, price: 0.0 }, - expected: "count=0&price=0", - }, - { - description: "should handle arrays with empty strings", - input: { items: ["a", "", "c"] }, - expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c", - }, - ]; - - edgeCaseTests.forEach(({ description, input, expected }) => { - it(description, () => { - expect(toQueryString(input)).toBe(expected); - }); - }); - }); - - describe("Options combinations", () => { - interface OptionsTestCase { - description: string; - input: any; - options?: { arrayFormat?: "repeat" | "indices"; encode?: boolean }; - expected: string; - } - - const optionsTests: OptionsTestCase[] = [ - { - description: "should respect both arrayFormat and encode options", - input: { items: ["a & b", "c & d"] }, - options: { arrayFormat: "repeat", encode: false }, - expected: "items=a & b&items=c & d", - }, - { - description: "should use default options when none provided", - input: { items: ["a", "b"] }, - expected: "items%5B0%5D=a&items%5B1%5D=b", - }, - { - description: "should merge provided options with defaults", - input: { items: ["a", "b"], name: "John Doe" }, - options: { encode: false }, - expected: "items[0]=a&items[1]=b&name=John Doe", - }, - ]; - - optionsTests.forEach(({ description, input, options, expected }) => { - it(description, () => { - expect(toQueryString(input, options)).toBe(expected); - }); - }); - }); -}); diff --git a/seed/ts-sdk/allof/tests/wire/.gitkeep b/seed/ts-sdk/allof/tests/wire/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/seed/ts-sdk/allof/tests/wire/main.test.ts b/seed/ts-sdk/allof/tests/wire/main.test.ts deleted file mode 100644 index 6af233ca61ca..000000000000 --- a/seed/ts-sdk/allof/tests/wire/main.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import { SeedApiClient } from "../../src/Client"; -import { mockServerPool } from "../mock-server/MockServerPool"; - -describe("SeedApiClient", () => { - test("searchRuleTypes", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { - paging: { next: "next", previous: "previous" }, - results: [{ id: "id", name: "name", description: "description" }], - }; - - server.mockEndpoint().get("/rule-types").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.searchRuleTypes(); - expect(response).toEqual(rawResponseBody); - }); - - test("createRule", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - const rawRequestBody = { name: "name", executionContext: "prod" }; - const rawResponseBody = { - createdBy: "createdBy", - createdDateTime: "2024-01-15T09:30:00Z", - modifiedBy: "modifiedBy", - modifiedDateTime: "2024-01-15T09:30:00Z", - id: "id", - name: "name", - status: "active", - executionContext: "prod", - }; - - server - .mockEndpoint() - .post("/rules") - .jsonBody(rawRequestBody) - .respondWith() - .statusCode(200) - .jsonBody(rawResponseBody) - .build(); - - const response = await client.createRule({ - name: "name", - executionContext: "prod", - }); - expect(response).toEqual(rawResponseBody); - }); - - test("listUsers", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { - paging: { next: "next", previous: "previous" }, - results: [{ id: "id", email: "email" }], - }; - - server.mockEndpoint().get("/users").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.listUsers(); - expect(response).toEqual(rawResponseBody); - }); - - test("getEntity", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { name: "name", summary: "summary", id: "id", status: "active" }; - - server.mockEndpoint().get("/entities").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.getEntity(); - expect(response).toEqual(rawResponseBody); - }); - - test("getOrganization", async () => { - const server = mockServerPool.createServer(); - const client = new SeedApiClient({ maxRetries: 0, environment: server.baseUrl }); - - const rawResponseBody = { metadata: { region: "region", tier: "tier" }, id: "id", name: "name" }; - - server.mockEndpoint().get("/organizations").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.getOrganization(); - expect(response).toEqual(rawResponseBody); - }); -}); diff --git a/seed/ts-sdk/allof/tsconfig.base.json b/seed/ts-sdk/allof/tsconfig.base.json deleted file mode 100644 index 93a92c0630b5..000000000000 --- a/seed/ts-sdk/allof/tsconfig.base.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "extendedDiagnostics": true, - "strict": true, - "target": "ES6", - "moduleResolution": "node", - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "outDir": "dist", - "rootDir": "src", - "isolatedModules": true, - "isolatedDeclarations": true - }, - "include": ["src"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof/tsconfig.cjs.json b/seed/ts-sdk/allof/tsconfig.cjs.json deleted file mode 100644 index 5c11446f5984..000000000000 --- a/seed/ts-sdk/allof/tsconfig.cjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "CommonJS", - "outDir": "dist/cjs" - }, - "include": ["src"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof/tsconfig.esm.json b/seed/ts-sdk/allof/tsconfig.esm.json deleted file mode 100644 index 6ce909748b2c..000000000000 --- a/seed/ts-sdk/allof/tsconfig.esm.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "outDir": "dist/esm", - "verbatimModuleSyntax": true - }, - "include": ["src"], - "exclude": [] -} diff --git a/seed/ts-sdk/allof/tsconfig.json b/seed/ts-sdk/allof/tsconfig.json deleted file mode 100644 index d77fdf00d259..000000000000 --- a/seed/ts-sdk/allof/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./tsconfig.cjs.json" -} diff --git a/seed/ts-sdk/allof/vitest.config.mts b/seed/ts-sdk/allof/vitest.config.mts deleted file mode 100644 index 0dee5a752d39..000000000000 --- a/seed/ts-sdk/allof/vitest.config.mts +++ /dev/null @@ -1,32 +0,0 @@ -import { defineConfig } from "vitest/config"; -export default defineConfig({ - test: { - typecheck: { - enabled: true, - tsconfig: "./tests/tsconfig.json", - }, - projects: [ - { - test: { - globals: true, - name: "unit", - environment: "node", - root: "./tests", - include: ["**/*.test.{js,ts,jsx,tsx}"], - exclude: ["wire/**"], - setupFiles: ["./setup.ts"], - }, - }, - { - test: { - globals: true, - name: "wire", - environment: "node", - root: "./tests/wire", - setupFiles: ["../setup.ts", "../mock-server/setup.ts"], - }, - }, - ], - passWithNoTests: true, - }, -});